W3H in React: Lazy Initializers in React.useState()

When to use lazy initializer in your React component

ยท

4 min read

Context๐Ÿ‘€

hook-flow.png

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 the initialValue (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!

Let's connect ๐Ÿ˜ƒ LinkedIn Twitter

ย