useState, Props vs State, and State Snapshots

State gives a component memory. Props bring data from a parent, while each render receives a fixed snapshot of both props and state.

The mental model

React calls a component to calculate its interface. Props are inputs to that call, and state is React-managed memory associated with the component's position in the UI tree.

Remember

useState stores information between renders.

Receive

Props let a parent configure a child.

Render

Each render sees fixed values for that moment.

useState adds component memory

Call useState at the top level of a component. It returns an array containing the current state value and a setter function. Calling the setter requests a new render with the updated value.

A counter remembers its count between renders and updates after a click.
import { useState } from "react";

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

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}
  1. useState(0) sets the initial value.

    React uses 0 on the first render. The initial value is ignored on later renders.

  2. count is the value for this render.

    Read it when calculating JSX or handling an interaction.

  3. setCount requests an update.

    Do not assign to count directly. A setter tells React to render again and keeps the interface in sync.

State can hold strings, booleans, arrays, objects, or any other JavaScript value.
function SearchBox() {
  const [query, setQuery] = useState("");

  return (
    <label>
      Search
      <input
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <span>Looking for: {query || "nothing yet"}</span>
    </label>
  );
}

Hook rule: call useState only at the top level of a component or custom Hook, not inside conditions, loops, or nested functions. A stable call order lets React match each state value to the correct Hook.

Props vs state

Both affect what a component renders, but ownership is different. A parent owns the props it passes. The component owns its local state.

Props

Source
Passed in by a parent component.
Updates
The parent renders with new prop values.
Rule
Read-only inside the receiving component.

State

Source
Declared by the component with a Hook.
Updates
The component calls its state setter.
Rule
Replace it through the setter, never mutate it directly.
The parent supplies a prop, while the child remembers interaction state.
function ExpandableNote({ title }) {
  // title is a prop; the parent owns it.
  const [isOpen, setIsOpen] = useState(false);

  return (
    <section>
      <button onClick={() => setIsOpen(!isOpen)}>
        {isOpen ? "Hide" : "Show"} {title}
      </button>
      {isOpen && <p>State belongs to this component instance.</p>}
    </section>
  );
}

export default function App() {
  return <ExpandableNote title="React state" />;
}
  • Use props for configuration from a parent.

    Examples include a label, product data, or an event handler.

  • Use state for information that changes through interaction.

    Examples include selected tabs, typed text, or whether a panel is open.

  • Do not copy a prop into state without a reason.

    The two values can drift apart. Read the prop directly when it already represents the value you need.

State behaves like a snapshot

Calling a setter does not change the state variable in code that is already running. It asks React for another render. React then calls the component again and gives that render a new state value.

The alert reads the same snapshot that rendered the button, even after requesting an update.
function ScoreButton() {
  const [score, setScore] = useState(0);

  function handleClick() {
    setScore(score + 1);
    alert(score); // Shows the value from this render.
  }

  return (
    <button onClick={handleClick}>Score: {score}</button>
  );
}

On the first click, score is 0 throughout that handler, so the alert shows 0. After the handler finishes, React renders again with score equal to 1. The old handler cannot see forward into the next render.

Several updates calculated from one snapshot all read the same count.
function addThree() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
  // If count is 0, each line requests 1, not 1, then 2, then 3.
}

Boundary for the next topic: when a new value depends on the previous queued value, React provides functional updates such as setCount(current => current + 1). Update queues and batching explain why that form can apply repeated changes correctly.

Keep this model: props are read-only inputs from a parent, state is local memory managed through a setter, and every render works with its own fixed snapshot.

Check your understanding

  1. Why does assigning count = count + 1 not correctly update the interface?
    Show answer

    Changing the local variable does not ask React to render again or update React's stored state. Call setCount(count + 1) instead.

  2. A parent passes theme="dark" to a child. Is theme a prop or state in the child?
    Show answer

    It is a prop because the parent supplies and owns it. The child reads it but does not change it directly.

  3. After setScore(score + 1), why does the next line still see the old score?
    Show answer

    The running event handler belongs to the current render and keeps that render's snapshot. The setter requests a new render; it does not rewrite the value inside code already running.