keepcalculator

06 Numbers and Powers

Why Nested React Components Lose State on Re-render

Card No. 06·10175 · Updated · Standard formula, shown below

Average pace 5.00 min/km
Average speed
12.00 km/h
Result
Finish time
50 min
You entered
Distance
10 km
You entered
You enter
Finish time, Distance
You get
Average pace, Average speed
Rounding
2 dp

When a parent React component re-renders, a nested child component loses its state if the child’s type or position in the tree changes, or if the child is defined inside the parent’s function body. The most common cause is defining a component inside another component, which creates a new component type on every render and forces React to unmount and remount the child, resetting its state.

How React Re-rendering Works

React re-renders a component when its props or state change, or when its parent re-renders. By default, when a parent re-renders, React re-renders all of its children, even if the children’s props haven’t changed. This is a deliberate design choice to keep the model simple: React doesn’t deeply compare props unless you explicitly opt into memoization.

During a re-render, React runs the component function again, producing a new element tree. It then compares that tree with the previous one (a process called reconciliation) and updates only the parts of the DOM that differ. However, the comparison is not a deep equality check on the elements themselves. Instead, React uses the element’s type and key to decide whether a component instance should be preserved or recreated.

The Core Reason: Component Type Identity

React preserves a component’s state as long as the component type at a given position in the tree is the same between renders. If the type changes, React unmounts the old instance and mounts a new one, wiping all local state (useState, useRef, useReducer, etc.).

The classic mistake is defining a component inside the parent’s render function:

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

  // WRONG: Child is re-created on every render
  function Child() {
    const [local, setLocal] = useState(0);
    return <div>{local}</div>;
  }

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

Each time Parent re-renders (e.g., when count changes), a new Child function is created. React sees a different function reference, so it treats it as a different component type and remounts it. The local state resets to 0.

Why Keys Also Matter

Even when the component type stays the same, React uses key to identify which instance corresponds to which element in a list or when elements are reordered. If you render a component in a list without a stable key, or with an index as a key, React may reuse the wrong instance or recreate it when the list changes.

For example, if you have a list of items and you prepend a new item, using the index as a key will cause React to shift the state of all subsequent items. The first item gets the new item’s key, so it unmounts and remounts with fresh state. Using a unique, stable ID as the key prevents this.

How to Fix It: Move the Component Out

The fix is straightforward: define the child component at the top level of the module, outside the parent function. This ensures the component type is a stable reference across renders.

function Child() {
  const [local, setLocal] = useState(0);
  return <div>{local}</div>;
}

function Parent() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <Child />
    </div>
  );
}

Now Child is the same function reference every render, so React preserves its state. If you need to pass data or callbacks, pass them as props. If you need to avoid re-rendering the child when the parent re-renders, wrap the child in React.memo to skip re-renders when props are unchanged.

Other Common Causes of State Loss

  • Changing the component type conditionally: Rendering <A /> in one branch and <B /> in another will reset state because the type changes.
  • Using an unstable key: Keys like Math.random() or the array index (when the list order changes) cause remounts.
  • Forcing a remount: Setting a key on a component that changes (e.g., <Child key={someId} /> where someId changes) will intentionally reset state.
  • Conditional rendering with different positions: If you move a component to a different position in the tree (e.g., from inside a <div> to outside it), React may treat it as a new instance.

How to Diagnose State Loss

If you’re seeing state reset unexpectedly, check the following:

  1. Open React DevTools and look at the component tree. If the component shows as a different type (e.g., Child vs Child with a different reference), that’s the issue.
  2. Add a console.log inside the child’s render to see if it’s being mounted fresh (you’ll see the log on every parent re-render).
  3. Check if the child is defined inside the parent. Move it out.
  4. Check if you’re using a key that changes. Use a stable ID.
  5. If you’re using React.memo, ensure the comparison function is correct—a faulty custom comparator can cause remounts or stale props.

Frequently Asked Questions

Does React re-render all children when a parent re-renders?

Yes, by default. When a parent re-renders, React re-renders all of its children, regardless of whether their props changed. This is because React doesn’t perform prop comparison by default. You can opt out by wrapping a child in React.memo or using useMemo/useCallback to stabilize props.

Why does my component lose state when I add a new item to a list?

This usually happens when you use the array index as the key. When you insert an item at the beginning, all subsequent items shift indices, so React sees a different key for each and remounts them. Use a unique, stable identifier (like an ID) as the key to preserve state.

Can I use useRef to preserve state across re-renders?

useRef preserves its value across re-renders, but it is still reset when the component unmounts. If the component is remounted due to a type change or key change, the ref is reinitialized. To truly preserve state across remounts, you’d need to lift the state up to a parent that doesn’t remount, or use a state management library like Redux or Context.

Is it bad to define components inside other components?

Yes, it’s a common anti-pattern because it causes the child to remount on every parent render, leading to state loss and performance issues. Always define components at the top level of a module. If you need to pass dynamic values, use props.

Does React.memo prevent state loss?

No, React.memo only prevents re-renders when props are shallowly equal. It does not prevent remounting. If the component type changes or the key changes, it will still unmount and remount, losing state. React.memo is about performance, not state preservation.

What is the difference between unmounting and re-rendering?

Re-rendering means the component function runs again, but the instance (and its state) is preserved. Unmounting means the instance is destroyed, and all local state is lost. A remount is an unmount followed by a mount, so state resets. React decides between these based on the element type and key.

Related

Formula used
in t | Finish time | min | 50 | 0
in d | Distance | km | 10 | 0 | 100000
out pace | Average pace | min/km | t / d | 2
out speed | Average speed | km/h | d / (t / 60) | 2

Each line is one input (in, sel, list) or one result (out: label, unit, formula, decimals). How the calculators work

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →