Check out "Do you speak JavaScript?" - my latest video course on advanced JavaScript.
Language APIs, Popular Concepts, Design Patterns, Advanced Techniques In the Browser

Children in JSX

(This article is part of React in patterns series.)

React is highly composable. And the API that enables that is props.children. It gives us the power to create a placeholder that is later filled with content from the outside.

Hiding complex formatting/styling

Let’s say that we have a title that needs some special formatting. For example:

<h1 className='lots-of-styles-here'>
  <strong>
    <i className='something-else'>Hello world</i>
  </strong>
</h1>

We may create a Title component that hides the h1, strong and i tags. And all we have to pass to it is the text Hello world:

function Title(props) {
  return (
    <h1 className='lots-of-styles-here'>
      <strong>
        <i className='something-else'>
          { props.children }
        </i>
      </strong>
    </h1>
  );
}

<Title>Hello world</Title>

That’s a nice way to encapsulate UI elements. Notice how simple is to manage the title. We don’t have to deal with all those HTML tags.

Composition

The same technique may be used for composing components. The same way as we passed Hello world we may pass another React component. Let’s say that we have a header component but we need different content inside:

function Header(props) {
  return (
    <header className='app-header'>
      { props.children }
    </header>
  );
}
function Navigation() {
  return <nav>Navigation</nav>;
}
function SearchBar() {
  return <div>Search bar</div>;
}

<Header><Navigation /></Header>
or
<Header><SearchBar /></Header>

Header component doesn’t know about its content. It does only one job and that’s rendering a header tag with specific CSS class. This approach is perfect for building UIs because we divide our application into small autonomous chunks which are easy to understand and test.

Using JSX expression

So far props.children was string literal and React component. It is interesting that we may pass a JSX expression too.

function UserName(props) {
  return (
      <div>
      <b>{ props.children.lastName }</b>,
      { props.children.firstName }
    </div>
  );
}

function App() {
  var user = {
    firstName: 'Krasimir',
    lastName: 'Tsonev'
  };
    return (
    <UserName>{ user }</UserName>
  );
}

This may look weird but may be useful in some cases. Like for example when we have some knowledge in the parent component and don’t necessary want to send it down the tree.

The example below prints a list of TODOs. The App component has all the data and knows how to determine whether a TODO is completed or not. The TodoList component simply encapsulate the needed HTML markup.

function TodoList(props) {
  const renderTodo = (todo, i) => {
    return (
      <li key={ i }>
        { props.children(todo) }
      </li>
    );
  }
  return (
    <section className='main-section'>
      <ul className='todo-list'>{ props.todos.map(renderTodo)}</ul>
    </section>
  );
}

function App() {
  const todos = [
    { label: 'Write tests', status: 'done' },
    { label: 'Sent report', status: 'progress' },
    { label: 'Answer emails', status: 'done' }
  ];
  var isCompleted = todo => todo.status === 'done';

  return (
    <TodoList todos={ todos }>
      { todo => isCompleted(todo) ? <b>{ todo.label }</b> : todo.label }
    </TodoList>
  );
}

Notice how the App component doesn’t expose the structure of the data. TodoList has no idea that there is label or status properties.

When I first saw that pattern I was thinking “How’s that different by passing an additional prop?”. For example:

<TodoList
  todos={ todos }
  renderTodo={
    todo => isCompleted(todo) ? <b>{ todo.label }</b> : todo.label
  } />

The truth is that I kind of like the syntax with `props.children`. It becomes obvious that the expression is used for rendering the children of the component. From another point of view having an explicit method like renderTodo makes it even clearer. So, I guess it is more or less a personal feeling.

If you enjoy this post, share it on Twitter, Facebook or LinkedIn.