Use React hook to implement a self-increment counter [duplicate]












2
















This question already has an answer here:




  • State not updating when using React state hook within setInterval

    1 answer




The code is here: https://codesandbox.io/s/nw4jym4n0



export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);

useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);

return () => {
clearInterval(interval);
};
});

return <h1>{counter}</h1>;
};


The problem is each setCounter trigger re-rendering so the interval got reset and re-created. This might looks fine since the state(counter) keeps incrementing, however it could freeze when combining with other hooks.



What's the correct way to do this? In class component it's simple with a instance variable holding the interval.










share|improve this question















marked as duplicate by Yangshun Tay, Community Nov 21 '18 at 3:45


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.























    2
















    This question already has an answer here:




    • State not updating when using React state hook within setInterval

      1 answer




    The code is here: https://codesandbox.io/s/nw4jym4n0



    export default ({ name }: Props) => {
    const [counter, setCounter] = useState(0);

    useEffect(() => {
    const interval = setInterval(() => {
    setCounter(counter + 1);
    }, 1000);

    return () => {
    clearInterval(interval);
    };
    });

    return <h1>{counter}</h1>;
    };


    The problem is each setCounter trigger re-rendering so the interval got reset and re-created. This might looks fine since the state(counter) keeps incrementing, however it could freeze when combining with other hooks.



    What's the correct way to do this? In class component it's simple with a instance variable holding the interval.










    share|improve this question















    marked as duplicate by Yangshun Tay, Community Nov 21 '18 at 3:45


    This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.





















      2












      2








      2









      This question already has an answer here:




      • State not updating when using React state hook within setInterval

        1 answer




      The code is here: https://codesandbox.io/s/nw4jym4n0



      export default ({ name }: Props) => {
      const [counter, setCounter] = useState(0);

      useEffect(() => {
      const interval = setInterval(() => {
      setCounter(counter + 1);
      }, 1000);

      return () => {
      clearInterval(interval);
      };
      });

      return <h1>{counter}</h1>;
      };


      The problem is each setCounter trigger re-rendering so the interval got reset and re-created. This might looks fine since the state(counter) keeps incrementing, however it could freeze when combining with other hooks.



      What's the correct way to do this? In class component it's simple with a instance variable holding the interval.










      share|improve this question

















      This question already has an answer here:




      • State not updating when using React state hook within setInterval

        1 answer




      The code is here: https://codesandbox.io/s/nw4jym4n0



      export default ({ name }: Props) => {
      const [counter, setCounter] = useState(0);

      useEffect(() => {
      const interval = setInterval(() => {
      setCounter(counter + 1);
      }, 1000);

      return () => {
      clearInterval(interval);
      };
      });

      return <h1>{counter}</h1>;
      };


      The problem is each setCounter trigger re-rendering so the interval got reset and re-created. This might looks fine since the state(counter) keeps incrementing, however it could freeze when combining with other hooks.



      What's the correct way to do this? In class component it's simple with a instance variable holding the interval.





      This question already has an answer here:




      • State not updating when using React state hook within setInterval

        1 answer








      javascript reactjs react-hooks






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 20 '18 at 16:35









      Yangshun Tay

      9,34353867




      9,34353867










      asked Nov 20 '18 at 14:25









      PeiSongPeiSong

      554




      554




      marked as duplicate by Yangshun Tay, Community Nov 21 '18 at 3:45


      This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.









      marked as duplicate by Yangshun Tay, Community Nov 21 '18 at 3:45


      This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.


























          3 Answers
          3






          active

          oldest

          votes


















          6














          You want to give an empty array as second argument to useEffect so that the function is only run once after the initial render.



          Because of how closures work, this will make the counter variable always reference the initial value. You can use the function version of setCounter instead to always get the correct value.



          Example






          const { useState, useEffect } = React;

          function App() {
          const [counter, setCounter] = useState(0);

          useEffect(() => {
          const interval = setInterval(() => {
          setCounter(counter => counter + 1);
          }, 1000);

          return () => {
          clearInterval(interval);
          };
          }, );

          return <h1>{counter}</h1>;
          };

          ReactDOM.render(
          <App />,
          document.getElementById('root')
          );

          <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
          <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

          <div id="root"></div>








          share|improve this answer
























          • I am seeing Hooks can only be called inside the body of a function component. when I do something similar. Any ideas?

            – Paul Fitzgerald
            Jan 17 at 10:46











          • @PaulFitzgerald You most likely call a hook inside render of a class component: class App extends React.Component { render() { ... } }. Try a regular function instead: function App() { ... }

            – Tholle
            Jan 17 at 10:56













          • It is actually because I am using a custom hook, and then wrapping this in a setTimeout. Not sure the best way around this, outside of updating the customHook, which will lead it to be a bit bloated

            – Paul Fitzgerald
            Jan 17 at 11:03





















          0














          As another answer already shows, it's possible to make it useEffect callback run only once and work similarly to componentDidMount. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter won't be available inside setInterval callback.



          The alternative is to make useEffect callback run on each counter update. In this case setInterval should be replaced with setTimeout, and updates should be limited to counter updates:



          export default ({ name }: Props) => {
          const [counter, setCounter] = useState(0);

          useEffect(() => {
          const timeout = setTimeout(() => {
          setCounter(counter + 1);
          }, 1000);

          return () => {
          clearTimeout(timeout);
          };
          }, [counter]);

          return <h1>{counter}</h1>;
          };





          share|improve this answer
























          • This is not ideal as you will keep run setTimeout on every render and seems kind of unnecessary.

            – Yangshun Tay
            Nov 20 '18 at 16:33











          • I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.

            – estus
            Nov 20 '18 at 16:43





















          -1














          The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.



          However, you will need to change setCounter to use the previous value of counter. The reason is because the callback passed into setInterval's closure only accesses the counter variable in the first render, it doesn't have access to the new counter value in the subsequent render because the useEffect() is not invoked the second time; counter always has the value of 0 within the setInterval callback.



          Like the setState you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState callback to ensure that you have the latest state value before incrementing it.






          function Counter() {
          const [counter, setCounter] = React.useState(0);
          React.useEffect(() => {
          const timer = setInterval(() => {
          setCounter(prevCount => prevCount + 1); // <-- Change this line!
          }, 1000);
          return () => {
          clearInterval(timer);
          };
          }, ); // Pass in empty array to run effect only once!

          return (
          <div>Count: {counter}</div>
          );
          }

          ReactDOM.render(<Counter />, document.querySelector('#app'));

          <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
          <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

          <div id="app"></div>








          share|improve this answer
































            3 Answers
            3






            active

            oldest

            votes








            3 Answers
            3






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            6














            You want to give an empty array as second argument to useEffect so that the function is only run once after the initial render.



            Because of how closures work, this will make the counter variable always reference the initial value. You can use the function version of setCounter instead to always get the correct value.



            Example






            const { useState, useEffect } = React;

            function App() {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const interval = setInterval(() => {
            setCounter(counter => counter + 1);
            }, 1000);

            return () => {
            clearInterval(interval);
            };
            }, );

            return <h1>{counter}</h1>;
            };

            ReactDOM.render(
            <App />,
            document.getElementById('root')
            );

            <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
            <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

            <div id="root"></div>








            share|improve this answer
























            • I am seeing Hooks can only be called inside the body of a function component. when I do something similar. Any ideas?

              – Paul Fitzgerald
              Jan 17 at 10:46











            • @PaulFitzgerald You most likely call a hook inside render of a class component: class App extends React.Component { render() { ... } }. Try a regular function instead: function App() { ... }

              – Tholle
              Jan 17 at 10:56













            • It is actually because I am using a custom hook, and then wrapping this in a setTimeout. Not sure the best way around this, outside of updating the customHook, which will lead it to be a bit bloated

              – Paul Fitzgerald
              Jan 17 at 11:03


















            6














            You want to give an empty array as second argument to useEffect so that the function is only run once after the initial render.



            Because of how closures work, this will make the counter variable always reference the initial value. You can use the function version of setCounter instead to always get the correct value.



            Example






            const { useState, useEffect } = React;

            function App() {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const interval = setInterval(() => {
            setCounter(counter => counter + 1);
            }, 1000);

            return () => {
            clearInterval(interval);
            };
            }, );

            return <h1>{counter}</h1>;
            };

            ReactDOM.render(
            <App />,
            document.getElementById('root')
            );

            <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
            <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

            <div id="root"></div>








            share|improve this answer
























            • I am seeing Hooks can only be called inside the body of a function component. when I do something similar. Any ideas?

              – Paul Fitzgerald
              Jan 17 at 10:46











            • @PaulFitzgerald You most likely call a hook inside render of a class component: class App extends React.Component { render() { ... } }. Try a regular function instead: function App() { ... }

              – Tholle
              Jan 17 at 10:56













            • It is actually because I am using a custom hook, and then wrapping this in a setTimeout. Not sure the best way around this, outside of updating the customHook, which will lead it to be a bit bloated

              – Paul Fitzgerald
              Jan 17 at 11:03
















            6












            6








            6







            You want to give an empty array as second argument to useEffect so that the function is only run once after the initial render.



            Because of how closures work, this will make the counter variable always reference the initial value. You can use the function version of setCounter instead to always get the correct value.



            Example






            const { useState, useEffect } = React;

            function App() {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const interval = setInterval(() => {
            setCounter(counter => counter + 1);
            }, 1000);

            return () => {
            clearInterval(interval);
            };
            }, );

            return <h1>{counter}</h1>;
            };

            ReactDOM.render(
            <App />,
            document.getElementById('root')
            );

            <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
            <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

            <div id="root"></div>








            share|improve this answer













            You want to give an empty array as second argument to useEffect so that the function is only run once after the initial render.



            Because of how closures work, this will make the counter variable always reference the initial value. You can use the function version of setCounter instead to always get the correct value.



            Example






            const { useState, useEffect } = React;

            function App() {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const interval = setInterval(() => {
            setCounter(counter => counter + 1);
            }, 1000);

            return () => {
            clearInterval(interval);
            };
            }, );

            return <h1>{counter}</h1>;
            };

            ReactDOM.render(
            <App />,
            document.getElementById('root')
            );

            <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
            <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

            <div id="root"></div>








            const { useState, useEffect } = React;

            function App() {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const interval = setInterval(() => {
            setCounter(counter => counter + 1);
            }, 1000);

            return () => {
            clearInterval(interval);
            };
            }, );

            return <h1>{counter}</h1>;
            };

            ReactDOM.render(
            <App />,
            document.getElementById('root')
            );

            <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
            <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

            <div id="root"></div>





            const { useState, useEffect } = React;

            function App() {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const interval = setInterval(() => {
            setCounter(counter => counter + 1);
            }, 1000);

            return () => {
            clearInterval(interval);
            };
            }, );

            return <h1>{counter}</h1>;
            };

            ReactDOM.render(
            <App />,
            document.getElementById('root')
            );

            <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
            <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

            <div id="root"></div>






            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Nov 20 '18 at 14:35









            TholleTholle

            34.1k53760




            34.1k53760













            • I am seeing Hooks can only be called inside the body of a function component. when I do something similar. Any ideas?

              – Paul Fitzgerald
              Jan 17 at 10:46











            • @PaulFitzgerald You most likely call a hook inside render of a class component: class App extends React.Component { render() { ... } }. Try a regular function instead: function App() { ... }

              – Tholle
              Jan 17 at 10:56













            • It is actually because I am using a custom hook, and then wrapping this in a setTimeout. Not sure the best way around this, outside of updating the customHook, which will lead it to be a bit bloated

              – Paul Fitzgerald
              Jan 17 at 11:03





















            • I am seeing Hooks can only be called inside the body of a function component. when I do something similar. Any ideas?

              – Paul Fitzgerald
              Jan 17 at 10:46











            • @PaulFitzgerald You most likely call a hook inside render of a class component: class App extends React.Component { render() { ... } }. Try a regular function instead: function App() { ... }

              – Tholle
              Jan 17 at 10:56













            • It is actually because I am using a custom hook, and then wrapping this in a setTimeout. Not sure the best way around this, outside of updating the customHook, which will lead it to be a bit bloated

              – Paul Fitzgerald
              Jan 17 at 11:03



















            I am seeing Hooks can only be called inside the body of a function component. when I do something similar. Any ideas?

            – Paul Fitzgerald
            Jan 17 at 10:46





            I am seeing Hooks can only be called inside the body of a function component. when I do something similar. Any ideas?

            – Paul Fitzgerald
            Jan 17 at 10:46













            @PaulFitzgerald You most likely call a hook inside render of a class component: class App extends React.Component { render() { ... } }. Try a regular function instead: function App() { ... }

            – Tholle
            Jan 17 at 10:56







            @PaulFitzgerald You most likely call a hook inside render of a class component: class App extends React.Component { render() { ... } }. Try a regular function instead: function App() { ... }

            – Tholle
            Jan 17 at 10:56















            It is actually because I am using a custom hook, and then wrapping this in a setTimeout. Not sure the best way around this, outside of updating the customHook, which will lead it to be a bit bloated

            – Paul Fitzgerald
            Jan 17 at 11:03







            It is actually because I am using a custom hook, and then wrapping this in a setTimeout. Not sure the best way around this, outside of updating the customHook, which will lead it to be a bit bloated

            – Paul Fitzgerald
            Jan 17 at 11:03















            0














            As another answer already shows, it's possible to make it useEffect callback run only once and work similarly to componentDidMount. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter won't be available inside setInterval callback.



            The alternative is to make useEffect callback run on each counter update. In this case setInterval should be replaced with setTimeout, and updates should be limited to counter updates:



            export default ({ name }: Props) => {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const timeout = setTimeout(() => {
            setCounter(counter + 1);
            }, 1000);

            return () => {
            clearTimeout(timeout);
            };
            }, [counter]);

            return <h1>{counter}</h1>;
            };





            share|improve this answer
























            • This is not ideal as you will keep run setTimeout on every render and seems kind of unnecessary.

              – Yangshun Tay
              Nov 20 '18 at 16:33











            • I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.

              – estus
              Nov 20 '18 at 16:43


















            0














            As another answer already shows, it's possible to make it useEffect callback run only once and work similarly to componentDidMount. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter won't be available inside setInterval callback.



            The alternative is to make useEffect callback run on each counter update. In this case setInterval should be replaced with setTimeout, and updates should be limited to counter updates:



            export default ({ name }: Props) => {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const timeout = setTimeout(() => {
            setCounter(counter + 1);
            }, 1000);

            return () => {
            clearTimeout(timeout);
            };
            }, [counter]);

            return <h1>{counter}</h1>;
            };





            share|improve this answer
























            • This is not ideal as you will keep run setTimeout on every render and seems kind of unnecessary.

              – Yangshun Tay
              Nov 20 '18 at 16:33











            • I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.

              – estus
              Nov 20 '18 at 16:43
















            0












            0








            0







            As another answer already shows, it's possible to make it useEffect callback run only once and work similarly to componentDidMount. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter won't be available inside setInterval callback.



            The alternative is to make useEffect callback run on each counter update. In this case setInterval should be replaced with setTimeout, and updates should be limited to counter updates:



            export default ({ name }: Props) => {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const timeout = setTimeout(() => {
            setCounter(counter + 1);
            }, 1000);

            return () => {
            clearTimeout(timeout);
            };
            }, [counter]);

            return <h1>{counter}</h1>;
            };





            share|improve this answer













            As another answer already shows, it's possible to make it useEffect callback run only once and work similarly to componentDidMount. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter won't be available inside setInterval callback.



            The alternative is to make useEffect callback run on each counter update. In this case setInterval should be replaced with setTimeout, and updates should be limited to counter updates:



            export default ({ name }: Props) => {
            const [counter, setCounter] = useState(0);

            useEffect(() => {
            const timeout = setTimeout(() => {
            setCounter(counter + 1);
            }, 1000);

            return () => {
            clearTimeout(timeout);
            };
            }, [counter]);

            return <h1>{counter}</h1>;
            };






            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Nov 20 '18 at 16:08









            estusestus

            69.6k21102218




            69.6k21102218













            • This is not ideal as you will keep run setTimeout on every render and seems kind of unnecessary.

              – Yangshun Tay
              Nov 20 '18 at 16:33











            • I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.

              – estus
              Nov 20 '18 at 16:43





















            • This is not ideal as you will keep run setTimeout on every render and seems kind of unnecessary.

              – Yangshun Tay
              Nov 20 '18 at 16:33











            • I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.

              – estus
              Nov 20 '18 at 16:43



















            This is not ideal as you will keep run setTimeout on every render and seems kind of unnecessary.

            – Yangshun Tay
            Nov 20 '18 at 16:33





            This is not ideal as you will keep run setTimeout on every render and seems kind of unnecessary.

            – Yangshun Tay
            Nov 20 '18 at 16:33













            I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.

            – estus
            Nov 20 '18 at 16:43







            I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.

            – estus
            Nov 20 '18 at 16:43













            -1














            The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.



            However, you will need to change setCounter to use the previous value of counter. The reason is because the callback passed into setInterval's closure only accesses the counter variable in the first render, it doesn't have access to the new counter value in the subsequent render because the useEffect() is not invoked the second time; counter always has the value of 0 within the setInterval callback.



            Like the setState you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState callback to ensure that you have the latest state value before incrementing it.






            function Counter() {
            const [counter, setCounter] = React.useState(0);
            React.useEffect(() => {
            const timer = setInterval(() => {
            setCounter(prevCount => prevCount + 1); // <-- Change this line!
            }, 1000);
            return () => {
            clearInterval(timer);
            };
            }, ); // Pass in empty array to run effect only once!

            return (
            <div>Count: {counter}</div>
            );
            }

            ReactDOM.render(<Counter />, document.querySelector('#app'));

            <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
            <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

            <div id="app"></div>








            share|improve this answer






























              -1














              The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.



              However, you will need to change setCounter to use the previous value of counter. The reason is because the callback passed into setInterval's closure only accesses the counter variable in the first render, it doesn't have access to the new counter value in the subsequent render because the useEffect() is not invoked the second time; counter always has the value of 0 within the setInterval callback.



              Like the setState you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState callback to ensure that you have the latest state value before incrementing it.






              function Counter() {
              const [counter, setCounter] = React.useState(0);
              React.useEffect(() => {
              const timer = setInterval(() => {
              setCounter(prevCount => prevCount + 1); // <-- Change this line!
              }, 1000);
              return () => {
              clearInterval(timer);
              };
              }, ); // Pass in empty array to run effect only once!

              return (
              <div>Count: {counter}</div>
              );
              }

              ReactDOM.render(<Counter />, document.querySelector('#app'));

              <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
              <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

              <div id="app"></div>








              share|improve this answer




























                -1












                -1








                -1







                The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.



                However, you will need to change setCounter to use the previous value of counter. The reason is because the callback passed into setInterval's closure only accesses the counter variable in the first render, it doesn't have access to the new counter value in the subsequent render because the useEffect() is not invoked the second time; counter always has the value of 0 within the setInterval callback.



                Like the setState you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState callback to ensure that you have the latest state value before incrementing it.






                function Counter() {
                const [counter, setCounter] = React.useState(0);
                React.useEffect(() => {
                const timer = setInterval(() => {
                setCounter(prevCount => prevCount + 1); // <-- Change this line!
                }, 1000);
                return () => {
                clearInterval(timer);
                };
                }, ); // Pass in empty array to run effect only once!

                return (
                <div>Count: {counter}</div>
                );
                }

                ReactDOM.render(<Counter />, document.querySelector('#app'));

                <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
                <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

                <div id="app"></div>








                share|improve this answer















                The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.



                However, you will need to change setCounter to use the previous value of counter. The reason is because the callback passed into setInterval's closure only accesses the counter variable in the first render, it doesn't have access to the new counter value in the subsequent render because the useEffect() is not invoked the second time; counter always has the value of 0 within the setInterval callback.



                Like the setState you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState callback to ensure that you have the latest state value before incrementing it.






                function Counter() {
                const [counter, setCounter] = React.useState(0);
                React.useEffect(() => {
                const timer = setInterval(() => {
                setCounter(prevCount => prevCount + 1); // <-- Change this line!
                }, 1000);
                return () => {
                clearInterval(timer);
                };
                }, ); // Pass in empty array to run effect only once!

                return (
                <div>Count: {counter}</div>
                );
                }

                ReactDOM.render(<Counter />, document.querySelector('#app'));

                <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
                <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

                <div id="app"></div>








                function Counter() {
                const [counter, setCounter] = React.useState(0);
                React.useEffect(() => {
                const timer = setInterval(() => {
                setCounter(prevCount => prevCount + 1); // <-- Change this line!
                }, 1000);
                return () => {
                clearInterval(timer);
                };
                }, ); // Pass in empty array to run effect only once!

                return (
                <div>Count: {counter}</div>
                );
                }

                ReactDOM.render(<Counter />, document.querySelector('#app'));

                <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
                <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

                <div id="app"></div>





                function Counter() {
                const [counter, setCounter] = React.useState(0);
                React.useEffect(() => {
                const timer = setInterval(() => {
                setCounter(prevCount => prevCount + 1); // <-- Change this line!
                }, 1000);
                return () => {
                clearInterval(timer);
                };
                }, ); // Pass in empty array to run effect only once!

                return (
                <div>Count: {counter}</div>
                );
                }

                ReactDOM.render(<Counter />, document.querySelector('#app'));

                <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
                <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

                <div id="app"></div>






                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Nov 20 '18 at 16:42

























                answered Nov 20 '18 at 16:31









                Yangshun TayYangshun Tay

                9,34353867




                9,34353867















                    Popular posts from this blog

                    MongoDB - Not Authorized To Execute Command

                    How to fix TextFormField cause rebuild widget in Flutter

                    in spring boot 2.1 many test slices are not allowed anymore due to multiple @BootstrapWith