Multiple elements in React without a wrapper component

In React, each component can render only a single element. At the moment, there’s no way to return a multiple elements from a component’s render¬†function. However, if you need to render multiple components anywhere else, here are 2 solutions.

Returning Multiple Elements as an Array

It’s good practice¬†to use helper methods or functions to generate part of your component. It’s common to want to return multiple elements from your helper method without a wrapper. For example:

class Component extends React.Component {
  _renderInfo() {
    let author = ...
    let category = ...

    let info = [];
    if (author) {
      info.push(
        'By ',
        <a href={getAuthorUrl(author)}>{author}</a>
      );
    }

    if (author && category) {
      info.push(' | ');
    }

    if (category) {
      info.push(<a href={getCategoryUrl(category)}>{category}</a>);
    }

    return info;
  }

  render() {
    return (
      <div>
        <h3>{this.props.title}</h3>

        {this._renderInfo()}
      </div>

    );
  }
}
In the example above, the _renderInfo method conditionally displays some content’s author and category. It also adds a separator between the author and category if both of them would be displayed. However, this component would trigger a warning: Each child in an array or iterator should have a unique "key" prop. To fix this warning, we need to add a `key` to every element in the array. However, this would make the code a lot messier. A better solution is to wrap the return value of _renderInfo with a call to the following function:

function addKeys(arr) {
  return arr.map((obj, idx) => {
    if (obj instanceof Object && obj.hasOwnProperty('key') && obj.key === null) {
      return React.cloneElement(obj, {key: idx});
    }
    return obj;
  });
};
This automatically adds a key to each element in the array. Simply change return info; to return addKeys(info); and the warning will go away.

Multiple elements in an inline conditional

I often want to return multiple elements from a ternary conditional. For example:

render() {
  return (
    <div>
      {user ?
        <h2>{user.name}</h2>
        <p>{user.bio}</p>
      : isLoading ?
        <p>Loading...</p>
      :
        <h2>An error occurred</h2>
        <p>{error.message}</p>
      }
    </div>
  );
}
This would trigger a JSX syntax error: Adjacent JSX elements must be wrapped in an enclosing tag. To fix this, we could use the addKeys function above:

render() {
  return (
    <div>
      {user ?
        addKeys([
          <h2>{user.name}</h2>,
          <p>{user.bio}</p>
        ])
      : isLoading ?
        <p>Loading...</p>
      :
        addKeys([
          <h2>An error occurred</h2>,
          <p>{error.message}</p>
        ])
      }
    </div>
  );
}
However, I find the comma at the end of each line very ugly. It kind of defeats the purpose of JSX. Even if you modify addKeys to have rest parameters instead of having a single array as the parameter, you still need the commas. To remove the comma, we need a new function:

function unwrap(element) {
  return addKeys(element.props.children);
};
We would use this function like so:

render() {
  return (
    <div>
      {user ?
        unwrap(<wrap>
          <h2>{user.name}</h2>
          <p>{user.bio}</p>
        </wrap>)
      : isLoading ?
        <p>Loading...</p>
      :
        unwrap(<wrap>
          <h2>An error occurred</h2>
          <p>{error.message}</p>
        </wrap>)
      }
    </div>
  );
}
React treats any component whose name doesn’t start with a capital letter or contain a period as an HTML element. In this case, we don’t need to define wrap because React thinks wrap is an HTML element. After passing the wrap element to unwrap, unwrap returns the children of wrap without the element itself. We wrap the return value with addKeys to help React avoid rerenders. It won’t trigger a warning if we don’t call addKeys, but React would rerender each children every time the renders method runs. This technique also works with the return value of a method or function. You can return unwrap(<wrap></wrap>) from a helper render method instead of returning an array.

What’s next?

In the future, the React team may add better support for returning multiple elements. There has been discussions around adding a fragments API to React for over 2 years. However, I doubt we’re getting it any time soon. If you know of a better solution to tackle this problem, please let me know in the comments!

Leave a Reply

Your email address will not be published. Required fields are marked *