Skip to content
Laviyx Learn

Try

React course menu

React useState Hook: State Explained with Examples

Learn React state with useState: why normal variables do not work, declaring state, how setting state triggers a re-render, state as a snapshot, updater functions for updates based on the previous value, multiple state variables, and the rules of hooks.

Beginner9 min readUpdated
On this page

State is a component's memory: values that change while the app runs and that the screen should reflect, such as a counter, the text in a search box, or whether a menu is open.

Why a normal variable does not work#

JSX
export default function App() {
  let count = 0;

  function handleClick() {
    count = count + 1;          // the variable changes...
  }

  return <button onClick={handleClick}>Clicked {count} times</button>;   // ...but the screen never does
}
Result

Clicking does nothing visible, for two reasons: changing a local variable does not tell React to re-render, and even if it re-rendered, let count = 0 would run again and reset it.

useState#

useState solves both problems: React keeps the value between renders, and the setter function tells React to render again.

JSX
import { useState } from "react";

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

  return (
    <div>
      <button onClick={() => setCount(count - 1)}>−</button>
      <span style={{ margin: "0 12px" }}>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(0)} style={{ marginLeft: 12 }}>Reset</button>
    </div>
  );
}
Result
  • useState(0) declares a state variable with initial value 0.
  • It returns a pair, destructured as [count, setCount]: the current value and a function to change it.
  • Calling setCount(newValue) stores the new value and re-renders the component; the function runs again and useState returns the new value.

Each component instance has its own state. Two counters on a page count independently.

Several pieces of state#

Use one useState call per independent value:

JSX
import { useState } from "react";

export default function App() {
  const [name, setName] = useState("");
  const [isDark, setIsDark] = useState(false);

  return (
    <div style={{ background: isDark ? "#222" : "#fff", color: isDark ? "#eee" : "#222", padding: 12 }}>
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Your name" />
      <label style={{ marginLeft: 12 }}>
        <input type="checkbox" checked={isDark} onChange={(e) => setIsDark(e.target.checked)} /> Dark mode
      </label>
      <p>Hello{name ? `, ${name}` : ""}!</p>
    </div>
  );
}
Result

State is a snapshot#

Setting state does not change the variable in the code that is already running. It requests a new render with the new value. Within one event handler, count keeps the value it had when that render happened:

JSX
import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const [note, setNote] = useState("");

  function addThree() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    setNote(`Inside the handler, count was still ${count}`);
  }

  return (
    <div>
      <button onClick={addThree}>+3 (wrong way)</button>
      <p>Count: {count}</p>
      <p>{note}</p>
    </div>
  );
}
Result

Each click adds only 1: all three calls compute count + 1 from the same snapshot, so they all set the same value.

Updater functions#

When the new state depends on the previous state, pass a function to the setter. React calls it with the latest value, in order:

JSX
import { useState } from "react";

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

  function addThree() {
    setCount((c) => c + 1);
    setCount((c) => c + 1);
    setCount((c) => c + 1);
  }

  return (
    <div>
      <button onClick={addThree}>+3 (right way)</button>
      <p>Count: {count}</p>
    </div>
  );
}
Result

Use the updater form (setX(prev => ...)) whenever the update is based on the current value, especially in code that runs later, such as timers and after await.

Batching#

React groups (batches) all state updates from one event into a single re-render, so setting several state variables in one handler re-renders once, not once per setter. This is why the snapshot behaviour above is consistent.

Rules of hooks#

useState is a hook: a function starting with use that lets components use React features. Hooks have two rules:

  1. Call hooks only at the top level of a component (or another hook): not inside if, loops or nested functions.
  2. Call hooks only from React function components or custom hooks, not from ordinary functions.

React identifies each useState by the order of calls, so the order must be the same on every render.

What should be state?#

Not everything needs state. Ask:

  • Does it change over time because of user actions or data loading? If not, it is a constant or a prop.
  • Can it be calculated from other state or props? Then calculate it during rendering instead of storing it. For example, store items and compute total from them; storing total separately risks it getting out of sync.

Common mistakes#

Practice#

Practice exercise

Build a "like" counter for a post: a ♥ button that adds one like per click, a "+5" button that adds five likes using the updater form, and text that says "1 like" or "N likes" correctly.

Show hint

For "+5", call setLikes((l) => l + 5) (or five updater calls). Choose the word with likes === 1 ? "like" : "likes".

Show solution
JSX
import { useState } from "react";

export default function App() {
  const [likes, setLikes] = useState(0);
  return (
    <div>
      <button onClick={() => setLikes((l) => l + 1)}>♥</button>
      <button onClick={() => setLikes((l) => l + 5)} style={{ marginLeft: 8 }}>+5</button>
      <p>{likes} {likes === 1 ? "like" : "likes"}</p>
    </div>
  );
}
Result

Key takeaway#

Next: Updating Objects and Arrays in State.

Finished this lesson?