Preserving and Resetting State in React

React attaches state to a component's place in the UI tree. Keep the same component identity in that place to preserve state, or change its identity when a fresh start is intentional.

State belongs to a component identity

A state variable is not stored inside a JSX tag or component function. React stores it and associates it with an identity under a particular parent. By default, that identity comes from the component's type and position among its siblings. A key can become part of the identity.

Useful shortcut: same type + same position + same key means preserve state. A different type, position, or key can create a new identity and reset state.

Preserving state

When React renders the same component type in the same position, it reuses that component instance and its state. Changing props or changing how the JSX was calculated does not reset the state by itself.

Toggling the theme changes a prop, but Counter stays the first child of the same main, so its score survives.
import { useState } from "react";

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);

  return (
    <button
      className={isFancy ? "fancy" : "plain"}
      onClick={() => setScore(score + 1)}
    >
      Score: {score}
    </button>
  );
}

export default function Scoreboard() {
  const [isFancy, setIsFancy] = useState(false);

  return (
    <main>
      <Counter isFancy={isFancy} />
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={event => setIsFancy(event.target.checked)}
        />
        Fancy style
      </label>
    </main>
  );
}

React cares about the resulting UI tree, not whether the JSX appears in one branch, a variable, or a helper expression. If the resulting component matches the previous identity, its state is preserved.

Resetting state

When a component at a position is removed or replaced by a different type, React destroys its state. If the original component returns later, it is a new instance initialized from scratch. State in its descendant components is removed too.

Preserved

What React sees
<Counter /> in the same position before and after.
Result
The existing state remains.

Reset

What React sees
<Counter /> replaced by <p>, or by a new key.
Result
The old state is discarded and later starts from its initial value.
Hiding this counter removes it. Showing it again mounts a new counter with a score of 0.
function Scoreboard() {
  const [showCounter, setShowCounter] = useState(true);

  return (
    <main>
      {showCounter ? <Counter /> : <p>Counter is hidden.</p>}
      <button onClick={() => setShowCounter(!showCounter)}>
        Toggle counter
      </button>
    </main>
  );
}

Avoid defining components inside other components. A nested component function is created again on every parent render. React sees a different component type and resets that subtree's state. Define component functions at the top level instead.

Keys outside lists

Keys are best known for identifying list items, but they can identify any sibling component. A key tells React to use the key instead of sibling order when matching an element with its previous identity.

Switching players changes the key, so each player receives a fresh counter even though both render in the same JSX position.
function Game() {
  const [player, setPlayer] = useState("Mira");

  return (
    <main>
      <Counter key={player} player={player} />
      <button
        onClick={() => setPlayer(player === "Mira" ? "Noah" : "Mira")}
      >
        Next player
      </button>
    </main>
  );
}

The transition from key="Mira" to key="Noah" tells React that the old counter and new counter are different identities. React unmounts the old subtree, creates a new one, and runs its state initializers.

A chat composer uses the recipient ID as a key so a draft cannot accidentally carry over to another person.
function Messenger({ selectedContact }) {
  return (
    <Chat
      key={selectedContact.id}
      contact={selectedContact}
    />
  );
}

function Chat({ contact }) {
  const [draft, setDraft] = useState("");

  return (
    <textarea
      aria-label={`Message ${contact.name}`}
      value={draft}
      onChange={event => setDraft(event.target.value)}
    />
  );
}
  • Keys are local to a parent.

    A key only needs to distinguish siblings under that parent. It is not a global ID.

  • A key is not a prop.

    React uses it internally. Pass a separate prop if the child needs the same value.

  • Choose stable, meaningful keys.

    Use an entity ID when identity follows data. Do not use a random value on every render because that forces repeated resets.

  • Reset deliberately.

    Keys are useful for forms, games, editors, and other stateful subtrees that should restart when their subject changes.

Keep this model: React preserves state for the same component identity under the same parent. Removing it, changing its type, or changing its key gives React a new identity and resets that subtree.

Check your understanding

  1. Does changing a component's props automatically reset its state?
    Show answer

    No. If the component keeps the same type, position, and key under its parent, React preserves its state and renders it with the new props.

  2. Why does changing <Chat key={contact.id} /> clear the draft?
    Show answer

    The new key creates a different component identity. React removes the previous Chat subtree and mounts a new one, so useState("") supplies a fresh draft.

  3. Why can a component declared inside its parent lose state whenever the parent renders?
    Show answer

    The parent creates a new component function on each render. React sees that function as a different component type, replaces the old subtree, and resets its state.