DEV Community

Franklin
Franklin

Posted on

Notes from the Pass: The Question Six Components Couldn't Answer

Also available in Español

The Claim Being Tested

Tuesday's essay argued that component architecture can organize source code without being able to replace the structure of the document it produces — and that the failure isn't dividing a page into components, it's dividing a single decision across components without checking whether any one of them was left holding enough information to make it. The example was a heading level, decided six components away from the page position that should have set it.

This Note runs that exact six-component chain, asks the same question at every layer, and marks precisely where the decision and the context it needed come apart. Then it puts them back together two different ways, without touching the number of components in the chain.

The Chain

Six components, composed the way they'd actually be used:

<DashboardLayout>
  <CardGroup>
    <Card title="Active Subscriptions">
      {/* subscription list */}
    </Card>
  </CardGroup>
</DashboardLayout>
Enter fullscreen mode Exit fullscreen mode

Each layer does one job:

function DashboardLayout({ children }) {
  return (
    <main>
      <h1>Dashboard</h1>
      {children}
    </main>
  );
}

function CardGroup({ children }) {
  return <div className="card-grid">{children}</div>;
}

function Card({ title, children }) {
  return (
    <div className="card">
      <CardHeader title={title} />
      <div className="card-body">{children}</div>
    </div>
  );
}

function CardHeader({ title }) {
  return (
    <div className="card-header">
      <Typography variant="subheading">{title}</Typography>
    </div>
  );
}

function Typography({ variant, children }) {
  return <Heading variant={variant}>{children}</Heading>;
}

function Heading({ variant, children }) {
  const Tag = variant === 'subheading' ? 'h6' : 'h2';
  return <Tag>{children}</Tag>;
}
Enter fullscreen mode Exit fullscreen mode

Rendered, this produces exactly what the essay described:

<main>
  <h1>Dashboard</h1>
  <div class="card-grid">
    <div class="card">
      <div class="card-header">
        <h6>Active Subscriptions</h6>
      </div>
      <div class="card-body">...</div>
    </div>
  </div>
</main>
Enter fullscreen mode Exit fullscreen mode

Six Questions, Same Answer

Ask every layer the same two questions. Does it know where this section sits in the page's outline? Does it know what HTML tag its heading will become?

DashboardLayout knows the outline. It renders the page's only <h1>, which means it also implicitly knows that whatever it renders as children belongs below that <h1> in the hierarchy. It doesn't know the tag — it never touches a heading itself, only hands off to whatever it's given.

CardGroup knows neither. It's a grid wrapper. It doesn't know it's inside a dashboard, and it has no opinion about headings at all.

Card knows neither. Border, padding, a CardHeader, a body. No heading logic lives here either.

CardHeader knows the string — "Active Subscriptions" — but not where the card holding it sits on the page. It doesn't decide a tag. It hands the string to Typography with a styling instruction: render this like a subheading.

Typography knows the styling instruction, not the outline, and doesn't decide a tag either. It exists so the design system has one place that says what "subheading" looks like. It forwards variant straight through.

Heading is where "subheading" actually becomes <h6>. This is the only layer in the entire chain where a real tag name gets chosen — and it's also the layer furthest from the page, the one with no way to know that "Active Subscriptions" will land six levels below a page title it has never seen.

Six layers, six identical answers to the second question, until the sixth one flips — at exactly the layer least equipped to answer it correctly.

Two Ways Back

Thread the level explicitly. Give every layer a level prop and pass it straight through, unchanged, from the top:

function DashboardLayout({ children }) {
  return (
    <main>
      <h1>Dashboard</h1>
      {children}
    </main>
  );
}

function CardGroup({ level, children }) {
  return <div className="card-grid">{children}</div>;
}

function Card({ level, title, children }) {
  return (
    <div className="card">
      <CardHeader level={level} title={title} />
      <div className="card-body">{children}</div>
    </div>
  );
}

function CardHeader({ level, title }) {
  return (
    <div className="card-header">
      <Typography level={level}>{title}</Typography>
    </div>
  );
}

function Typography({ level, children }) {
  return <Heading level={level}>{children}</Heading>;
}

function Heading({ level, children }) {
  const Tag = `h${level}`;
  return <Tag>{children}</Tag>;
}
Enter fullscreen mode Exit fullscreen mode
<DashboardLayout>
  <CardGroup level={2}>
    <Card level={2} title="Active Subscriptions">
      {/* subscription list */}
    </Card>
  </CardGroup>
</DashboardLayout>
Enter fullscreen mode Exit fullscreen mode

Visual styling is left out of this version to keep the level-threading visible — in practice it would travel the same path level does now, just under its own prop, separate from structure the way the essay argued it should be.

The cost of this fix: CardGroup, Card, CardHeader, and Typography all had to change, even though none of them ever had a stake in the decision. Add a seventh wrapper to the chain next quarter, and it has to remember to forward level too, or the chain breaks again in a new place.

Track it through context instead. Let the layers that never had a stake in the decision stay exactly as they were in "The Chain" — CardGroup, Card, CardHeader, and Typography are untouched, not simplified, not rewritten. Only two components change:

const HeadingLevelContext = createContext(1);

function DashboardLayout({ children }) {
  return (
    <main>
      <h1>Dashboard</h1>
      <HeadingLevelContext.Provider value={2}>
        {children}
      </HeadingLevelContext.Provider>
    </main>
  );
}

function Heading({ children }) {
  const level = useContext(HeadingLevelContext);
  const Tag = `h${level}`;
  return <Tag>{children}</Tag>;
}
Enter fullscreen mode Exit fullscreen mode

DashboardLayout establishes the level once. Heading reads it from context instead of guessing at it from a variant string. Nothing in between needs to know the context exists.

The cost here is different, not absent. Reading Heading in isolation no longer tells you what tag it renders — you have to know a provider exists somewhere above it in the tree. The first fix is easier to trace by reading props alone. The second one asks fewer components to participate in a decision they never owned.

Whether the Claim Held

The essay Why Component-Abstracted DOM Trees Become Harder to Read claim was that component architecture organizes source code and cannot, on its own, replace the structure of the document — and that the failure is a decision split across components, not a component that's broken. Six layers, walked one at a time, never produced a single layer holding both halves of the information the decision needed. DashboardLayout had the outline. Heading had the tag. Nothing in between had either.

Both fixes confirm the second half of the claim too. Neither one reduces six components to fewer. One threads the missing context through all of them. The other threads it through none but the two that actually needed it. Either way, the fix was never about how many components exist. It was about which one gets handed the information it was missing.

Top comments (0)