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!
Nice article! Although you shouldn’t use a loop’s index as keys because it’s essentially just surpressing the warning.
For anyone else struggling with this issue: React 16.2.0 supports fragments: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html
In case anybody reads this article today.
Checkout
https://reactjs.org/docs/fragments.html
Now we have Fragments so you don’t have to do all that: https://reactjs.org/docs/fragments.html