TIL: You can include a react component inside itself
Tags: react, gatsby, todayilearned
Published: 2020-09-06 12:19:44


I recently converted the posts in one of my [Gatsby](https://www.gatsbyjs.com/) sites from regular markdown to [MDX](https://mdxjs.com/). During this process, I had to change the way I render the Table of Contents for each post. Instead of a block of HTML passed to the `dangerouslySetInnerHTML` function, I'm now working with an array of objects, each representing a level 2 heading:

```js
[
  {
    url: '#description',
    title: 'Description'
  },
  {
    url: '#layout',
    title: 'Layout'
  }
  // More headings here…
];
```

To render a list of these headings, we can loop through them using the `Array.map()` method:

```jsx
<ul>
  {items.map(item => (
    <li key={item.title}>
      <a href={item.url}>{item.title}</a>
    </li>
  ))}
</ul>
```

This works fine if we’re only rendering one level of heading, but what if we also wanted to show level 3 headings? Fortunately, the `tableOfContents` provided by [gatsby-plugin-mdx](https://www.gatsbyjs.org/packages/gatsby-plugin-mdx/) includes any child headings in a nested `items` object. e.g.

```js
{
  url: '#markup',
  title: 'Markup',
  items: [
    {
      url: '#approach-1-heading-with-button',
      title: 'Approach 1: Heading with Button'
    },
    {
      url: '#approach-2-summary-and-details',
      title: 'Approach 2: Summary and Details'
    }
  ]
}
```

How do we render these child headings? One answer is to nest the same logic again inside our existing loop:

```jsx
const TableOfContents = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={item.title}>
        <a href={item.url}>{item.title}</a>
        {/* Begin nested loop */}
        {item.items && item.items.length > 0 && (
          <ul>
            {items.map(item => (
              <li key={item.title}>
                <a href={item.url}>{item.title}</a>
              </li>
            ))}
          </ul>
        )}
      </li>
    ))}
  </ul>
);
```

Unfortunately, not only is this starting to get unwieldy and repetitive, it also has its limitations: what if we want to render three levels of heading: h2, h3 and h4s? This is a problem that can be solved using **recursion**. If we make a new `TableOfContentsItem` component for rendering our list items, we can give it the ability to call itself if it needs to render any children:

```jsx
const TableOfContentsItem = ({ item }) => {
  const nestedItems = (item.items || []).map(nestedItem => {
    return <TableOfContentsItem item={nestedItem} key={nestedItem.title} />;
  });

  return (
    <li key={item.title}>
      <a href={item.url}>{item.title}</a>
      {nestedItems.length > 0 && <ul>{nestedItems}</ul>}
    </li>
  );
};
```

Note how, when it needs to, the `TableOfContentsItem` loops through any child items, rendering each one as another `TableOfContentsItem`. Our `TableOfContents` component now only needs to include it once, inside the outer-most `ul`:

```jsx
const TableOfContents = ({ items }) => (
  <ul>
    {items.map(item => (
      <TableOfContentsItem item={item} key={item.title} />
    ))}
  </ul>
);
```

And that’s it: we can now handle any level of nesting. This technique comes in handy for all sorts of complex navigation and it’s not only for React: the same pattern can be achieved using template macros in languages such as [Nunjucks](https://mozilla.github.io/nunjucks/) or [Twig](https://twig.symfony.com/).

<small>Photo by [Iza Gawrych](https://unsplash.com/@ilmatar) on [Unsplash](https://unsplash.com/s/photos/matryoshka)</small>