Double-Invoke of State Functions in React
Ryan McCoy
Posted on March 27, 2020
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?
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;
Again, I was able to see the logged state variable was getting duplicated, immediately, with no API call involved.
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')
);
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
const [stringVal] = useState("stringVal")
console.log(stringVal)
// stringVal
const [booleanVal] = useState(true)
console.log(booleanVal)
// true
Object and Array
const [obj] = useState({var1: 1,var2:2});
console.log(obj)
// Object { var1: 1, var2: 2 }
// Object { var1: 1, var2: 2 }
const [arr] = useState([1,2,3,4])
console.log(arr)
// Array(4) [1, 2, 3, 4]
// Array(4) [1, 2, 3, 4]
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
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
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]
(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
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
November 30, 2024