Double-Invoke of State Functions in React

mccoyrjm

Ryan McCoy

Posted on March 27, 2020

Double-Invoke of State Functions in React

Overview

This article covers the seemingly unexpected behavior of seeing multiple executions of a component's state functions (setState() for class-based components and useState() for functional components that use React hooks).

Intro - Seeing Double Logs

The project started out like any other. Spin up a create-react-app project, find an API to play with (Open Weather API in this case), create call to said API when the component loads, add a few console logs to verify the data is coming in how I want and...

Hold up... am I seeing double?

Alt Text

At this point, I had put a console.log after initializing my state variable and after updating it with the weather information from the API. However, I was unexpectedly seeing 2 logs for each of those steps!

Simplifying and Further Debugging

Googling resulted in articles about how to architect your React app, Hook tutorials, and the like. Although interesting, they were not the answer. So instead, I set up another create-react-app to debug this behavior further and replaced App.js with the following:

import React, {useState} from 'react';

function App() {
    const [obj] = useState({ var1: 1, var2: 2 });

    console.log(obj);

    return <h1>See dev console.</h1>;
  }

export default App;
Enter fullscreen mode Exit fullscreen mode

Again, I was able to see the logged state variable was getting duplicated, immediately, with no API call involved.

Alt Text

Was I already building a poorly architected React application? Admittedly, I'm still getting my feet wet with Hooks, but have I already messed up something in only 3 lines of code??? Cue imposter syndrome.

After making some queries to the #react-help channel of the Scrimba discord server, an answer was found:

THIS BEHAVIOR IS PER DESIGN.

Yes indeed! The reason for the double-firing of the state function was due to React's Strict Mode being enabled. Looking back at our project, you can see the index.js file controls how this is enabled.

ReactDOM.render(
  <React.StrictMode> // <--------------- BAM
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

The React Docs actually covers this behavior in the Detecting Unexpected Side Effects section. It's also important to note that this only happens in Development mode.

"This only applies to development mode. Lifecycles will not be double-invoked in production mode."

On a Github issue, Dan Abramov himself had this to say:

"It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and alter restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application." - Dan Abramov (source)

Further Observations

It should be mentioned that this double-invoke doesn't happen all the time depending on what types of state variables you have set up and how many. For example, String, Number, and Boolean would not cause a double-invoke by themselves. However, Object and Array types would.

All code snippets below are the content of the App() function which always returns <h1>See dev console.</h1>.

Number, String, and Boolean

const [numericVal] = useState(1)
console.log(numericVal)

// 1
Enter fullscreen mode Exit fullscreen mode
const [stringVal] = useState("stringVal")
console.log(stringVal)

// stringVal
Enter fullscreen mode Exit fullscreen mode
const [booleanVal] = useState(true)
console.log(booleanVal)

// true
Enter fullscreen mode Exit fullscreen mode

Object and Array

const [obj] = useState({var1: 1,var2:2});
console.log(obj)

// Object { var1: 1, var2: 2 }
// Object { var1: 1, var2: 2 }
Enter fullscreen mode Exit fullscreen mode
const [arr] = useState([1,2,3,4])
console.log(arr)

// Array(4) [1, 2, 3, 4]
// Array(4) [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

Now most of the time, you won't just have a single useState() variable in a component. Trying with multiple actually results in the double-invoke behavior once again no matter the type of variable you declare. Here's a few examples:

const [stringVal] = useState("stringVal")
const [booleanVal] = useState(true)

console.log(stringVal)
console.log(booleanVal)

// stringVal
// true
// stringVal
// true
Enter fullscreen mode Exit fullscreen mode
const [numericVal] = useState(1)
const [stringVal] = useState("stringVal")
const [booleanVal] = useState(true)

console.log(numericVal)
console.log(stringVal)
console.log(booleanVal)

// 1
// stringVal
// true
// 1
// stringVal
// true
Enter fullscreen mode Exit fullscreen mode
const [numericVal] = useState(1)
const [arr] = useState([1,2,3,4])

console.log(numericVal)
console.log(arr)

// 1
// Array(4) [1, 2, 3, 4]
// 1
// Array(4) [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

(If anyone has any insight as to what exactly triggers a double-invoke, please comment and I'll be sure to update this article!)

Conclusion

If you're ever concerned that you're logging more than you anticipated or double-invoking certain functions in your local development environment, be sure to check if you're running in React's Strict Mode!

Resources

💖 💪 🙅 🚩
mccoyrjm
Ryan McCoy

Posted on March 27, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related