Jeremy Levett
Jeremy Levett

Reputation: 947

React UseEffect Example Not Working Reason

Why does this not work as a normal one second counter?


function UseEffectBugCounter() {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1);
      console.log(count);
    }, 1000);

    return () => clearInterval(intervalId);
  }, []);

  return <div>The count is: {count}</div>;
}

Example: https://codesandbox.io/s/sparkling-rgb-6ebcp

-Is it because of stale closures?

or

-Is it because the count is a state variable and the component would be re-rendered after the state update so a new interval will be created creating some sort of loop?

or

-Is it something else?

I'm looking for a why this occurs in this answer if possible, there are a few different articles stating why it doesn't work (as per above). But none have been able to provide a good argument so far.

Upvotes: 1

Views: 2451

Answers (5)

akhtarvahid
akhtarvahid

Reputation: 9769

You need to pass count instead of blank array in useEffect

function UseEffectBugCounter() {
  const [count, setCount] = React.useState(0);
  React.useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1);
      console.log(count);
    }, 1000);
    return () => clearInterval(intervalId);
  },[count]);
  return <div>The count is: {count}</div>;
}

Upvotes: 0

Long Nguyen Duc
Long Nguyen Duc

Reputation: 1325

Because you didn't add count to useEffect's dependences, then inside the effect count always is 0.

You should use useReducer to resolve your problem:

function UseEffectBugCounter() {
  const [count, dispatchCount] = React.useReducer((state, { type }) => {
    switch(type) {
      case 'inc':
        return state + 1
      default:
        return state
    }
  }, 0);

  React.useEffect(() => {
    const intervalId = setInterval(() => {
      dispatchCount({ type: 'inc'})
    }, 1000);
    return () => clearInterval(intervalId);
  }, []);
  return <div>The count is: {count}</div>;
}

Upvotes: 0

Richard
Richard

Reputation: 121

If you removed second params for useEffect your application will be rendered always if u have some change in state, but this is bad practice. You need in second params choose for wich parametrs you need watch ...

Example with choosen params:

React.useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1);
      console.log(count);
    }, 1000);

    return () => clearInterval(intervalId);
  }[count]);

Without

React.useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1);
      console.log(count);
    }, 1000);

    return () => clearInterval(intervalId);
  });

Upvotes: 0

Yash Joshi
Yash Joshi

Reputation: 2774

You may need to add the dependency for count in useEffect. Currently useEffect is only called on the mount and is not called after that (i.e when the count value changes).

So it always says 0 because useEffect is executed only once ( on mount ) and that time the count value is set to 0. And thus it every time it logs 0 on setInterval due to closure.

I have updated the code sandbox to find the reason and meaningful logs. Here is the sandbox link: https://codesandbox.io/s/admiring-thompson-uz2xe

How to find closure value: You can check the logs and traverse through prototype object to find [[Scopes]] and you will get the values as seen in the below screenshot: enter image description here

This would work:

React.useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1);
      console.log(count);
    }, 1000);

    return () => clearInterval(intervalId);
  }, [count]);

You can check this doc: https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect

You can read this as well: You can read this as well: https://overreacted.io/making-setinterval-declarative-with-react-hooks/

Hope this helps!

Upvotes: 0

Andres
Andres

Reputation: 1012

You can use callback for set state to use latest counter value:

setCount(count => (count + 1));

Upvotes: 2

Related Questions