I recently saw a question in StackOverflow which had the below code snippet:
export default function Mycomponent() {
const [number,setNumber] =useState(0);
const [string,setstring] =useState('');
function func(event){
setstring(event.target.value);
setTimeout(()=>{
setNumber(number+1);
},3000);
}
console.log(number,string);
return (
<>
<input type='text'onChange={(e)=>{func(e)}} value={string}/>
</>
)
}
The user was confused with the log output:
If like me, your initial thought was that the output should have 1 'abcd'
only once, then this post might be useful for you.
Multiple re-renders
Suppose the user starts entering a
,b
,c
,d
sequentially into the input. It goes without saying that every character typed will set a timeout. So setTimeout
will be called 4 times. The callback in the timeout will be scheduled to run after atleast 3seconds. So there will be multiple setNumber
calls, which will further lead to re-renders.
setstring
changes the value of the input. After 3s, setNumber
is executed with argument 1. So 1 abcd
is printed for the first time. There will be further calls to setNumber due to the multiple timeouts set, but all will call setNumber(1)
. What is confusing is why the log shows 1 'abcd'
twice. Calling setNumber(1)
multiple times should cause only one re-render.
Here is a link of the sandbox
Is React un-optimized?
The above behaviour is not specific to setTimeout
and is a detail of React. It can be observed even without a setTimeout.
function App() {
const [number, setNumber] = useState(0);
const [string, setstring] = useState("");
function func(event) {
setNumber(1);
}
console.log(number, string);
return (
<>
<input
type="text"
onChange={(e) => {
func(e);
}}
value={string}
/>
</>
);
}
The output for the above is:
Even when calling setNumber
twice with the same value, React causes two renders. But yes, not a third one. Here is a link to sandbox
Does this mean React is unoptimized and causes re-render for the same value always?
React bailing behaviour
No, React does bail out of multiple re-renders in most cases. This works when we write setNumber
twice in the same flow like the below.
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import "./App.css";
function App() {
const [number,setNumber] =useState(0);
const [string,setstring] =useState('');
function func(event){
setstring(event.target.value);
setNumber(number+1);
setNumber(number+1);
}
console.log(number,string);
return (
<>
<input type='text'onChange={(e)=>{func(e)}} value={string}/>
</>
)
}
export default App;
The output for the above is:
Here is the link to the sandbox
As expected, calling multiple setStates is not causing multiple re-renders.
But in the original example, set state is called from different contexts (from different setTimeout callbacks) and react still re-renders the component at least once. It does bail out from re-rendring the children though and that is how React creates an optimised User interface.
This is documented in the React docs. React can bail out of renders if the previous and new state values are same, but it might have to re-render the current component. It will definitely bail our for children though and that is why, this not a performance issue. From the docs:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree.
The code snippet below shows the same behaviour:
const TestChild = () => {
console.log("rerender");
return null;
};
export default function App() {
const [number, setnumber] = useState(0);
const [string, setstring] = useState("");
function func(event) {
setstring(event.target.value);
setTimeout(() => {
setnumber(number + 1);
}, 3000);
}
console.log(number, string);
return (
<>
<TestChild />
<input
type="text"
onChange={(e) => {
func(e);
}}
value={string}
/>
</>
);
}
The output to the above is:
which clearly shows that TestChild is not rendered more than it should.
Here is a link to the sandbox
Notes
It is worth noting that a re-render does not mean re-paint of the screen. Repaint is only done when there is an actual diff between the two Virtual DOMs created. Render and commits are different and this gets even more important with concurrent React.
Top comments (0)