Search code examples
javascriptcssreactjs

How to properly animate loading bar in React?


I'm building a loading bar in React, but something is not right. I want it to fill width 100% once the timer is reached 3 seconds, but for some reason it does not work as expected.

const { useState, useEffect } = React;

const Loading = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [progress, setProgress] = useState(0);

  useEffect(() => {
    const duration = 3000;
    const increment = 100 / duration;

    const timer = setInterval(() => {
      setProgress((prevProgress) => {
        const newProgress = prevProgress + increment;
        if (newProgress >= 100) {
          clearInterval(timer);
          setIsLoading(false);
          return 100;
        }
        return newProgress;
      });
    }, 10);

    return () => clearInterval(timer);
  }, []);

  return (
    <div className="canvas">
      {isLoading && (
        <div
          className="loadng_bar"
          style={{ width: `${progress}%` }}
        ></div>
      )}
    </div>
  );
};

// Render it
ReactDOM.createRoot(
  document.getElementById("root")
).render(
  <Loading />
);
.loadng_bar {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 5px;
  background-color: #c4483f;
  transition: width 3.05s linear;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

It should increase the progress up to full width during 3 seconds. How can I achieve it?


Solution

  • Option 1: Animate with CSS only You can remove the setInterval and handle the animation with pure CSS by setting the width to 100% initially and letting CSS handle the timing:

    const { useState, useEffect } = React;
    
    const Loading = () => {
      const [isLoading, setIsLoading] = useState(true);
    
      useEffect(() => {
        const timer = setTimeout(() => {
          setIsLoading(false);
        }, 3000); // 3 seconds timer
    
        return () => clearTimeout(timer);
      }, []);
    
      return (
        <div className="canvas">
          {isLoading && (
            <div className="loading_bar"></div>
          )}
        </div>
      );
    };
    
    // Render it
    ReactDOM.createRoot(document.getElementById("root")).render(<Loading />);
    .loading_bar {
      position: absolute;
      top: 0;
      left: 0;
      width: 0;
      height: 5px;
      background-color: #c4483f;
      animation: load 3s linear forwards;
    }
    
    @keyframes load {
      to {
        width: 100%;
      }
    }
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

    Option 2: Animate with JavaScript If you prefer to control the animation manually with JavaScript using setInterval, you don't need the CSS transition property. Here's the corrected approach:

    const { useState, useEffect } = React;
    
    const Loading = () => {
      const [isLoading, setIsLoading] = useState(true);
    
      useEffect(() => {
        const duration = 3000; // 3 seconds
        const stepTime = 10; // Interval time in ms
        const increment = 100 / (duration / stepTime); // Progress increment per interval
    
        const timer = setInterval(() => {
          setProgress((prevProgress) => {
            const newProgress = prevProgress + increment;
            if (newProgress >= 100) {
              clearInterval(timer);
              setIsLoading(false);
              return 100;
            }
            return newProgress;
          });
        }, stepTime);
    
        return () => clearInterval(timer);
      }, []);
    
    
      return (
        <div className="canvas">
          {isLoading && (
            <div
              className="loading_bar"
              style={{ width: `${progress}%` }}
            ></div>
          )}
        </div>
      );
    };
    .loading_bar {
      position: absolute;
      top: 0;
      left: 0;
      height: 5px;
      background-color: #c4483f;
    }
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>