'Why is my setState in a setTimeout in a useEffect not updating my state?

I have a react component, where I want to store the width of a div in a state. I am using the following code (extracted from my project):

const Test = (): JSX.Element => {
    const ref = useRef<HTMLDivElement>(null);

    const [width, setWidth] = useState(123);

    useEffect(() => {
        setTimeout(() => {
            const width = ref.current?.clientWidth || 321;
            setWidth(() => width);
        }, 1000);
        setTimeout(() => {
            console.error(width);
        }, 2000);
    }, []);

    ...

    return (
        <div ref={ref}>Test</div>
    );
}

When running it, in the console I see the value 123 printed (original value of width) and not the actual width or 321.

I am sure I am doing something silly, but I have been staring at this code for already quite some time. So, I hope someone can help!

(the reason I am using a setTimeout is that I have read somewhere, that sometimes you don't get the right value if you get ref.current.clientWidth right away.



Solution 1:[1]

There are some issues with your approach.

It is generally best to avoid using setTimeout to wait on dynamic changes like component rendering. At best, it makes your application extra slow by adding unnecessary waiting times; and it's possible that whatever you are waiting on will still not be ready at the end of the timeOut, which is a source of bugs.

In this case, you don't actually want to wait 1s; you want to wait until that div is rendered, and your code should reflect that instead.

This link details the solution to your problem as recommended by React, including why useCallback is actually a better choice than useRef in this case. Applied to your example, it should look like this:

const Test = (): JSX.Element => {
  const [width, setWidth] = useState(0);

  const ref = useCallback(node => {
    if (node !== null) {
      setWidth(node.getBoundingClientRect().width);
    }
  }, []);

    ...

    return (
        <div ref={ref}>Test</div>
    );
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pierre Lejay