React components can possess internal “state,” a set of key-value pairs which belong to the component. When the state changes, React re-renders the component. Historically, state could only be used in class components. Using hooks, you can apply state to functional components too.
The Traditional Approach
React class components have a state property that holds their state. They provide a setState() method you can use to update the state, triggering a re-render.
In this example, the rendered text will always show the number in the component’s state. Clicking the button will increment the value.
Converting to a Functional Component
With such a simple component, it would be ideal to rewrite this as a functional component. To do so, you’ll need to use the useState() hook. Hooks were added in React 16.8; prior to this release, there was no mechanism to add state to functional components.
Here’s what the above component looks like as a functional component:
This is shorter and more readable than the class-based original. Unlike the class component, you can’t access a state instance property or setState() method. Instead, useState() is called to setup the state and obtain an updater function.
Anatomy of the useState() Hook
Hooks are a React feature which allow you to “hook” functionality into functional components. As functions are pure and don’t have instances, capabilities which were originally implemented as React.Component class methods can’t be used directly. Hooks let you add these features to components without having to convert to classes.
The useState() hook sets up an individual state property. It returns an array containing two elements: the current state value, and a function you can call with a new value to update the state.
In the example, we’re using the array destructuring assignment to unpack the array values into clearly named variables. By convention, the setter method should be prefixed with set as it takes the place of the setState() class method.
Calling useState() declares a state variable, value in our case, which will be “preserved” between function calls. That means useState() is guaranteed to return the same value each time you call it within your component. Any other variable value is lost once a function exits; React maintains state values internally to ensure you get the same one back each time your function runs.
Updating the State
The state update function is just a regular function. It’s used in the onClick handler to replace the current state value. React’s internal handling of state values ensures your component will then be re-rendered. useState() will supply the new value, causing the state change to be effected.
There’s an important difference compared to the setState() of class components: functional state updators replace the state, whereas setState() does a shallow merge:
Instead of passing a new state value directly, you may also hand a function to state updators. Functions receive the current state as a parameter and should return the new state value. This is useful when working with toggleable values.
This helps you reuse toggling logic in multiple places within your component.
Default Values
There’s one more point to note about useState(). The hook itself accepts a parameter which sets the initial value of the state variable. In the example above, the value will be initialised to 1. When you don’t specify a value, undefined is used. This matches the behaviour when setting up the state instance property in a class component.
If you pass a function to useState(), React will call it and use its return value as the initial state value.
This technique enables “lazy” state initialisation. The function won’t be called until React is actually ready to setup the state.
Using a function also ensures the initial state value only gets computed once. This is important if determining your initial state requires an expensive computation – if you pass it directly, then the value will be computed every time the component renders, compared with once on first render if you pass a reference to a function.
The subtle but significant difference in the two useState() calls illustrates the potential performance improvement. The first line would perform the expensive operation on every render call, even if it was redundant because the state was already initialised. This wouldn’t occur in the second case.
Using Multiple State Values
You have a couple of choices when using multiple state values in a single functional component. You could revert to a class-like system, using a single object stored in state:
You’d need to make sure you call setUser() with the updated user object. The spread syntax comes in handy in the same way as class components:
This creates a new object with the existing properties of user. It then updates the username property to its new value. It’s important you create a new object, instead of directly mutating the existing object, so React’s state reconciliation can identify the change.
Alternatively, you can call useState() multiple times to set up unique state variables for each item. This is often the preferred approach for functional components. It can make it easier to update individual state values.
The stateful properties now have their own state variables and update functions.
Conclusion
React’s useState() hook makes functional components more powerful by allowing them to possess state. You can set an initial value, access the current value with an assurance it’ll persist between re-renders, and update the state using a specially provided function.
Stateful functional components are often quicker to write than their class-based counterparts. Moreover, they can make it more obvious what’s going on in your codebase as the references to state and setState() are eliminated in favour of clear variable names. Ultimately, useState() provides flexibility and means you no longer need to convert functional components to class components the moment you require state.