W3H in React: Lazy Initializers in React.useState()
When to use lazy initializer in your React component
Context๐
React and React Hooks need no introduction (Wow what a start of this blog, LOL)!
The above image is a result of efforts from Donavon West and Dan Abramov.
I was looking at this flow diagram and thought of sharing my understanding about the first activity of the Mount
phase which is "Run lazy initializers".
Let us begin!
It is absolutely optional!
If you are thinking that if the hook flow diagram is what React Hooks follow then am I doing it wrong the whole time with useState()
- then let me assure you that is not the case!
Lazy Initializers are completely optional for useState
and it should be utilized in your codebase depending on your need.
The W3H (Why, What, When, How) ๐ค
When you call the useState
hook with an initialValue
(=initialState
in React Codebase), it returns an array. We destructure and extract two items from that:
- value of the state
- a function to update that value of the state (which is called the "state
dispatch
function" - we are probably familiar with this syntax:setWHATEVER_NAME_YOU_PREFER
)
When you call the state dispatch function
, you pass the new value for the state and that triggers a re-render
of the component which leads to useState
getting called again to retrieve the new state value and the dispatch function again.
Now, let us wait and try to visualize a very simple React component which has a useState hook. I am thinking about a basic counter component example like below:
const Counter = () => {
const initialValue = 0;
const [lightWeightValue, setLightWeightValue] = React.useState(initialValue);
const incrementCount = () => setLightWeightValue(lightWeightValue + 1);
return <button onClick={incrementCount}>{lightWeightValue}</button>;
};
So when I am saying that, "when you call the state dispatch function, you pass the new value for the state and that triggers a re-render
of the component" - then it is expected that all the code present inside the <Counter />
component in the above example will run.
Okay great! So far so good.
The superfast JS Engines would not break a sweat to create and set initialValue
in every re-render, child's play! ๐ผ
Now, let us tweak the code a bit:
const Counter = () => {
const initialValue = calculateExpensiveFactorial(valueReceivedAfterFetchCall);
const [computationallyExpensiveValue, setComputationallyExpensiveValue] = React.useState(initialValue);
const incrementCount = () => setComputationallyExpensiveValue(computationallyExpensiveValue + 1);
return <button onClick={incrementCount}>{computationallyExpensiveValue}</button>;
};
Can we repeat our statement now: "The superfast JS Engines would not break a sweat to create and set initialValue
in every re-render, child's play! ๐ผ"
Absolutely not!
Because if we call calculateExpensiveFactorial
on every re-render then this code will become very slow and the JS Engine will loose it's performance drastically. Same thing would have happened for IO operations (like, fetching values from localStorage
) because that is also computationally heavy.
This is the type of scenario where lazy initialization should be introduced in your component code.
Please observe what has been done here:
const Counter = () => {
const initialValue = () => calculateExpensiveFactorial(valueReceivedAfterFetchCall); // lazy initialization
const [computationallyExpensiveValue, setComputationallyExpensiveValue] = React.useState(initialValue);
const incrementCount = () => setComputationallyExpensiveValue(computationallyExpensiveValue + 1);
return <button onClick={incrementCount}>{computationallyExpensiveValue}</button>;
};
Now we can see that a function is being passed in useState
hook - this is what "Lazy Initialization" is.
When you pass a function to
useState
, React will only call the function when it needs theinitialValue
(which is when the component is initially rendered).
With great power, comes great responsibility ๐ค
When a library exposes an API for devs to use, it should be used wisely.
We must understand lazy initialization is a performance optimization technique provided by the React team and should be suitable in some specific scenarios which is explained in this blog post.
You can explore the React codebase in detail to know more about the implementation of useState() hook, this is the entry point ๐
Oh...and the lazy initializer is completely optional, you can see it here .
Nobody's born "great", it is the "habit" that helps you develop the sense of "responsibility" in real life.
โI'm not a great programmer; I'm just a good programmer with great habits.โ โ Kent Beck
Read my other article here: Why typeof(null) is "object" in JavaScript
Thanks!