joaoricardotg
joaoricardotg

Reputation: 137

Weird behavior of useEffect in React.js

I was experiencing some weird behavior in my React app, where setEffect with a state (say state1) as a second argument was changing another variable (not a state) without setState1 being ever called. So I made the following code to see if the same would happen, and it did:

import React, { useState, useEffect } from 'react';

function App() {

  let [state1, setState1] = useState('');
  let count = 0;

  useEffect(() => {
    count = count + 1000;
    console.log('USEEFFECT_count: ' + count);
  }, [state1]);

  setInterval(() => {
    console.log('SETINTERVAL_count: ' + count);
  }, 1000);

  return (
    <div className="App">
    </div>
  );
}

export default App;

Basically, the variable count should receive the value of 1000 only when setState1 is called (which never happens) to change the value of state1. But the return of the console is

SETINTERVAL_count: 1000
SETINTERVAL_count: 0
SETINTERVAL_count: 1000
SETINTERVAL_count: 0
SETINTERVAL_count: 1000
SETINTERVAL_count: 0
SETINTERVAL_count: 1000
SETINTERVAL_count: 0
SETINTERVAL_count: 1000
SETINTERVAL_count: 0
 

And the console.log inside the useEffect is never called, as expected. But if that's the case, then:

If I add a state2 and another useEffect that sums 5000 to count, it adds to the 1000 from the first useEffect :

import React, { useState, useEffect } from 'react';

function App() {

  let [state1, setState1] = useState('');
  let [state2, setState2] = useState('');
  let count = 0;

  useEffect(() => {
    count = count + 1000;
    console.log('USEEFFECT1_count: ' + count);
  }, [state1]);

  useEffect(() => {
    count = count + 5000;
    console.log('USEEFFECT2_count: ' + count);
  }, [state2]);

  setInterval(() => {
    console.log('SETINTERVAL_count: ' + count);
  }, 1000);

  return (
    <div className="App">
    </div>
  );
}

export default App;

The result of the console.log:

SETINTERVAL_count: 6000
SETINTERVAL_count: 0
SETINTERVAL_count: 6000
SETINTERVAL_count: 0
SETINTERVAL_count: 6000
SETINTERVAL_count: 0
SETINTERVAL_count: 6000
SETINTERVAL_count: 0

Upvotes: 2

Views: 395

Answers (2)

Yousaf
Yousaf

Reputation: 29282

Their's nothing weird about the output you are getting with your code.

In your code, useEffect hook will execute only once, i.e. after the initial render. Then it will only execute if state1 is changed but that never happens in your code, so useEffect executes only once and sets count to 1000.

And the console.log inside the useEffect is never called, as expected

No, it will be called. useEffect will execute after the initial render, that is why count's value becomes 1000. Having state1 in the dependency array of useEffect hook doesn't means that it will only execute if state1 is changed. useEffect always executes after the initial render, irrespective of whether you pass the dependency array or not or if you pass a non-empty dependency array.

Now, the question of why setInterval is logging two values of count?

That is because there are two intervals set in your code. Once during the intial render when count is 0 and second because of the Strict Mode which renders your component twice.

As setInterval() is called at the top level from within your component, a new interval will be set whenever the component re-renders. As your component renders two times, two intervals are set. Callback function of each interval has a closure over the count when callback function is defined.

In the index.js file, you will see the App component wrapped in React.StrictMode as shown below:

<React.StrictMode>
  <App/>
</React.StrictMode>

If you remove the React.StrictMode component, you will see that only one value of the count will be logged to the console

Output without Strict Mode:

USEEFFECT_count: 1000 
SETINTERVAL_count: 1000 
SETINTERVAL_count: 1000 
SETINTERVAL_count: 1000 
SETINTERVAL_count: 1000 

Upvotes: 1

Nicholas Tower
Nicholas Tower

Reputation: 84912

Your call to setInterval is in the body of the component, so it will happen every time the component renders. Your component is rendering twice, so it's setting up two intervals. Each interval is referring to a different count variable. One of the counts is at 1000, because the useEffect corresponding to that render has run. The other is at 0, because it's useEffect has not run (since state1 didn't change).

The reason there are two renders is likely because you're using react strict mode, which deliberately does double renders. This is done to help highlight bugs where your code is dependent on the number of times the component renders. So it seems to be doing its job, as it highlighted your dependency on the number of renders.

Upvotes: 1

Related Questions