Progress Bar

Implement a progress bar that updates based on a value input by the user.

Solution

Enter a string with at least 50 characters

Explanation

The HTML Structure

A progress container will have two overlapping elements. A background div that has a constant fixed width and a progress div that will change size based on a calculation.

Beneath that is the text area to enter the content to be counted.

Counting the characters

The text area has an onChange attribute linked to a handleCount function.

The function calculates the characters by measuring the length of the value. This is tracked in useState, with count.

Animating the progress bar

To update the width of the progress div a small calculation is done on progressStyle to see how many pixels should be added for every extra unit of the count.

The width of this element has been fixed to 400px and an upper limit of 50 characters. This means every 1 unit is equivalent to 8px.

Bonus animation

For extra benefit to the user experience, isComplete is a state to track when the 50 character limit has been reached. Then short-circuit evaluation controls the display of the 'complete' alert.

Code

import { useState, CSSProperties } from "react";
import styles from "./Solution.module.css";

const Solution = () => {
  const [count, setCount] = useState(0);
  const [isComplete, setIsComplete] = useState(false);

  const progressStyle: CSSProperties = {
    // 400px / 50 characters = 8 -> calc proportion of length
    width: `${8 * count}px`,
  };

  const handleCount: React.ChangeEventHandler<HTMLTextAreaElement> = (
    event
  ) => {
    const newCount = event.target.value.length;
    setCount(newCount);
    setIsComplete(newCount >= 50);
  };

  return (
    <div>
      <h3>Enter a string with at least 50 characters</h3>
      <div className={styles.progressContainer}>
        <div className={styles.progressBG}></div>
        <div style={progressStyle} className={styles.progress}></div>
      </div>
      <textarea
        className={styles.input}
        onChange={handleCount}
        cols={46}
        rows={3}
      />
      {isComplete && <h3 className={styles.completeMsg}>Complete!</h3>}
    </div>
  );
};

export default Solution;

Styling

.input {
  padding: 5px;
  margin: 50px 0 20px;
}

.progressContainer {
  position: relative;
}

.progress {
  position: absolute;
  margin-top: 10px;
  height: 20px;
  max-width: 400px;
  background-color: #7b8ade;
  border: 1px solid black;
}

.progressBG {
  position: absolute;
  margin-top: 10px;
  height: 20px;
  width: 400px;
  border: 1px solid black;
}

.completeMsg {
  color: red;
}

Links