Skip to main content

Understanding Your UI as a Tree

React models the UI as a tree — and it's actually two different trees depending on the question I'm asking: one about which components render inside which, and one about which files import which. They look similar but answer different problems.

The render tree — who renders whom​

Each node is a component (not an HTML tag), the root is whatever React renders first, and every edge is a parent rendering a child.

render-tree-shape.jsx
App
├─ FancyText
└─ InspirationGenerator
├─ FancyText
└─ Copyright

The thing that surprised me: this tree isn't fixed. Conditional rendering reshapes it every render. If a component returns <FancyText> in one state and <Color> in another, the tree literally has a different child depending on props/state at that moment. So "the render tree" is really a snapshot of one render, not a permanent structure.

What it's good for is seeing performance and data flow at a glance:

  • Top-level components (near the root) sit above everything else, so re-rendering them re-renders large chunks below — that's where most cost and complexity lives.
  • Leaf components (no children) sit at the bottom and tend to re-render the most often.

Knowing which is which is what makes render-performance debugging make sense later.

The module dependency tree — who imports whom​

Same tree shape, completely different nodes. Here each node is a module (a file), and each edge is an import. The root is the entry file.

dependency-tree-shape.jsx
App.js
├─ FancyText.js
├─ InspirationGenerator.js
│ └─ inspirations.js // a data file — not a component
└─ Copyright.js

Two differences from the render tree worth holding onto:

  • Non-component files show up here. Something like inspirations.js (just data) is a node in the dependency tree but never appears in the render tree — it renders nothing.
  • The parent can differ. Copyright.js might be imported by App.js (so it hangs off App in the dependency tree), yet render as a child of InspirationGenerator because it was passed down as children. Imports and rendering aren't the same relationship.

Why two trees​

Because they answer different questions. The render tree explains what's on screen and why it re-renders — runtime, component-shaped. The dependency tree explains what ends up in the bundle — build-time, file-shaped. Bundlers walk the dependency tree to decide what to ship, and a bloated tree means a bigger bundle and a slower first paint. Different tree, different problem.