React anti-pattern: don’t make <li> your component’s root

Noticed a React anti-pattern while mentoring a younger front-end developer on our project.

Imagine you have a component that represents a list of articles. At one point, you realize that each article in the list is too complex:

const ArticleList = (props) => {
  return <div class="articles">
    <ul class="article-list">
      { props.articles.map(article => 
        <li class="article">
          <h2 class="article__title">{ article.title }</h2>
          { /* 25 other tags */ }
        </li>
      ) }
    </ul>
    { /* ... */ }
  </div>;
}

So you decide to move the item to a separate component. You take that code inside map(), extract it into <Article> and get something like this:

const Article = (props) => {
  return <li class="article">
    <h2 class="article__title">{ props.title }</h2>
    { /* 25 other tags */ }
  </li>;
}

const ArticleList = (props) => {
  return <div class="articles">
    <ul class="article-list">
      { props.articles.map(article => 
        <Article title={article.title} { /* other props */ } />
      ) }
    </ul>
    { /* ... */ }
  </div>;
}

Don’t do it this way. This approach is wrong. The problem is that by taking a <li> and making it a root of the component, you’ve just made your component non-reusable. This means that if you’d like to reuse <Article> in another place, you’ll only be able to apply it somewhere inside a list – because of this <li>. If you decide to render <Article> into e.g. a <div>, not only will this be non-semantic (<li> can only appear inside of <ul>s), but will also add unnecessary list item styling which is super weird.

Solution#

The solution is simple: move the <li> back into the <ArticleList> component and make the <Article>’s root element a <div> or something else. This will probably require some refactoring in your styles, but will make the component reusable. Look how cool:

const Article = (props) => {
  // Notice: <li>s are gone
  return <div class="article">
    <h2 class="article__title">{ props.title }</h2>
    { /* ... */ }
  </div>;
}

const ArticleList = (props) => {
  return <div class="articles">
    <ul class="article-list">
      { props.articles.map(article => 
        <li class="article-list__item">
          <Article title={article.title} { /* other props */ } />
        </li>
      ) }
    </ul>
    { /* ... */ }
  </div>;
}

// And now you can easily render an article inside of a sidebar – have no idea why though
const Sidebar = (props) => {
  return <div class="sidebar">
    <div class="sidebar__article">
      <Article title="Look ma" { /* other props */ } />
    </div>
    { /* ... */ }
  </div>;
}

Author: Ivan Akulov

I'm a software engineer specializing in web performance, JavaScript, and React. I’m also a Google Developer Expert. I work at Framer.

One thought on “React anti-pattern: don’t make <li> your component’s root”

Comments are closed.