skip to Main Content

I don’t understand why currentExpVal is always has a value of 0. It means the interval doesn’t stop. Could anybody explain it?

I want to have a smoothly updating counter during interval until it’s not 10 (for example).

import React, { useEffect, useRef, useState } from "react";
import styles from "./Experience.module.scss";

type Props = {};

export const Experience: React.FC<Props> = (props: Props) => {
  const expValueRef = useRef(10);
  const [currentExpVal, setCurrentExpVal] = useState(0);

  useEffect(() => {
    const intervalRef = setInterval(() => {
      // console.info(currentExpVal, expValueRef.current)
      if (currentExpVal < expValueRef.current) {
        console.info(currentExpVal, expValueRef.current);
        setCurrentExpVal((prev) => {
          return prev + 1;
        });
      } else {
        console.info("clear");
        clearInterval(intervalRef);
      }
    }, 1500);
  }, []);

  // currentExpVal is not updated within if (currentExpVal < expValueRef.current)

  return (
    <div className={styles["exp-wrapper"]}>
      {currentExpVal}
    </div>
  );
};

2

Answers


  1. Your effect runs only once due to the empty array [] in your effect dependency, so inside your effect currentExpVal references the very first value of the state variable, that’s why it’s always 0.

    I would split the work in 2 effects, one to init the interval and another to check if the limit is reached.

    const expValueRef = useRef(10);
    const [currentExpVal, setCurrentExpVal] = useState(0);
    const interValId = useRef();
    
    // init and clean interval
    useEffect(() => {
      interValId.current = setInterval(() => {
        setCurrentExpVal(prevValue => prevValue + 1);
      });
    
      // clean interval when component is unmounted
      return () => clearInterval(intervalId.current);
    }, [intervalId]);
    
    // check if the limit is reached
    useEffect(() => {
      if (currentExpVal >= expValueRef.current) {
        clearInterval(intervalId.current);
      }
    }, [currentExpVal]);
    

    I suggest you to use eslint-plugin-react-hooks to prevent this kind of error. It would have warned you that you forgot to pass currentExpVal in the effect dependencies.

    Login or Signup to reply.
  2. The problem you have is a closure one, which occurs especially when used with setInterval. When the useEffect hook is run for the first time, the current value of currentExpVal is captured and is 0 at that point. Inside the setInterval, this value never gets updated; hence you always see the 0 value.

    You can use the setCurrentExpVal callback to access the latest state. You already use that but you’re comparing the wrong values. Instead of comparing currentExpVal and expValueRef.current, you can compare the current state within the updater function.

    The solution above that @Olivier gave is also valid. Here is the other one:

    useEffect(() => {
        const intervalRef = setInterval(() => {
            setCurrentExpVal((prev) => {
                console.info(prev, expValueRef.current);
    
                // If the previous value is less than the target, increment and return.
                if (prev < expValueRef.current) {
                    return prev + 1;
                } else {
                    console.info('clear');
                    clearInterval(intervalRef);  // If not, clear the interval.
                    return prev;  // Return the current value without modification.
                }
            });
        }, 1500);
        return () => clearInterval(intervalRef);
    }, []);
    
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search