Problem Statement
Build a progress bar component that takes a percentage and animates the fill from 0 to that value when it mounts. Render several bars with different values.
Functional Requirements
- Progress prop:
ProgressBartakesprogress, a number from 0 to 100, and shows it as text like40%. - Fill: The green fill covers
progresspercent of the bar. - Animation: The fill starts at 0 and slides to the target value 100ms after mount, over 0.5s.
- Text color: The label is black below 5% so it stays readable, and white above that.
- Demo:
Apprenders bars for[1, 20, 40, 60, 80, 100].
Technical Constraints
- Move the fill with
transform: translateX()instead of changingwidth. - Expose the value with
role="progressbar",aria-valuenow,aria-valuemin="0"andaria-valuemax="100".
Code Flow
-
ProgressBarComponent:- The
ProgressBarcomponent takes aprogressprop (a number between 0 and 100) and renders a progress bar. - It uses
useStateto manage the animated progress value (animatedProgress). - The
useEffecthook is used to animate the progress bar when theprogressprop changes. - The
innerdiv's width is controlled by thetransform: translateX()property, which animates the progress bar horizontally. - The text color inside the progress bar changes dynamically based on the progress value.
- The
-
AppComponent:- The
Appcomponent renders multipleProgressBarcomponents. - It uses an array
barscontaining progress values ([1, 20, 40, 60, 80, 100]). - Each value in the
barsarray is passed as aprogressprop to aProgressBarcomponent.
- The
-
CSS Styling:
- The
.outerclass defines the container for the progress bar, with a border, rounded corners, and hidden overflow. - The
.innerclass defines the progress bar's appearance, including its background color, padding, text alignment, and transition animation.
- The
Key Concepts
1. useEffect Hook
- Purpose: The
useEffecthook is used to perform side effects in functional components. In this case, it handles the animation of the progress bar. - How it works:
- The
useEffecthook listens for changes in theprogressprop. - When the
progressprop changes, it triggers asetTimeoutto update theanimatedProgressstate after a 100ms delay. - The cleanup clears the pending timeout if
progresschanges again or the component unmounts first. - This delay creates a smooth animation effect for the progress bar.
- The
- Dependency Array:
[progress]ensures the effect runs only when theprogressprop changes.
useEffect(() => {
const timer = setTimeout(() => setAnimatedProgress(progress), 100);
return () => clearTimeout(timer);
}, [progress]);2. setTimeout
- Purpose:
setTimeoutis used to introduce a delay before updating theanimatedProgressstate. - Why it's used:
- Without
setTimeout, the progress bar would update instantly, making the animation less smooth. - The 100ms delay ensures the progress bar animates gradually, creating a visually appealing effect.
- Without
setTimeout(() => setAnimatedProgress(progress), 100);3. Accessibility
- Purpose: Accessibility features ensure the progress bar is usable and understandable for all users, including those using screen readers.
- Key Accessibility Attributes:
role="progressbar": Indicates that the element is a progress bar.aria-valuenow={progress}: Represents the current progress value.aria-valuemin="0": Indicates the minimum value of the progress bar (0%).aria-valuemax="100": Indicates the maximum value of the progress bar (100%).
<div
className="inner"
role="progressbar"
aria-valuenow={progress}
aria-valuemin="0"
aria-valuemax="100"
>
{progress}%
</div>Code Breakdown
ProgressBar Component
const ProgressBar = ({ progress }) => {
const [animatedProgress, setAnimatedProgress] = useState(0);
useEffect(() => {
const timer = setTimeout(() => setAnimatedProgress(progress), 100);
return () => clearTimeout(timer);
}, [progress]);
return (
<div className="outer">
<div
className="inner"
style={{
transform: `translateX(${animatedProgress - 100}%)`,
color: animatedProgress < 5 ? "black" : "white",
}}
role="progressbar"
aria-valuenow={progress}
aria-valuemin="0"
aria-valuemax="100"
>
{progress}%
</div>
</div>
);
};- State:
animatedProgressis used to animate the progress bar. - Effect:
useEffectupdatesanimatedProgresswith a 100ms delay. - Rendering:
- The
outerdiv is the container. - The
innerdiv represents the progress bar, with its width controlled bytransform: translateX(). - The text color changes dynamically based on the progress value.
- The
App Component
const App = () => {
const bars = [1, 20, 40, 60, 80, 100];
return (
<div className="App">
{bars.map((bar) => (
<ProgressBar progress={bar} key={bar} />
))}
</div>
);
};barsArray: Contains progress values to be displayed.- Rendering: Maps over the
barsarray and renders aProgressBarfor each value.
CSS Styling
.outer {
border: 1px solid black;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.inner {
background-color: green;
padding: 1px;
text-align: right;
transition: 0.5s ease-in;
}.outer:- Defines the container for the progress bar.
- Uses
overflow: hiddento clip the inner progress bar.
.inner:- Defines the progress bar's appearance.
- Uses
transitionto animate the width change smoothly.
Summary
-
Code Flow:
- The
Appcomponent renders multipleProgressBarcomponents with different progress values. - Each
ProgressBaranimates its width based on theprogressprop. - The
useEffecthook andsetTimeoutwork together to create a smooth animation. - Accessibility attributes make the progress bar usable for screen readers.
- The
-
Key Takeaways:
useEffecthandles side effects (e.g., animations) in response to prop changes.setTimeoutintroduces a delay for smoother animations.- Accessibility attributes (
role,aria-*) ensure the component is inclusive.