React State

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

Think of state as your component’s memory. A counter remembers its current count. A form remembers what the user typed. A toggle remembers whether a panel is open or closed. Without state, your components would be static: they could display information, but nothing could change.

The Problem State Solves

In plain JavaScript, if you want to update the page when data changes, you have to find the right DOM elements and update them manually. React works differently. You store your data in state, update the state, and React handles re-rendering the correct parts of the page automatically.

// Without state, this button never updates the number shown
function Counter() {
  let count = 0;

  function handleClick() {
    count = count + 1; // this updates the variable, but React never knows
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

The code above does not work as expected. The variable count changes, but React has no way to know it changed, so the displayed number never updates. State fixes this.

Adding State with useState

useState is a React hook that lets you add state to a functional component. Import it from React:

import { useState } from 'react';

Call useState inside your component and pass in the initial value:

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

This one line does three things:

  1. Creates a state variable called count, starting at 0.
  2. Creates a function called setCount that you call when you want to change the value.
  3. Returns both as an array, which you unpack using destructuring.

The naming convention is [value, setValue]. You can name them anything, but following this pattern makes code easier to read.

A Simple Counter

Here is the counter example rewritten with state:

import { useState } from 'react';

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

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

export default Counter;

Now when the button is clicked, setCount is called with the new value. React sees that state has changed, re-renders Counter, and the updated count appears on screen.

Why You Cannot Mutate State Directly

You must always use the setter function to change state. Never modify the state variable directly:

// Wrong. React will not re-render when you do this.
count = count + 1;

// Correct.
setCount(count + 1);

React uses the setter function call as the signal to re-render. If you skip it and modify the variable directly, React has no idea anything changed and the UI will not update.

The same rule applies to objects and arrays. Do not mutate them in place. Create a new object or array instead:

// Wrong
person.name = 'Alice'; // mutates the existing object
setPerson(person);

// Correct
setPerson({ ...person, name: 'Alice' }); // creates a new object

The Functional Update Pattern

If your new state value depends on the previous value, pass a function to the setter instead of a value:

// Less safe: reads count from the current render
setCount(count + 1);

// Safer: always uses the most recent state value
setCount(prev => prev + 1);

This matters when multiple state updates happen in quick succession or inside async code. The function version always receives the latest state, so you avoid stale values.

Here is a counter that uses the functional pattern for both increment and decrement:

import { useState } from 'react';

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

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

export default Counter;

Common Mistakes

Relying on state updating immediately

Calling the setter function does not change the variable right away. The update is scheduled, and the new value is only available on the next render:

function handleClick() {
  setCount(count + 1);
  console.log(count); // still shows the old value here
}

If you need to do something after state updates, look at the useEffect hook.

Storing derived values in state

If a value can be calculated from existing state or props, do not put it in state. Just compute it during render:

// Unnecessary state
const [fullName, setFullName] = useState('');

// Better: derive it
const fullName = `${firstName} ${lastName}`;

Multiple independent values vs one object

For values that change independently, use separate useState calls. For values that always change together (like form fields that get submitted as one object), keeping them in a single state object makes sense.

// Good for independent values
const [isOpen, setIsOpen] = useState(false);
const [count, setCount] = useState(0);

// Good for related values
const [form, setForm] = useState({ firstName: '', lastName: '', email: '' });

FAQ

Why is my state not updating immediately?

State updates in React are asynchronous. When you call setCount(5), the component does not re-render at that exact moment. React batches updates and processes them together for performance. The new value will be available on the next render. If you need to read the updated value, use useEffect or the functional update pattern.

When should I use useState vs useReducer?

Start with useState. Reach for useReducer when you have multiple related pieces of state that change together in response to the same event, or when the next state depends on the previous state in complex ways. A form with a single “reset all fields” action, or a shopping cart with add, remove, and clear operations, are good fits for useReducer.

Can I call useState more than once in a component?

Yes. You can call useState as many times as you need:

const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [isActive, setIsActive] = useState(false);

Each call creates its own independent piece of state.


State in Class Components (Legacy)

Before hooks existed, state was only available in class components via this.state and this.setState. You will see this pattern in older codebases:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prev => ({ count: prev.count + 1 }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Count: {this.state.count}
      </button>
    );
  }
}

For new code, use useState instead. It is simpler, requires less boilerplate, and achieves the same result. See Class Components for more context on when you might encounter the class pattern.


See also