1

I'm a beginner in React and stuck with some problem. As below we can able to see that useEffect can be called for only once for the Counter Functional component below(as in the dependency array I haven't specify the count state). But why the interval callbacks belongs to first render is not capable of sending the update instruction everytime the interval fires?

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function Counter() {
  const [count, setCount] = useState(0);

   useEffect(() => {
     const id = setInterval(() => {
       setCount(count + 1);
     }, 1000);

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

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

const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);



Pedro Filipe
  • 995
  • 1
  • 8
  • 17
Abhijeet
  • 588
  • 1
  • 4
  • 18

1 Answers1

2

You're passing an empty array to useEffect, which means that, since none of the array values change during re-renders, the useEffect call is only called once, on the initial render.

Put the count into the array, so that the useEffect callback runs only when count changes - or, remove the second parameter altogether, so that the callback runs on every render.

Also note that since each run of the setInterval callback will result in a re-render, it would make more sense to use setTimeout instead (it doesn't affect the functionality, but it makes the code more intuitively understandable):

function Counter() {
  const [count, setCount] = React.useState(0);
  React.useEffect(() => {
    const id = setTimeout(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearTimeout(id);
  }, [count]);

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

const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • I liked your answer and it is correct, however I am just curious, why would setInterval doesn't work then? Let's say on ComponentDidMount you did something similar as in the question (setInterval), then shouldn't that interval be called once per second even though you are not entering to that block of code anymore? – Ali Beyit May 18 '20 at 14:46
  • `setInterval` *does* work, it's just weird, because you only want its callback to run once *at most*, so `setTimeout` is more appropriate. If the `useEffect` callback returns a value, that callback is run when the component needs to be cleaned up (like for a re-render), so the interval gets cleared. (Even if the interval wasn't cleared, the code would still work, it'd just be inelegant, because of spawning multiple intervals to change disconnected DOM elements) – CertainPerformance May 18 '20 at 14:48
  • I'd also add to this that, if you console.log the `count` value in the `setInterval()` you'll notice that it is always `0`. I'm not an expert but I'd say that when the `setInterval()` get's registered, it will take the value of `count` as is in that moment. What you were expecting was that `count` would change for each interval, it is not the case. This answer is correct and clean, I just wanted to provide a bit more insight. – Pedro Filipe May 18 '20 at 14:52
  • @CertainPerformance, I have a question as I have mentioned above that I haven't mention count state in the dependency array and thus useEffect will be called for once.but The interval from the 1st render still there which will call setCount(count +1) for every time interval of 1sec. Then why interval won't send the update instructions to React. ex:- As setCount(0+1) for the first render and now setCount(1+1) call for next sec and setCount(2+1) for next sec. If it doesn't actually happen then what would happen to the interval calls of 1st Render. – Abhijeet May 18 '20 at 15:16
  • 1
    @Abhijeet The function returned from the `useEffect` callback runs when the component gets cleaned up, in preparation for a re-render, like I commented above. So once the interval callback runs, `setCount` is called, which queues a render. When the component gets unmounted, the callback that clears the interval is called. Then a new Counter component is rendered with the updated `count` of `1`. So it's not really an interval at all, since a given callback passed to it only runs once. – CertainPerformance May 18 '20 at 17:44
  • @Abhijeet **IF** you left out that `clearInterval` code, then like I said in the comment above: *the code would still work, it'd just be inelegant, because of spawning multiple intervals to change disconnected DOM elements*. The interval would be changing elements from *finished* `Counter` instances which are no longer in the DOM, and not visible to the user. – CertainPerformance May 18 '20 at 17:45
  • @CertainPerformance Please help on this -- Suppose First Render is rendered by React and then suppose second render is same as the first one then according to Tree Reconciliation only the changes should be moved and rendered to the browser. Then if second render is same as the first render Then 1st render will always be in the browser which will not be unmounted in future or 2nd Render will be rendered and 1st Render will unmount? -- As you mentioned above, 1st render will be unmounted and interval will be cleared after unmount. – Abhijeet May 19 '20 at 09:35