Home / Articles / React speed starts with component tree organization

This article is published in English.

React speed starts with component tree organization

Re-renders are not the enemy—wide state propagation is. Move sources leafward before micro-memoizing.

1699 words

React does what the tree design asks for. When an app feels slow, the framework is often obeying a wide state/update propagation path—not failing mysteriously.

Re-rendering is not the enemy

Renders are how UI stays consistent. The enemy is unnecessary work across large subtrees on every keystroke.

// ❌ count lives in Parent — expensive siblings re-render on every click
function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <ExpensiveComponent />
      <AnotherExpensiveComponent />
    </div>
  );
}
// ✅ count lives in Counter — expensive siblings are untouched
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

function Parent() {
  return (
    <div>
      <Counter />
      <ExpensiveComponent />
      <AnotherExpensiveComponent />
    </div>
  );
}
// ❌ SearchLayout owns state — ExpensiveList re-renders on every keystroke
function SearchLayout() {
  const [query, setQuery] = useState('');

  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <ExpensiveList />
    </div>
  );
}
/* ✅ StatefulInput receives expensive content as children
 — its reference stays stable */
function StatefulInput({ children }) {
  const [query, setQuery] = useState('');
  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      {children}
    </div>
  );
}

function SearchLayout() {
  return (
    <StatefulInput>
      <ExpensiveList />
    </StatefulInput>
  );
}

Where the propagation should stop

Moving the source

Place state beside the components that write and read it. A form’s draft text need not live at the application root.

Blocking the path

Children that accept stable props can bail out. Composition and children-as-slots often beat blanket memo.

The tree as a design decision

Organization is an architecture choice: feature folders, container/presentational splits, and local providers bound update blast radius.

Starting from the tree

Before reaching for micro-optimizations, sketch which state belongs where and which subtrees must remain calm during frequent updates. Most “React is slow” stories are tree stories.

Profile, then reorganize; memoize last.

Context providers are part of the tree: a mega-context that changes every render undoes leaf memo. Split contexts by update frequency.

Route-level code splitting shrinks the tree you pay for on first paint; combine with local state to keep navigations light.

List virtualization is tree surgery—unmount offscreen rows instead of reconciling thousands of dormant nodes.

Document ownership: which team owns which subtree state. Shared global stores recreate the wide-propagation problem under a different name.

Use React Profiler’s commit timings before and after a state move; keep the diff in the PR for review.

Server components and client islands change where the tree lives, but propagation discipline still applies on the client island.

Context providers are part of the tree: a mega-context that changes every render undoes leaf memo. Split contexts by update frequency.

Route-level code splitting shrinks the tree you pay for on first paint; combine with local state to keep navigations light.

List virtualization is tree surgery—unmount offscreen rows instead of reconciling thousands of dormant nodes.

Document ownership: which team owns which subtree state. Shared global stores recreate the wide-propagation problem under a different name.

Use React Profiler’s commit timings before and after a state move; keep the diff in the PR for review.

Server components and client islands change where the tree lives, but propagation discipline still applies on the client island.

Context providers are part of the tree: a mega-context that changes every render undoes leaf memo. Split contexts by update frequency.

Route-level code splitting shrinks the tree you pay for on first paint; combine with local state to keep navigations light.

List virtualization is tree surgery—unmount offscreen rows instead of reconciling thousands of dormant nodes.

Document ownership: which team owns which subtree state. Shared global stores recreate the wide-propagation problem under a different name.

Use React Profiler’s commit timings before and after a state move; keep the diff in the PR for review.

Server components and client islands change where the tree lives, but propagation discipline still applies on the client island.

Context providers are part of the tree: a mega-context that changes every render undoes leaf memo. Split contexts by update frequency.

Route-level code splitting shrinks the tree you pay for on first paint; combine with local state to keep navigations light.

List virtualization is tree surgery—unmount offscreen rows instead of reconciling thousands of dormant nodes.

Document ownership: which team owns which subtree state. Shared global stores recreate the wide-propagation problem under a different name.

Use React Profiler’s commit timings before and after a state move; keep the diff in the PR for review.

Server components and client islands change where the tree lives, but propagation discipline still applies on the client island.

Context providers are part of the tree: a mega-context that changes every render undoes leaf memo. Split contexts by update frequency.

Route-level code splitting shrinks the tree you pay for on first paint; combine with local state to keep navigations light.

List virtualization is tree surgery—unmount offscreen rows instead of reconciling thousands of dormant nodes.

Document ownership: which team owns which subtree state. Shared global stores recreate the wide-propagation problem under a different name.

Use React Profiler’s commit timings before and after a state move; keep the diff in the PR for review.

Server components and client islands change where the tree lives, but propagation discipline still applies on the client island.

Context providers are part of the tree: a mega-context that changes every render undoes leaf memo. Split contexts by update frequency.

Route-level code splitting shrinks the tree you pay for on first paint; combine with local state to keep navigations light.

List virtualization is tree surgery—unmount offscreen rows instead of reconciling thousands of dormant nodes.

Document ownership: which team owns which subtree state. Shared global stores recreate the wide-propagation problem under a different name.

Use React Profiler’s commit timings before and after a state move; keep the diff in the PR for review.

Server components and client islands change where the tree lives, but propagation discipline still applies on the client island.

Context providers are part of the tree: a mega-context that changes every render undoes leaf memo. Split contexts by update frequency.

Route-level code splitting shrinks the tree you pay for on first paint; combine with local state to keep navigations light.

List virtualization is tree surgery—unmount offscreen rows instead of reconciling thousands of dormant nodes.

Document ownership: which team owns which subtree state. Shared global stores recreate the wide-propagation problem under a different name.

Use React Profiler’s commit timings before and after a state move; keep the diff in the PR for review.

Server components and client islands change where the tree lives, but propagation discipline still applies on the client island.

Context providers are part of the tree: a mega-context that changes every render undoes leaf memo. Split contexts by update frequency.

Route-level code splitting shrinks the tree you pay for on first paint; combine with local state to keep navigations light.

List virtualization is tree surgery—unmount offscreen rows instead of reconciling thousands of dormant nodes.

Document ownership: which team owns which subtree state. Shared global stores recreate the wide-propagation problem under a different name.

Use React Profiler’s commit timings before and after a state move; keep the diff in the PR for review.

Server components and client islands change where the tree lives, but propagation discipline still applies on the client island.

Context providers are part of the tree: a mega-context that changes every render undoes leaf memo. Split contexts by update frequency.

Route-level code splitting shrinks the tree you pay for on first paint; combine with local state to keep navigations light.

Providers that update on every keystroke should wrap only the consumers that need that frequency—not the entire page shell.

Derived data belongs in selectors or memoized children, not mirrored into sibling state that fans out more renders.

Visualize the tree: draw boxes for state owners and arrows for props. Wide fans from the root are the usual smell.

Concurrent features help urgency, but they cannot fix a god-component that owns every field of a complex wizard.

Migration tactic: move one state island leafward per PR with a profiler before/after attachment.

Providers that update on every keystroke should wrap only the consumers that need that frequency—not the entire page shell.

Derived data belongs in selectors or memoized children, not mirrored into sibling state that fans out more renders.

Visualize the tree: draw boxes for state owners and arrows for props. Wide fans from the root are the usual smell.

Concurrent features help urgency, but they cannot fix a god-component that owns every field of a complex wizard.

Migration tactic: move one state island leafward per PR with a profiler before/after attachment.

Providers that update on every keystroke should wrap only the consumers that need that frequency—not the entire page shell.

Derived data belongs in selectors or memoized children, not mirrored into sibling state that fans out more renders.

Visualize the tree: draw boxes for state owners and arrows for props. Wide fans from the root are the usual smell.

Concurrent features help urgency, but they cannot fix a god-component that owns every field of a complex wizard.

Migration tactic: move one state island leafward per PR with a profiler before/after attachment.

Providers that update on every keystroke should wrap only the consumers that need that frequency—not the entire page shell.

Derived data belongs in selectors or memoized children, not mirrored into sibling state that fans out more renders.

Visualize the tree: draw boxes for state owners and arrows for props. Wide fans from the root are the usual smell.