How to render react components by using map and join?

Reactjs

Reactjs 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.

Array.prototype.reduce()

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>
    )
  }

sample: https://jsbin.com/xomopahalo/edit?html,js,output

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>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionXiaohe DongView Question on Stackoverflow
Solution 1 - ReactjsMaarten ter HorstView Answer on Stackoverflow
Solution 2 - ReactjsdevsndView Answer on Stackoverflow
Solution 3 - ReactjsPithView Answer on Stackoverflow
Solution 4 - ReactjsCasimirView Answer on Stackoverflow
Solution 5 - ReactjsJstuffView Answer on Stackoverflow
Solution 6 - ReactjsoobgamView Answer on Stackoverflow
Solution 7 - ReactjsFen1kzView Answer on Stackoverflow
Solution 8 - ReactjsMarco AntônioView Answer on Stackoverflow
Solution 9 - ReactjsincView Answer on Stackoverflow
Solution 10 - Reactjsqin.juView Answer on Stackoverflow
Solution 11 - ReactjsFredric WaadelandView Answer on Stackoverflow
Solution 12 - ReactjsCalin ortanView Answer on Stackoverflow
Solution 13 - ReactjsLokasennaView Answer on Stackoverflow
Solution 14 - ReactjsilovettView Answer on Stackoverflow
Solution 15 - ReactjssFritsch09View Answer on Stackoverflow
Solution 16 - ReactjsΔO 'delta zero'View Answer on Stackoverflow