0

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.

MWB
  • 1,830
  • 1
  • 17
  • 38
  • Does this answer your question? [useState set method not reflecting change immediately](https://stackoverflow.com/questions/54069253/usestate-set-method-not-reflecting-change-immediately) – Giorgi Moniava Jan 28 '22 at 12:33

1 Answers1

0

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>
    );
}
  • Hi @Pierre Lejay: Thank you for your response. Really helpful. There is one remaining problem: the width that is set is incorrect. In my case it is set to 1488 (when I log it to the console), whereas it should be 1404! Any idea why? – MWB Jan 29 '22 at 09:28