Skip to content
Frontend May 15, 2026 7 min read

A Mental Model for React Rendering That Actually Sticks

Stop guessing why your component re-rendered. A clear, durable model of how React decides what to render and when — and what that means for performance.

D

DevCraftly Team

DevCraftly

Share
A Mental Model for React Rendering That Actually Sticks

Most React performance confusion comes from a fuzzy mental model of when components render. Once the model is crisp, the optimizations become obvious — and you stop reaching for memo everywhere out of superstition.

Rendering is just calling your function

A “render” is React calling your component function to get a description of the UI. It does not necessarily touch the DOM. React renders, diffs the result against the previous tree, and only then commits the minimal DOM changes.

So the cost of a render is: your function body + reconciliation, not repaint the screen.

What triggers a render?

A component re-renders when:

  1. Its state changes (useState, useReducer).
  2. Its parent re-renders (by default, children re-render too).
  3. A context it consumes changes.

That second point surprises people. By default, when a parent renders, all of its children render, regardless of whether their props changed.

function Parent() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount((c) => c + 1)}>{count}</button>
      <ExpensiveList /> {/* re-renders on every click — even with no props */}
    </div>
  );
}

The three levers

When a re-render is genuinely expensive, you have three tools:

  • React.memo — skip a child’s render if its props are shallow-equal.
  • useMemo — cache an expensive computed value between renders.
  • useCallback — keep a function identity stable so memo children don’t break.

Reach for these only after you’ve measured a real cost. Premature memoization adds complexity and can even be slower than the render it’s avoiding.

Composition beats memoization

Often the cleanest fix isn’t memo at all — it’s moving state down or passing children as props so they don’t re-render:

function Parent({ children }: { children: React.ReactNode }) {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount((c) => c + 1)}>{count}</button>
      {children} {/* created by the grandparent — does NOT re-render here */}
    </div>
  );
}

Internalize this: state changes render a component and its subtree; props identity decides whether memo’d children opt out. With that model, React performance stops being mysterious.

#react #performance #frontend #hooks