React useState Hook

State is data that can change over time inside a component. When state changes, React re-renders the component so the UI stays in sync with the data.

useState is the hook you reach for when a component needs to remember something between renders: a counter value, a text input’s current value, whether a modal is open, a list of items, and so on.

See React State for a broader overview of how state works in React.

How useState works

useState takes an initial value as its argument and returns an array with two items:

  1. The current state value
  2. A function to update it
import { useState } from 'react';

const [count, setCount] = useState(0);
//     ^^^^^  ^^^^^^^^          ^
//     value  setter        initial value

The square bracket syntax is JavaScript array destructuring. You can name the two values anything you like, but the convention is value and setValue.

When you call the setter function, React updates the state and re-renders the component with the new value.

Examples

Basic counter

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Every time the button is clicked, setCount is called with a new value. React re-renders Counter with the updated count.

Updating based on previous state

When the new value depends on the old value, pass a function to the setter instead of a value. React guarantees the function receives the most recent state.

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>{count}</h2>
      <button onClick={() => setCount(prev => prev - 1)}>Decrease</button>
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => setCount(prev => prev + 1)}>Increase</button>
    </div>
  );
}

Always use the functional form setCount(prev => prev + 1) when the new value depends on the old one. This prevents bugs that occur when multiple state updates happen in the same event.

Array state

When state is an array, you must return a new array instead of mutating the existing one. Common patterns use filter, map, and the spread operator.

import { useState } from 'react';

const initialPeople = [
  { id: 1, name: 'Anna' },
  { id: 2, name: 'Joe' },
  { id: 3, name: 'Pan' },
];

export default function PeopleList() {
  const [people, setPeople] = useState(initialPeople);

  const removeItem = (id) => {
    setPeople(current => current.filter(person => person.id !== id));
  };

  return (
    <div>
      {people.map(person => (
        <div key={person.id}>
          <span>{person.name}</span>
          <button onClick={() => removeItem(person.id)}>Remove</button>
        </div>
      ))}
      <button onClick={() => setPeople([])}>Clear all</button>
    </div>
  );
}

Object state

When state is an object, spread the existing values and override only the ones that changed.

import { useState } from 'react';

export default function Profile() {
  const [person, setPerson] = useState({
    name: 'Anna',
    age: 20,
    message: 'Hello there',
  });

  const updateMessage = () => {
    setPerson(current => ({ ...current, message: 'Message updated!' }));
  };

  return (
    <div>
      <p>{person.name}, age {person.age}</p>
      <p>{person.message}</p>
      <button onClick={updateMessage}>Update message</button>
    </div>
  );
}

The spread operator (...current) copies all existing properties. Then message: 'Message updated!' overrides just the one you want to change. Without the spread, you would lose all the other properties.

Lazy initialisation

If the initial state value is expensive to compute (reading from localStorage, doing a calculation), pass a function instead of a value. React will only call that function once, on the first render.

// This runs the function on every render (wasteful if it's slow)
const [data, setData] = useState(expensiveCalculation());

// This only runs the function once, on mount
const [data, setData] = useState(() => expensiveCalculation());

TypeScript

If you are using TypeScript, you can annotate the type of your state:

const [name, setName] = useState<string>('');
const [items, setItems] = useState<string[]>([]);
const [user, setUser] = useState<User | null>(null);

TypeScript will then catch any attempt to set a value of the wrong type.

Common mistakes

Mutating state directly

React only knows to re-render when you call the setter function. If you modify state directly, nothing updates.

// Wrong: React does not know the array changed
people.push({ id: 4, name: 'Lee' });

// Correct: replace the array with a new one
setPeople(current => [...current, { id: 4, name: 'Lee' }]);

This applies to objects too. Do not do person.name = 'New Name'. Use the setter with a spread instead.

Assuming state updates are synchronous

Calling a setter does not immediately change the state variable. The component re-renders first, and then the new value is available.

const [count, setCount] = useState(0);

function handleClick() {
  setCount(count + 1);
  console.log(count); // Still logs the old value
}

If you need to work with the updated value, do it in the next render, or calculate it from the current value directly.

Using the value instead of the functional update

If you call a setter multiple times in the same event handler, each call reads the same snapshot of state:

// Both calls see count = 0, so count ends up as 1, not 2
setCount(count + 1);
setCount(count + 1);

// Both calls chain correctly, so count ends up as 2
setCount(prev => prev + 1);
setCount(prev => prev + 1);

Use the functional form any time you call the setter more than once per event, or when you are not certain you have the latest value.

FAQ

Why isn’t my state updating right away?

State updates in React are batched and asynchronous. When you call setCount(1), React schedules a re-render. The actual update happens before the next render, not on the same line you called the setter. If you need to react to a state change, use useEffect with that state value as a dependency.

When should I use useState vs useReducer?

Use useState when you have one or two simple values that update independently. Use useReducer when you have several pieces of state that update together, when the next state depends on the previous one in a complex way, or when your update logic has grown hard to follow with multiple setState calls.

Can I store objects or arrays in state?

Yes. The important rule is that you must always replace the value with a new object or array rather than modifying the existing one. React compares state by reference. If you mutate an array or object in place, React sees the same reference and skips the re-render.

What to learn next