最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - SetInterval is not showing updated state - Stack Overflow

programmeradmin2浏览0评论

I have set the state to true before calling the setInterval function. But even though the useEffect hook is being triggered with the new value of the state, it's not being reflected in the setInterval function.

Code sandbox here: /

let interval;
const Component = () => {
  React.useEffect(() => {
    console.log('State updated to', state);
  });
  const [state, setState] = React.useState(false);
  const on = () => {
    setState(true);
    interval = setInterval(() => {
      console.log(state);
    }, 1000);
    }
  const off = () => {
    setState(false);
    clearInterval(interval);
  }
  const toggle = () => state ? off() : on()

  return (<div>
    <button onClick={toggle}>Toggle State</button>
   </div>);
}

ReactDOM.render(
  <Component />,
  document.getElementById('container')
);

Shouldn't it be using the newer value of state once it's updated?

I have set the state to true before calling the setInterval function. But even though the useEffect hook is being triggered with the new value of the state, it's not being reflected in the setInterval function.

Code sandbox here: https://jsfiddle.net/6e05tc2L/3/

let interval;
const Component = () => {
  React.useEffect(() => {
    console.log('State updated to', state);
  });
  const [state, setState] = React.useState(false);
  const on = () => {
    setState(true);
    interval = setInterval(() => {
      console.log(state);
    }, 1000);
    }
  const off = () => {
    setState(false);
    clearInterval(interval);
  }
  const toggle = () => state ? off() : on()

  return (<div>
    <button onClick={toggle}>Toggle State</button>
   </div>);
}

ReactDOM.render(
  <Component />,
  document.getElementById('container')
);

Shouldn't it be using the newer value of state once it's updated?

Share Improve this question edited Aug 13, 2019 at 11:25 Ulysse BN 11.4k7 gold badges61 silver badges93 bronze badges asked Aug 13, 2019 at 11:19 acesmndracesmndr 8,4953 gold badges24 silver badges28 bronze badges 4
  • 2 The function passed to setInterval is created just 1 time when on is called, and it closes over the value of state at the time it is created. Future renders call React.useState again and "see" a new state, but that function created when on was called is essentially stuck in the past: it closed over state when it was created and will never get a new value of state. – Ross Allen Commented Aug 13, 2019 at 17:16
  • 2 So if you check out Abramov's useInterval, it calls useEffect on each render and updates a ref's .current with a new version of the callback that closed over the value of state for that render call. The function passed to the native setInterval never changes, but all that function does is call another function whose reference actually is updating on every render. – Ross Allen Commented Aug 13, 2019 at 17:28
  • @RossAllen So the callback function changes due to the use of useRef but the tick function remains the same. That's awesome but feels quite complex. Thanks! :) – acesmndr Commented Aug 14, 2019 at 4:08
  • 1 Yup, exactly. That tick never changes, but in your own call of useInterval you are passing in a new function on every render. That function gets set to savedCallback.current and called by the permanent tick. – Ross Allen Commented Aug 14, 2019 at 12:09
Add a comment  | 

4 Answers 4

Reset to default 16

The values inside the function which you pass to useEffect are refreshed on every render, because useEffect uses a new definition of the function you pass to it.

But the function passed to setInterval is defined once and it closes over the old stale value of state. Which has not yet updated.

Closures are tricky with hooks, but the thing to realize is that useEffect creates a new function for each render and hence each time the function closes over a fresh state value.

The trick then is to call your setInterval related code inside a useEffect itself, which itself depends on the changing value of state

React.useEffect(() => {
  if(state) {

    interval = setInterval(() => {
      console.log(state);
    }, 1000);
  } else {
    clearInterval(interval);
  }

}, [state]);

Or, better, use a useInterval hook which takes care of these details for you.

setInterval always has access to the value of your component's first render because the function passed to setInterval closes around that value and is never redeclared. You can use a custom hook to fix this:

function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay]);
}

That implementation and a thorough explanation of the mismatch between React Hooks and setInterval is from Making setInterval Declarative with React Hooks by Dan Abramov, one of the React contributors.

I'm not a ReactJS expert, but I guess the state you are logging is not refreshed since it is declared once and never refreshed. If React.useState(false) is the method that is giving you your state, you should use it in your interval function.

Here is an example of what I'm trying to explain:

const object = { value: false }


const notRefreshed = object.value // here we are copying the value

const interval = setInterval(() => {
  const refreshed = object.value // here we are using the reference to copy the latest value
  console.log("refreshed", refreshed)
  console.log("notRefreshed", notRefreshed)
}, 500)


setTimeout(() => object.value = true, 1600)

setTimeout(() => clearInterval(interval), 2600)

If you want to reaload your component whenever your state change you should create your useEffect like this.

React.useEffect(() => {
    console.log('State updated to', state);
}, [state]);

The way you created is the same as componentDidMount() with an array as the second parameter it's like componentDidUpdate() with it's dependencies. So, your component will re-render whenever your state change.

To solve the infinity call of setTimeout you can do this where you create the function

React.useCallback(() => {
    setInterval(() => {
        console.log(state);
    }, 1000);
})

with this React will know that you want to create this function just once.

发布评论

评论列表(0)

  1. 暂无评论