Back to all notes

Children Pattern: Flexible Slots Without Extra Props

Pass UI through the children prop to keep parents flexible and children independent.

2 min read

I kept writing the same show/hide conditional in every parent that needed one. The children pattern is the way out: the component defines the structure, and the consumer passes the content through children instead of another prop.

It also keeps the parent from owning the child’s state, which is where a lot of unnecessary re-renders come from.

The conditional lives in the parent

What I want is to show or hide content based on a flag, with a wrapper generic enough to reuse. The direct approach puts the check in App:

import { useState } from "react";

function App() {
  const [showContent, setShowContent] = useState(true);
  const content = "This is the content";

  return (
    <div>
      <h1>Children Pattern</h1>
      <button onClick={() => setShowContent(!showContent)}>Toggle Content</button>
      {showContent ? <p>{content}</p> : null}
    </div>
  );
}

export default App;

Repeat that in a few parents and the toggling logic and structure get duplicated everywhere.

Move the check into a wrapper

The wrapper renders its children only when showContent is true:

import { useState } from "react";

function Wrapper({ children, showContent }) {
  if (!showContent) return null;
  return (
    <div>
      <p>{children}</p>
    </div>
  );
}

function App() {
  const [showContent, setShowContent] = useState(true);
  const content = "This is the content";

  return (
    <div>
      <h1>Children Pattern</h1>
      <button onClick={() => setShowContent(!showContent)}>
        Toggle Content
      </button>
      <Wrapper showContent={showContent}>{content}</Wrapper>
    </div>
  );
}

export default App;

It returns null early when the flag is false, and App just toggles the flag with the button. Any renderable value works as children, because the content is never coupled to the wrapper: a string, an element, a fragment, even a function, which is the next lesson.

I keep wrappers like this minimal. If one genuinely needs logic, document it so the contract stays clear. And prefer the early return; it reads better than burying the condition in JSX.