State & InteractionIntermediate8 min04 / 9

State with useState

Learn how useState gives your React components memory — letting them track data that changes over time and automatically update the screen when it does.

So far you've built React components that always display the same thing — they receive props and render HTML. That's great for static content, but real apps need to react to the user. A button that counts clicks. A toggle that shows or hides a menu. A form that tracks what you've typed.

All of these need state — data that can change over time and that should cause the component to update what it shows. This is where React's useState hook comes in, and it's one of the most satisfying moments in learning React.

#What Is State, Exactly?

Think of it like

A Scoreboard at a Sports Game

Think of a scoreboard at a basketball game. The scoreboard always shows the current score. When a team scores, the number changes and the scoreboard immediately reflects the new value.

In React, state is your component's scoreboard. It holds the current value of something that can change. When you update state, React automatically re-renders the component — just like the scoreboard updating the moment a basket is scored. You never have to manually touch the DOM.

State is different from props in one key way: a component owns and controls its own state, whereas props are handed down from a parent. If you need a number that changes when a button is clicked, that number lives in state — not in props.

#Importing and Calling useState

useState is a hook — a special function React provides that lets you "hook into" React features from inside a function component. You import it from the react package and call it at the top of your component.

A minimal but complete counter component using useState.
import { useState } from 'react';

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Let's unpack that one line: const [count, setCount] = useState(0);

  • `useState(0)` — calls the hook and tells React: "this state variable starts at 0".
  • `count` — the current value of the state. Use this anywhere you'd use a regular variable.
  • `setCount` — the setter function. Call it whenever you want to change the value. React will update count and re-render the component automatically.

The square brackets are JavaScript array destructuringuseState returns a two-element array, and you're naming those two elements.

#Why Never Mutate State Directly

Common mistake

Never Do This

``jsx // WRONG — React won't know anything changed! count = count + 1; ``

If you change count directly, React has no idea the value changed, so it never re-renders. The screen stays frozen even though the variable updated. Always use the setter function (setCount) — that's the signal React listens for.

React works by comparing the previous state to the new state. When you call setCount(newValue), React schedules a re-render with the updated value. Mutating the variable directly bypasses this system entirely — it's like changing the scoreboard's wiring without flipping the switch.

#The Updater Function Form

Most of the time passing a value directly works fine: setCount(count + 1). But there's a subtle problem if you call the setter multiple times in one event handler — React batches those updates, so each call might read the same stale value of count.

The safe pattern is the updater function form: instead of passing the next value, you pass a function that receives the guaranteed-current previous value and returns the next one.

Use the updater function whenever the new value depends on the old value, especially in rapid or batched updates.
// Directly passing a value — usually fine
setCount(count + 1);

// Updater function form — always safe
setCount(prevCount => prevCount + 1);

// Example: incrementing twice correctly
function handleDoubleClick() {
  setCount(prev => prev + 1); // first
  setCount(prev => prev + 1); // second — reads updated prev!
}
Tip

Naming Convention

By convention, name the previous-value argument with a prev prefix: prevCount, prevItems, prevIsOpen. This makes it immediately clear you're working with the previous state, not some other variable.

#State Can Hold Any Value

useState isn't limited to numbers. You can store strings, booleans, arrays, objects — anything JavaScript can hold.

A boolean state value toggling a piece of UI on and off.
function ToggleMessage() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <div>
      <button onClick={() => setIsVisible(prev => !prev)}>
        {isVisible ? 'Hide' : 'Show'} message
      </button>
      {isVisible && <p>Hello there! </p>}
    </div>
  );
}
Note

Each Component Instance Has Its Own State

If you render <Counter /> twice on the same page, each one gets its own completely independent count. Clicking the button on one counter has zero effect on the other. State lives inside the component instance, not globally.

Quick check

You want to increment a counter by 1. Which of the following is the SAFEST way to update state?

Key takeaways

  • `useState(initialValue)` returns `[currentValue, setterFn]` — use the setter to update, never mutate directly.
  • Calling the setter tells React the value changed, triggering an automatic re-render of the component.
  • Use the updater function form `setState(prev => ...)` whenever the new value depends on the previous one.
  • Each component instance has its own independent copy of its state.
  • State can hold any JavaScript value: numbers, strings, booleans, arrays, or objects.
Practice challenges
Test yourself · earn XP
0/4
Fix the bug#1

This code has a bug — what's wrong?

fix-bug
function Counter() {
  let count = 0;

  return (
    <button onClick={() => count = count + 1}>
      Clicked {count} times
    </button>
  );
}
Fill in the blank#2

Complete this counter's state declaration so it starts at 0 and gives you both the current value and a setter. Fill in the hook being called.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = (0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Predict the output#3

Using the updater function form, what number does the paragraph show after ONE click of the button?

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

function handleClick() {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
}

// <p>{count}</p> and a button wired to handleClick
Reorder the lines#4

Arrange these lines into a correct ToggleMessage component that uses a boolean state to show or hide a paragraph and flips it with the updater form.

1
  );
2
    </div>
3
}
4
  return (
5
      {isVisible && <p>Hello there!</p>}
6
    <div>
7
      <button onClick={() => setIsVisible(prev => !prev)}>Toggle</button>
8
  const [isVisible, setIsVisible] = useState(false);
9
function ToggleMessage() {
Your turn
Practice exercise

Build a LikeButton component. It should display a count starting at 0 and a button labeled "Like". Each time the button is clicked, the count should go up by 1. Use the updater function form of setCount.

Try it live — edit the code and hit Run to see it rendered:

solution.jsx · editable