0

I have a state that tracks the window width:

const [innerWidth, setInnerWidth] = useState(window.innerWidth)

In useEffect, I create a resize eventListener which sets the state to the new width:

  useEffect(() => {
    document.addEventListener('resize', () => {
      setInnerWidth(window.innerWidth)
    })
  }, [])

Lastly, I have a function test that logs the innerWidth every 5 seconds, with an interval started in useEffect

  function test() {
    console.log(innerWidth)
  }

  useEffect(() => {
     setInterval(test, 5000)
  }, [])

Unfortunately, despite any resize that happen, the test() function keeps on logging the original innerWidth value.

How can I tell react to reload the test function as well?

EDIT:

The perpetual log of the innerWidth was just a simplification of my actual use case. Actually, the timer is shifting an element on the x-axis, and I need to know when it exceeds the width to stop the execution and start again.

Creating and invalidating a loop every time the window changes, like in several answers you've given, temporarily stops the shifting of my element, as the loop gets invalidated. I would like to avoid this.

Riccardo Perego
  • 383
  • 1
  • 11
  • @S.Marx That will just create new intervals every time the width changes. Wouldn't it? – Riccardo Perego Sep 28 '22 at 11:27
  • It would, which means that `useEffect` would need to return a function which cancels the interval. So every change to that state would result in canceling the old interval and starting a new one. Which kind of makes the interval unnecessary... Why not just log the value directly in the `useEffect` any time it changes instead of re-logging the same value every 5 seconds? – David Sep 28 '22 at 11:31
  • Here `useRef` can help you to capture latest values. When the function is defined inside `setInterval` it's taking initial value because of closure(the time when this function is initialised). – Shubham Verma Sep 28 '22 at 11:45

4 Answers4

3

The useEffect created a closure around the original values, so that's all it ever logs. You'd need the effect to update any time the value changes, by adding it to the dependency array:

useEffect(() => {
  setInterval(test, 5000)
}, [innerWidth])

This would of course create a new interval on every state change. So the useEffect should return a function which cancels the interval:

useEffect(() => {
  const x = setInterval(test, 5000);
  return () => clearInterval(x);
}, [innerWidth])

That way there's only one interval running at any given time.

Though this begs the question... Why? If the goal is to log the value of innerWidth to observe its changes, then why re-log the same value every 5 seconds indefinitely? Skip the test function and the interval entirely and just log the value any time it changes:

useEffect(() => {
  console.log(innerWidth);
}, [innerWidth])
David
  • 208,112
  • 36
  • 198
  • 279
  • Posted an edit. – Riccardo Perego Sep 28 '22 at 11:40
  • @RiccardoPerego: That sounds like a somewhat different problem. The question asked was specifically about `useEffect`, it's closure, and how to update the values therein. Whereas *"temporarily stops the shifting of my element"* sounds like a kind of animation issue that needs to be smoother? There could be a variety of ways to address that, whether using smaller interval and calculating timing more deliberately or using a different approach altogether. That would likely warrant a separate question, to include a runnable [mcve] which demonstrates the issue. – David Sep 28 '22 at 12:03
0

Can you change the test function to an anonymous function?

const test = () => {
    console.log(innerWidth);
};

Change you useEffect:

useEffect(() => {
    window.addEventListener('resize', () => {
        setInnerWidth(window.innerWidth);
    });
}, [setInnerWidth]);
Tom
  • 74
  • 4
0

Edit: Your issue using the interval function is explained in this answer

This code works for me by logging the state variable using the effect hook:

const [innerWidth, setInnerWidth] = useState(window.innerWidth);

useEffect(() => {
  const updateWidth = () => {
    setInnerWidth(window.innerWidth);
  };
  window.addEventListener("resize", updateWidth);
}, []);

useEffect(() => {
  console.log(innerWidth);
}, [innerWidth]);

I added the eventlistener using the window object.

Sandbox

D Lav
  • 17
  • 7
0

The solution was wrapping the innerWidth into an object, so that it is passed by reference and it 'updates' in the test function.

const innerWidthWrapper = {width: window.innerWidth}

  useEffect(() => {
     innerWidthWrapper.width = window.innerWidth
    })
  }, [])
Riccardo Perego
  • 383
  • 1
  • 11