Most efficient way of rendering JSX elements when iterating on array of data in React
Asked Answered
L

8

18

I have an array which contains objects. I am creating a map of this array to renders the names with a span component.

let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];

I have been using the below two different functionalities to iterate on that array of objects, and using map to render JSX elements.

Functionality1:

import React, { Component } from 'react';
class App extends Component {

  render() {
    let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
    const items = data.map((key, i) => {
      return <span key={key.id}>{key.name}</span>;
    });
    return (
      <div>
        {items}
      </div>
    );
  }
}

export default App;

Functionality2:

import React, { Component } from 'react';
class App extends Component {

  render() {
    let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
    let rows = [];
    data.map((key, i) => {
      rows.push(<span key={key.id}>{key.name}</span>);
    });
    return (
      <div>
        {rows}
      </div>
    );
  }
}


export default App;

I known to the above two different ways of using map and rendering JSX elements. Is there any other ways of doing the same, apart from these two? If so, which is recommended?

Lentz answered 5/9, 2018 at 3:31 Comment(5)
"Functionality2" creates an Array when .map is called that is never used. Switch to data.forEach so no unnecessary Array is created.Laurinelaurita
but that is only when assigned to a reference right? like const array = data.map( correct me if I am wrongLentz
No, the Array is created regardless of whether you assign it to a variable. In your code above, the Array is created and then immediately eligible for garbage collection because no references point to it. If you switch to .forEach, no extra Array is created.Laurinelaurita
OK understood. Thanks. Can you also help me with other ways of rendering jsx elements when we render them in iteration. Apart from what I mentioned and others mentioned belowLentz
Put together a quick JsPerf comparison of your two approaches, as well as some variants. Functionnality1 is your best bet.Hidden
I
14

Mostly, I follow this rule:

Create a component which renders the items

// in some file
export const RenderItems = ({data}) => {
  return data && data.map((d, i) => <span key={d.id}>{d.name}</span>) || null
}

Hook the RenderItems

import { RenderItems } from 'some-file'

class App extends Component {
  render() {
    // you may also define data here instead of getting data from props
    const { data } = this.props
    return (
      <div>
        <RenderItems data={data} />
      </div>
    )
  }
}

Attach the data in the component

const data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}]
<App data={data} />

Following this rule will not impact on performance even with your second example of code ie. pushing items in an array and rendering the items. Because, you're not directly working inside the render hook. Always take care that render hook wouldn't implement any logic inside it directly.


Further, I wouldn't create id just for using key:

const data = [{"name": "Hi"}, {"name": "Hello"}]
//... and when using index as key
.map((d, i) => <span key={'item-'+i}>
// or,
.map((d, i) => <span key={'item-'+i+'-'+d.name}>

See this post why I follow this syntax while using index as key.


Update:

If you want to avoid unnecessary html tags being used, you can use React.Fragment

export const RenderItems = ({data}) => {
  return data && 
    data.map(
      (d, i) => <React.Fragment key={d.id}>{d.name}</React.Fragment>
    ) || null
}
// and when rendering, you just return the component
return <RenderItems data={data} />

Note:

  1. You can use <></> as an alias for <React.Fragment></React.Fragment> only if you don't have any additional property. Since we're using key property on it, not using it.
  2. Take a look at this to make support for short notation of React.Fragment.

Example using <></>:

<>{d.name}</>

This will be rendered d.name's value in html without any tag. This is considered best when we specifically transform our existing design to react application. Or, there might be other cases. Like, we are going to display a definition list:

<dl>
  <dt></dt>
  <dd></dd>
  <dt></dt>
  <dd></dd>
  <dt></dd>
</dl>

And we don't want to attach unnecessary html tag, then using Fragment will make our life easier:

Example:

<>
  <dt>{d.term}</dt>
  <dd>{d.definition}</dd>
</>

The most important case will be for rendering td element in tr (a TR component). If we don't, then we're breaking the rule of HTML. The component will not be rendered properly. In react, it will throw you an error.

Update2:

Also, if you have long list of props like below:

const {
  items,
  id,
  imdbID,
  title,
  poster,
  getMovieInfo,
  addToFavorites,
  isOpen,
  toggleModal,
  closeModal,
  modalData,
} = props

You may consider destructuring like:

const { items, ...other } = props
// and in your component you can use like:
<div modalData={other.modalData}>

But, personally I prefer using first example code. It's because while developing I won't need to look back to other component or look for the console each and every time. In the given example there's key like modalData={} so we easily maintain modalData={other.modalData}. But what if it is needed to code like <div>{modalData}</div>? Then, you may also agree with my preference.

Inconsistency answered 7/9, 2018 at 15:41 Comment(4)
Thank you. I never tried like this before seeing it for the first time.Lentz
Glad to help you.Inconsistency
An index is appropriate to be used as a key here but is considered bad practice in general. The answer doesn't mention that.Revert
@estus when used with some constant value it's not considered bad.Inconsistency
H
12

I would do this

const data = [{id: 1, name: 'a'}, {id: 2, name: 'b'}];

export default class App extends PureComponent {
  render() {
    return (
      <div>
        {data.map(({ id, name }) => <span key={id}>{name}</span>)}
      </div>
    );
  }
}

Now, your data is not reinstantiated on every render, and you don't have to garbage collect any unnecessary variable declarations.

Habitancy answered 5/9, 2018 at 4:6 Comment(1)
Upvoted. I would add that if you want to avoid wrapping the <span> elements in a <div> you can use React Fragments: reactjs.org/docs/fragments.htmlMercia
C
8

The first way is better.

  1. Array.prototype.map creates an array behind the scenes and returns it after applying the modification on each element. Functionality-1 creates two arrays, while Functionality-2 creates three.

  2. Functionality-1 reads better. It's how React code usually being written. For a simple element like this, I'd save the const definition for items and put the map statement in the JSX to be returned directly.

Coinage answered 5/9, 2018 at 3:51 Comment(0)
R
6

Generally, for or while statement is the most efficient way to iterate an array. The way a small array is processed in non-critical place can be considered microoptimisation.

The use of map is idiomatic in React components because it's fast enough and can return a value as a part of an expression.

While this is an antipattern:

let rows = [];
data.map((key, i) => {
  rows.push(<span key={key.id}>{key.name}</span>);
});

map is supposed to map array elements to other values (hence the name), not to iterate an array instead of forEach or other loop. This problem can be tracked with ESLint array-callback-return rule.

The component is stateless and doesn't need to be Component class. It can be functional component or PureComponent class. Since data is constant, it doesn't need to be assigned on each render:

const data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];

const App = props => <div>
  {data.map(({ id, name }) => <span key={id}>{name}</span>)}
</div>;
Revert answered 8/9, 2018 at 2:15 Comment(2)
Thank you. What if I want to generate jsx elements with event handler functions in iteration. Can I still use stateless component for that?Lentz
@Think-Twice Possibly yes, depending on what these handlers should do. Can you update the question with another example that shows that?Revert
M
2

the first method is correct. use the map function to iterate through the array.

export default class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
          data: [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
        };
 }
    render(){
        return(
            <div>           
                {this.state.data.map((data,index)=>
                      <span key={data.id}>{data.name}</span>
                )}             
        );
    }
}
Mota answered 11/9, 2018 at 7:22 Comment(0)
P
2

I'd go with the map inside the return(...) as map returns an array. It's cleaner and readable, something I personally strive for.

To add on to the answer, if the array data might change down the lane I'd go with creating a new stateless dump component:

const Spanner = props => { return <span key={ props.data.id }>{ props.data.name }</span> };

class App extends Component {
  render() {
    let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
      return (
        <div>
          { 
            data.map( (item, ind) => {
              return (<Spanner key={item.id} data={item.name} />);
            })
          }
        </div>
      );
    }
  }
}

This way we can use this Spanner Component as a common component shared among different component. And in case the data changes over time ( which most of the time, does) you can write a wrapper function to the Spanner component and call the new function wherever required. data=[{"id": "01", "name": "Hi", "userType": "Admin"}, {"id": "02", "name": "Hello", "userType": "Client"}];

const UserWidget = (props) => {
  return (
    <div>
      <h4>{ props.type }</h4>
      <Spanner key={ props.key } data={ props.name }/>
    </div>
  );
}
// now both the UserWidget and the Spanner Components can be used wherever required
// say a dashboard Component wants to use the UserWidget
class Dashboard extends Component {
  render() {
    let data = [{"id": "01", "name": "Hi", "userType": "Admin"}, {"id": "02", "name": "Hello", "userType": "Client"}];
      return (
        <div>
          { 
            data.map( (item, ind) => {
              return (<UserWidget type={ item.userType } key={item.id} data={item.name} />);
            })
          }
        </div>
      );
    }
  }
}

This way you're not repeating yourself, and your App Component still has the expected behavior even if now the new data array has the new key userType. Again this is how I'd do it.

Pallas answered 12/9, 2018 at 5:44 Comment(0)
F
1

You may use this for best understanding

 const data = [{id: 1, name: 'a'}, {id: 2, name: 'b'}];

export default class App extends React.Component {
  render() {
    return (
      <div>
        {data.map((data, dataIndex ) => <span key={dataIndex}>{data.name}</span>)}
      </div>
    );
  }
}

Here you can understand that the name belongs to with attribute.

Fielder answered 7/9, 2018 at 10:0 Comment(1)
index provided by map-method is not recommended to use as element key-value.Sierrasiesser
O
1

The function toSpan can be reused.

class App extends React.Component {
  render() {
    const data = [{id: `01`, name: `Hi`}, {id: `02`, name: `Hello`}]
    const toSpan = ({id, name}) => <span key={id}>{name}</span>
    return (
      <div>{data.map(toSpan)}</div>
    )
  }
}
Oilstone answered 12/9, 2018 at 16:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.