How to useState in React
A detailed look at how React.useState works
Since the additions of React Hooks in React 16.8, functional components allow for side effects and state management. Within state management in React, there are two hooks that can be utilized:
useReducer. This article will guide you on using
useState for state management.
React ships with
useState hook (function) and can be accessed through
React.useState. The method takes a single argument, the initial value for that piece of state. It then returns an array, where the 0th index is the actual state and the 1st index is used to update that state.
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.
Under the hood, when a new argument is passed into the current state value, React causes a re-render of the component, therefore updating the UI. In the above example, upon calling our setter
setFruit and assuming that we’re passing in a new
fruit value, React will re-render and show, for example, avocado or peach, depending on which fruit we’re currently seeing.
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
To simply put,
useState is an instrument to preserve values between function invocations/renders and to trigger component re-rendering.
useState, it’s imperative to understand that each piece of the state comes with its own way to access the persisted value and updater function, following React’s composition pattern. If you’re coming from class components, you’ll find that the state management is nicely modularized as opposed to an object with multiple properties from
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
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
There may be a scenario where you will need to initialize the state through a function call. To illustrate, it will look something like this.
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.
useStateallows you to trigger a component re-render, add state to a function component, and preserve values between renders
- enabling lazy state initialization is the result of passing in a function definition versus a function invocation