How to useState in React

A detailed look at how React.useState works


Mental Model

The conventional and more precise way to write the above code is to use ES6 array destructuring, enabling us a one-liner.

Now that we’ve seen a simple example of how to utilize the useState API, we now know that useState allows us to trigger a component re-render and preserve values between those renders.

Component Re-render

Preserving Values between Re-renders

Since the beginning of React Hooks, react components are now functions. It may be natural for you to think the same. However, this is not the case. The entire philosophy of React is to ensure that components can illustrate their UI based on the current state. As a result, React has to have a way to persist values between function calls, preventing them from being garbage collected even after function invocation. The public API for this, as we have already seen, is useState.

To simply put, useState is an instrument to preserve values between function invocations/renders and to trigger component re-rendering.


It is also important to note that compared to setState in class components, useState does not merge the new object with the previous state. If you’re brand new to React, I would not worry about this. The important characteristic to understand is that if you have a previous piece of state and you’d like to have it merged to the new state, you’ll need to include a copy of it beforehand.

A more comprehensive example of setting the current state based on the previous state is illustrated below. In summary, you’ll pass a handler function to an event attribute, such as an onClick, which will call the updater function setFruit.

It may also be pertinent to utilize the useReducer hook if it so happens that the most logical data type is an object. That’s for another article, however!

Lazy State Initialization

If you were to play around with this code, you’ll notice that React uses the calculated value from getAvocadoCount on the initial render, as well as any subsequent re-renders. In other words, getAvocadoCount is invoked anytime the component re-renders — a computationally expensive calculation that can potentially result in a performance hit. This is not ideal at all as we would like for the initialization to be done once during the initial render only, not on subsequent renders. Fortunately, react gives us a way to navigate through this situation.

The solution is to pass useState a function that, when invoked, will resolve to the initial state. By doing so, useState sees that it received a function as the initial state argument, and it’ll only invoke it once on the initial render.

In summary, the solution lies in what we pass into useState. The first example illustrates when useState is passed a function invocation, whereas in the second example useState is initialized a function definition. By utilizing the function definition version, state will initialize in the first render and is disregarded in subsequent renders, resulting in performance optimization.

In Summary…

  • enabling lazy state initialization is the result of passing in a function definition versus a function invocation

Software developer with a liking to avocados