Skip to content

PATTERN Cited by 1 source

Conditional child state scoping

Intent

Move expensive / rarely-active state into conditionally-rendered child components so the state and its cost exist only when the state is active — not multiplied by every parent instance that could have the state.

Context

React idioms commonly push state into the component that might display it — e.g. a diff-line component owning "is there a comment on this line?" and "is the context menu open?" state, even though the vast majority of diff lines never have a comment and never have a menu open. At small scale this is clean. At hot-path scale (concepts/hot-path) — 10,000+ instances — the pattern's cost compounds:

  • Every instance pays state-management overhead, even the 99 % that never use it.
  • Re-renders cascade: state changes in one instance's rarely- used branch trigger unnecessary reconciliation passes on siblings.
  • Memory: per-instance hooks, closures, refs live as long as the parent renders.

This is the state-scope-aligned-with-likelihood failure. The SRP framing applies: the diff-line component's responsibility is rendering code, not managing the commenting UI that 99 % of lines never trigger.

Mechanism

  1. Identify state that is (a) expensive, (b) rarely active in any given instance. Typical candidates in web UIs: comment threads, context menus, hover popovers, inline edit forms.
  2. Extract that state — and the components using it — into a dedicated child component.
  3. Conditionally render the child only when the state is active:
    function DiffLine({ lineId, file }) {
      return (
        <div data-line={lineId}>
          <CodeRender lineId={lineId} />
          {hasComments(file, lineId) && <CommentThread lineId={lineId} />}
          {isMenuOpen(lineId) && <ContextMenu lineId={lineId} />}
        </div>
      );
    }
    
  4. Look up the activation condition via O(1) key-based access (patterns/constant-time-state-map) — checking a Map is cheap enough to run on every line without regressing the common case.

Canonical instance

GitHub's Files-changed tab v2 — "The most impactful change from v1 to v2 was moving app state for commenting and context menus into their respective components. Given GitHub's scale, where some pull requests exceed thousands of lines of code, it isn't practical for every line to carry complex commenting state when only a small subset of lines will ever have comments or menus open. By moving the commenting state into the nested components for each diff line, we ensured that the diff- line component's main responsibility is just rendering code — aligning more closely with the Single Responsibility Principle." (Source: sources/2026-04-03-github-the-uphill-climb-of-making-diff-lines-performant)

The activation check is implemented via O(1) Map lookup (patterns/constant-time-state-map): commentsMap['path'][L]. A line with no comments pays one map lookup per render; a line with comments pays the map lookup and the commenting component's real cost.

Anti-patterns

  • Scoping state into children you still render unconditionally. The pattern's power comes from not rendering the expensive child when the state is inactive. {hasComments(...) && <X/>} not <X active={hasComments(...)}>.
  • Over-scoping. If state changes frequently enough that the child gets repeatedly mounted/unmounted, the mount/unmount cost exceeds the saved render cost. This pattern is for rarely active state, not frequently toggling state.
  • Losing parent-level invariants. State scoped to a child that should survive parent re-renders needs lifted state (or state in an external store). The pattern doesn't apply to state the parent is supposed to coordinate.
  • Forgetting to look up activation cheaply. If the activation predicate is itself O(n), the saving is reintroduced at the lookup layer. Pair with patterns/constant-time-state-map.
Last updated · 200 distilled / 1,178 read