How to render react components by using map and join?
ReactjsReactjs Problem Overview
I have one component which is going to display Array of String. The code looks like this:
React.createClass({
render() {
<div>
this.props.data.map(t => <span>t</span>)
</div>
}
})
It is working perfectly fine.
i.e. if props.data = ['tom', 'jason', 'chris']
, the rendered result in the page would be tomjasonchris
.
Then, I want to join all the names by using comma, so I change the code to:
this.props.data.map(t => <span>t</span>).join(', ')
However, the rendered result is [Object], [Object], [Object]
.
I don't know how to interpret object to become react components to be rendered. Any suggestion?
Reactjs Solutions
Solution 1 - Reactjs
A simple solution is to use reduce()
without second argument and without spreading the previous result:
class List extends React.Component {
render() {
<div>
{this.props.data
.map(t => <span>{t}</span>)
.reduce((prev, curr) => [prev, ', ', curr])}
</div>
}
}
Without second argument, reduce()
will start at index 1 instead of 0, and React is perfectly happy with nested arrays.
As said in the comments, you want to only use this for arrays with at least one item, because reduce()
without second argument will throw with an empty array. Normally this should not be a problem, since you want to display a custom message saying something like 'this is empty' for empty arrays anyway.
Update for Typescript
You can use this in Typescript (without type-unsafe any
) with a React.ReactNode
type parameter on .map()
:
class List extends React.Component {
render() {
<div>
{this.props.data
.map<React.ReactNode>(t => <span>{t}</span>)
.reduce((prev, curr) => [prev, ', ', curr])}
</div>
}
}
Solution 2 - Reactjs
You can use reduce
to combine multiple elements of an array:
React.createClass({
render() {
<div>
this.props.data
.map(t => <span>t</span>)
.reduce((accu, elem) => {
return accu === null ? [elem] : [...accu, ',', elem]
}, null)
</div>
}
})
This initializes the accumulator with null, so we can wrap the first item in an array. For each following element in the array, we construct a new array that contains all previous elements using the ...-operator
, add the separator and then the next element.
Solution 3 - Reactjs
Update with React 16: It's now possible to render strings directly, so you can simplify your code by removing all the useless <span>
tags.
const list = ({ data }) => data.reduce((prev, curr) => [ prev, ', ', curr ]);
Solution 4 - Reactjs
If I just want to render a comma-separated array of components, I usually find reduce
too verbose. A shorter solution in such cases is
{arr.map((item, index) => (
<Fragment key={item.id}>
{index > 0 && ', '}
<Item {...item} />
</Fragment>
))}
{index > 0 && ', '}
will render a comma followed by a space in front of all array items except the first one.
If you want to separate the second-to-last item and the last one by something else, say the string ' and '
, you can replace {index > 0 && ', '}
with
{index > 0 && index !== arr.length - 1 && ', '}
{index === arr.length - 1 && ' and '}
Solution 5 - Reactjs
The accepted answer actually returns an array of arrays because prev
will be an array each time. React is smart enough to make this work, but is it is prone to causing problems in the future such as breaking Reacts diffing algorithm when giving keys to each result of map.
The new React.Fragment
feature lets us do this in an easy to understand manner without the underlying issues.
class List extends React.Component {
render() {
<div>
{this.props.data
.map((t, index) =>
<React.Fragment key={index}>
<span> {t}</span> ,
</React.Fragment>
)
</div>
}
}
With React.Fragment
we can simply place the separator ,
outside of the of the returned HTML and React won't complain.
Solution 6 - Reactjs
the <span>{t}</span>
you are returning is an object, not a string. Check the react docs about it https://facebook.github.io/react/docs/jsx-in-depth.html#the-transform
By using .join()
on the returned array from map
, you are joining the array of objects. [object Object], ...
You can put the comma inside the <span></span>
so it gets rendered the way you want it to.
render() {
return (
<div>
{ this.props.data.map(
(t,i) => <span>{t}{ this.props.data.length - 1 === i ? '' : ','} </span>
)
}
</div>
)
}
Solution 7 - Reactjs
My variant:
{this.props.data
.map(item => <span>{item}</span>)
.map((item, index) => [index > 0 && ', ', item ])}
Solution 8 - Reactjs
Using es6 you could do something like:
const joinComponents = (accumulator, current) => [
...accumulator,
accumulator.length ? ', ' : '',
current
]
And then run:
listComponents
.map(item => <span key={item.id}> {item.t} </span>)
.reduce(joinComponents, [])
Solution 9 - Reactjs
Use nested array to keep "," outside.
<div>
{this.props.data.map((element, index) => index == this.props.data.length - 1 ? <span key={index}>{element}</span> : [<span key={index}>{element}</span>, ", "])}
</div>
Optimize it by saving the data to array and modify last element instead of checking if its last element all the time.
let processedData = this.props.data.map((element, index) => [<span key={index}>{element}</span>, ", "])
processedData [this.props.data.length - 1].pop()
<div>
{processedData}
</div>
Solution 10 - Reactjs
This worked for me:
{data.map( ( item, i ) => {
return (
<span key={i}>{item.propery}</span>
)
} ).reduce( ( prev, curr ) => [ prev, ', ', curr ] )}
Solution 11 - Reactjs
As mentioned by Pith, React 16 allow you to use strings directly so wrapping the strings in span tags are no longer needed. Building on Maarten's answer, if you also want to deal with a custom message right away (and avoid throwing an error on empty array), you could lead the operation with a ternary if statement on the length property on the array. That could look something like this:
class List extends React.Component {
const { data } = this.props;
render() {
<div>
{data.length
? data.reduce((prev, curr) => [prev, ', ', curr])
: 'No data in the array'
}
</div>
}
}
Solution 12 - Reactjs
This should return a flat array. Handle s case with non iterable first object by providing an initial empty array and filters the not needed first comma from the outcome
[{}, 'b', 'c'].reduce((prev, curr) => [...prev, ', ', curr], []).splice(1) // => [{}, 'b', 'c']
Solution 13 - Reactjs
Am I the only who thinks there's a lot of needless spreading and nesting going on in the answers here? Points for being concise, sure, but it leaves the door open to issues of scale or React changing how they deal with nested arrays/Fragments.
const joinJsxArray = (arr, joinWith) => {
if (!arr || arr.length < 2) { return arr; }
const out = [arr[0]];
for (let i = 1; i < arr.length; i += 1) {
out.push(joinWith, arr[i]);
}
return out;
};
// render()
<div>
{joinJsxArray(this.props.data.map(t => <span>t</span>), ', ')}
</div>
One array, no nesting. No sexy method chaining either, but if you find yourself doing this often you can always add it to the array prototype or wrap it in a function that takes a mapping callback as well to do it all in one go.
Solution 14 - Reactjs
function YourComponent(props) {
const criteria = [];
if (something) {
criteria.push(<strong>{ something }</strong>);
}
// join the jsx elements with `, `
const elements = criteria.reduce((accu, elem) => {
return accu === null ? [elem] : [...accu, ', ', elem]
}, null);
// render in a jsx friendly way
return elements.map((el, index) => <React.Fragment key={ index }>{ el }</React.Fragment> );
}
Solution 15 - Reactjs
Simply add join() after map and put it inside tag:
<span>{this.props.data.map((item) => item).join(', ')}</span>
Solution 16 - Reactjs
As of React 17, you can simply:
<div>
{[1, 2, 3].map((e, i) => <p key={i}>{e}</p>)}
</div>
And it will render as:
<div>
<p>1</p>
<p>2</p>
<p>3</p>
</div>