useState takes the initial value of the state variable as an argument. You can pass it directly, as shown in the previous example, or use a function to lazily initialize the variable (useful when the initial state is the result of an expensive computation):

Using useState alone won’t work because its argument is used the first time only, not every time the property changes (look here for the right way to do this).

But useState doesn’t return just a variable as the previous examples imply. It returns an array, where the first element is the state variable and the second element is a function to update the value of the variable:

Because compared to an object, an array is more flexible and easy to use.

If the method returned an object with a fixed set of properties, you wouldn’t be able to assign custom names in an easy way. You’d have to do something like this (assuming the properties of the object are state and setState):

However, this update function doesn’t update the value right away. Rather, it enqueues the update operation. Then, after re-rendering the component, the argument of useState will be ignored and this function will return the most recent value.

If you use the previous value to update state, you must pass a function that receives the previous value and returns the new value:

The first rule means that, even inside functional components, you shouldn’t call useState in loops, conditions, or nested functions because React relies on the order in which useState functions are called to get the correct value for a particular state variable.

In that regard, the most common mistake is to wrap useState calls or update function calls in a conditional statement (they won’t be executed all the time):

A functional component can have many calls to useState or other hooks. Each hook is stored in a list, and there’s a variable that keeps track of the currently executed hook.

When useState is executed, the state of the current hook is read (or initialized during the first render), and then, the variable is changed to point to the next hook. That’s why it is important to always maintain the hook calls in the same order, otherwise, a value belonging to another state variable could be returned.

In general terms, here’s an example of how this works step by step:

React initializes the list of hooks and the variable that keeps track of the current hook

React calls your component for the first time

React finds a call to useState, creates a new hook object (with the initial state), changes the current hook variable to point to this object, adds the object to the hooks list, and return the array with the initial state and the function to update it

React finds another call to useState and repeats the actions of the previous step, storing a new hook object and changing the current hook variable

The component state changes

React sends the state update operation (performed by the function returned by useState) to a queue to be processed

React determines it needs to re-render the component

React resets the current hook variable and calls your component

React finds a call to useState, but this time, since there’s already a hook at the first position of the list of hooks, it just changes the current hook variable and returns the array with the current state and the function to update it

React finds another call to useState and since a hook exists in the second position, once again, it just changes the current hook variable and returns the array with the current state and the function to update it

If you like to read code, ReactFiberHooks is the class where you can learn how hooks work under the hood.

Conclusion

useState is a hook (function) that allows you to have state variables in functional components. You pass the initial state to this function, and it returns a variable with the current state value (not necessarily the initial state) and another function to update this value.

The important points to remember are:

The update function doesn’t update the value right away

If you use the previous value to update state, you must pass a function that receives the previous value and returns an updated value, for example, setMessage(previousVal => previousVal + currentVal)

If you use the same value as the current state (React uses the Object.is for comparing) to update the state, React won’t trigger a re-render

Unlike this.setState in class components, useState doesn’t merge objects when the state is updated. It replaces them

useState follows the same rules that all hooks do. In particular, pay attention to the order in which these functions are called (there’s an ESLint plugin that will help you enforce these rules)

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Hi Alan, you’re right, it’s not completely true. From the documentation:
> If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
> Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go ‘deeper’ into the tree.

So it doesn’t always skip the rendering. I’ll update this part of the article. Thank you so much!

I can see why a call to useState() shouldn’t be mixed into branching code (ifs, loops etc), because the identity of the state is dictated by the order of useState() calls within the stateless function call.

However, I can’t see why having calls to the setMessage function in branching code below would be a problem (as per your example code quoted below).

That’s because once the setMessage reference has been created, the association between the function and the specific state hook has been established, and any call to setMessage or setList will manipulate the correct value.

In fact I don’t think hooks could work at all if the order of state hook _setting_ calls had to be predictable, so I don’t think there’s anything wrong with the code example.

By contrast if the useState() call was conditionally called (e.g. based on props), then any state established by a later useState call would have an unpredictable identity on the second render.