React Components rendered twice — any way to fix this?
Many developer have implemented a similar functional component and have seen this behavior. Some have even opened a bug report in the official React
repository.
The reason why this happens is an intentional feature of the
React.StrictMode
. It only happens in development mode and should help to find accidental side effects in the render phase.
Let’s find out if there is a way to avoid this problem by trying different implementations.
A) Functional Component with useState
function App() {
const [click, setClick] = React.useState(0);console.log('I render 😡', click);
return (
<div>
<button onClick={() => setClick(click => click + 1)}>
Clicks: {click}
</button>
</div>
)
}
if you used create-react-app
and run your app with yarn start
you will see the following output in the browser console:
I render 😡 0
I render 😡 0
if you click the button once, another two lines will be appended to the log:
I render 😡 1
I render 😡 1
The reason for this is the React.StrictMode
wrapper in the index.js
file:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Removing the wrapper
<React.StrictMode>
will solve the problem in development mode. The same is true, if you build the app for production withyarn build && npx serve -p 3001 build
I render 😡 0
I render 😡 1
B) Functional Component with useReduce
I replaced useState
with useReduce
and the result was the same:
function App() {
const [clicks, dispatch] = React.useReducer((state, action) => {
switch (action.type) {
case 'click':
return state + 1;
default:
throw new Error()
}
}, 0)console.log('I render 😡', clicks);
return (
<div >
<button onClick={()=>dispatch({type:'click'})}>
Clicks: {clicks}
</button>
</div>
)
}
Browser console:
I render 😡 0
I render 😡 0I render 😡 1
I render 😡 1
C) Class Component
Switching to Class Comonents
is no difference - you will see the effect even without any state management:
class App extends React.Component {
render() {
console.log('I render 😡');
return null;
}
}
What is happening behind the scenes?
The main reason is React.StrictMode
which was introduced in version 16.3.0. Back then it only could be used for class components. With the release of 16.8.0 it is also applied for hooks.
StrictMode is a tool for highlighting potential problems in an application. Like Fragment, StrictMode does not render any visible UI. It activates additional checks and warnings for its descendants. ‒ official React Documentation
React.StrictMode
currently helps you with:
- Identifying components with unsafe lifecycles
- Warning about legacy string ref API usage
- Warning about deprecated findDOMNode usage
- Detecting unexpected side effects
- Detecting legacy context API
In most cases it will throw an error message into the browser console log. It fails to automatically detect render side effects as they often can be non-deterministic behavior.
To detect side effects the following functions are invoked twice:
- Class component constructor, render, and shouldComponentUpdate methods
- Class component static getDerivedStateFromProps method
- Function component bodies
- State updater functions
- Functions passed to useState, useMemo, or useReducer (any Hook)
Why should I use React.StrictMode?
React will soon provide a new Concurrent Mode which will break render work into multiple parts. Pausing and resuming the work between this parts should avoid the blocking of the browsers main thread.
This means that the render phase could be invoked multiple times and should be a Pure Function without any side effects!
If you did not used React.StrictMode
because you started in the early React days or have been annoyed by the double rendering of your components I hope that I was able to convince you to use it now to aviod any surprise when switching to concurrent mode in the near future.
Originally published at https://www.heissenberger.at.