~/hackweb.dev
State with useState
Quiz
...

State with useState

beginner · updated Tue Sep 08 2026Contribute

Manage component state with the useState hook.

STEP 1 · React State & Render Cycle

React State & Render CycleStep 1 / 8
useStateComponentVirtual DOMReal DOMScreen

Component renders for the first time — useState gives the initial value.

State with useState

State is data that changes over time within a component. The useState hook lets you add state to function components.

Basic Syntax

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  return <p>Count: {count}</p>;
}

useState returns an array: the current state value and a setter function.

Updating State

Call the setter to trigger a re-render:

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

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

React re-renders the component with the new value.

State vs Variables

A regular variable resets on every render:

function Bad() {
  let x = 0;
  x = x + 1; // Resets to 0 next render
  return <p>{x}</p>;
}

function Good() {
  const [x, setX] = useState(0);
  setX(x + 1); // Persists across renders
  return <p>{x}</p>;
}

State persists. Variables don’t.

State is Asynchronous

React batches state updates. The state variable doesn’t update immediately:

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

  function handleClick() {
    setCount(count + 1);
    console.log(count); // Still 0!
  }

  return <button onClick={handleClick}>{count}</button>;
}

The console.log shows the old value because state updates are batched.

Functional Updates

When the new state depends on the previous state, use a function:

setCount(prev => prev + 1);
setCount(prev => prev + 1);
// Both work correctly — prev is always current

Without the function, both reads use the same stale count.

Lazy Initialization

Pass a function to useState for expensive initial computations:

const [data, setData] = useState(() => {
  return JSON.parse(localStorage.getItem("saved"));
});

The function only runs on the first render.

Best Practices

  1. Use functional updates — When new state depends on old state
  2. Keep state minimal — Derive values instead of duplicating
  3. Use one state object — For related values: useState({ name, age })
  4. Initialize with lazy values — For expensive computations

Common Mistakes

  1. Mutating state directlystate.value = x won’t trigger re-render
  2. Assuming state updates are synchronous — They’re batched and async
  3. Forgetting the setterconst [x] = useState(0) — no way to update
  4. Overusing useState — Combine related values into one state object