How useState Works in React (with Examples)

State is the data a component remembers between renders. In React function components, you add state with the useState hook. It looks simple — and it is — but a few details about how it works prevent most beginner bugs. (Not sure whether a value belongs in state at all? See props vs state.)

The basics

useState returns a pair: the current value and a function to update it. You typically destructure them:

import { useState } from 'react';

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

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

The argument to useState (0 here) is the initial value — it's only used on the first render. After that, React keeps track of the latest value for you.

Updating state triggers a re-render

Calling setCount does two things: it stores the new value, and it tells React to re-render the component. During the next render, useState hands you the updated value. This is the core loop of React: state changes in, new UI out.

Crucially, you never mutate the variable directly (count = count + 1 does nothing useful). You always call the setter so React knows something changed.

Updates are batched and "asynchronous"

The state variable does not change immediately after you call the setter. It updates on the next render. This trips people up:

function handleClick() {
  setCount(count + 1);
  setCount(count + 1);
  // count only increases by 1 — both calls read the same stale "count"
}

When the new value depends on the previous one, use the updater function form instead. React passes you the most recent value:

function handleClick() {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  // now count increases by 2
}

Updating objects and arrays

React compares state by reference, so you must create a new object or array rather than mutating the existing one:

// wrong — mutates the same object, React may not re-render
user.name = 'Ada';
setUser(user);

// right — new object
setUser({ ...user, name: 'Ada' });

Expensive initial values

If computing the initial value is costly, pass a function. React calls it only once instead of on every render:

const [data, setData] = useState(() => expensiveInit());

Key takeaways

  • The argument to useState is only the initial value.
  • Always update via the setter; never mutate state directly.
  • Use the prev => updater form when the new value depends on the old one.
  • Create new objects/arrays instead of mutating them.

Want the structured path? Explore the React roadmap or browse more articles.