bashleigh
bashleigh

Reputation: 9314

React render array of components

Quick question. Anyone know how to render an array of components? Trying to make it easier for a developer to alter a particular component. (It's like a dashboard).

Component list file

import React from 'react';
export default [
    <ComponentOne/>
    <ComponentTwo/>
];

Dashboard Component

import React from 'react';

import components from './../../components';

export default class Dashboard extends React.Component 
{
    render = () => {
        //Want to render the array of components here.
        return (
            <div className="tile is-parent">
                {components}
            </div>
        );
    };
}

The issue is I have an array of components that I need to add a key to. However! I can't seem to add a key to the component as well, not sure how to explain it really so here's the code I've tried:

{components.map((component, key) => (
    <component key={key}/>
}

If I do the above I get no 'you must apply a key' errors however nothing renders? And I'm guessing it's because 'component' doesn't exist or something weird along those lines.

I've also tried component.key = key; but it doesn't let me do that on this type of Object apparently?

My fallback I suppose is to return a shorthand function instead of an array but I like the array for some reason? Seems simpler for juniors.

Upvotes: 44

Views: 99392

Answers (10)

Anjan Biswas
Anjan Biswas

Reputation: 7912

Just to expand a bit more to the accepted answer. If you have an array of elements elementArray and you would like to pass props to it like callbacks etc. you would do something like this-

To create the component array somewhere-

elementArray.push(<MyComponent/>);

And then in render-

<div>
{
    elementArray.map((element,i) => {
       React.cloneElement(element, { key: i,  onClick: () => myOnclick(i)})
    })
}
</div>

The second argument to React.cloneElement is an object of all the props that you would pass to the component at the time of render.

Upvotes: 1

Dustin
Dustin

Reputation: 376

All of these answers are almost right. Just remove the <../> from your exports:

export default [
    ComponentOne,
    ComponentTwo,
]

And in the other file use .map():

export default class Dashboard extends React.Component {
    render = () => (
        <div className="tile is-parent">
            {components.map((Component, key) => (<Component key={key} />))}
        </div>
    )
}

Also note that if you wanted to use Fragment like others suggested you can just write <>...</> instead.

Upvotes: 4

Forth
Forth

Reputation: 170

You can also do like that :

{components.map(component => component)}

It mappes your components to display them one by one.

Upvotes: 2

Matt Wills
Matt Wills

Reputation: 726

If you’re always going to want to render all the components in your components file then you’re probably better off wrapping them in a React.Fragments tag.

Best practise is just to export this as a simple function that returns the components rather than as a constant.

So...

const Components = props => {
  return (
    <React.Fragment>

      <ComponentOne/>
      <ComponentTwo/>

    </React.Fragment>
  )
}

export default Components

That allows you to put multiple components next to each other without a DOM element containing them.

You should then just be able to render that by using it as a normal component and it’ll render all of them, so just import it then...

<Components />

Otherwise, if you want to treat them like an array, you have a function for free on the React object you’ve imported...

React.Children.toArray(arrayOfComponents)

You pass it an array of components (like in your original question) and it allows you to sort and slice it if you need to then you should be able to just drop it in the return of your render function

Upvotes: 15

congdc
congdc

Reputation: 65

If I put components inside an array, I usually do it like this:

const COMPONENTS = [
  <Foo />,
  <Bar />
]

[0, 1].map(item => {
      return (
        <React.Fragment key={item}>
          {COMPONENTS[item]}
        </React.Fragment>
      )
})

Upvotes: 0

Muho
Muho

Reputation: 3536

if you are using react version above 16 you can create your list of component like that:

import React from 'react';

export const componentList = [ ComponentOne, ComponentTwo ];

and render them anywhere like that:

import React from 'react';

function SomeComponent() {
  return (
    <div>
      {componentList.map((component, index) => (
        <div key={index}> {component && component.render()}</div>
      ))}
    </div>
  );
}

Upvotes: 0

codejockie
codejockie

Reputation: 10844

Following up with my comment, you should be doing this instead:

{components.map((component, index) => (
    <span key={index}>
        { component }
    </span>
}

With React 16, you can use React.Fragment:

{components.map((component, index) => (
    <React.Fragment key={index}>
        { component }
    </React.Fragment>
}

Upvotes: 10

Prakash Sharma
Prakash Sharma

Reputation: 16472

Actually you are exporting array of elements from the file. One way is to export array of component and render them like

import React from 'react';
export default [
    ComponentOne
    ComponentTwo
];
// Then following will work
{components.map((Component, key) => (
    // Remember to make first letter capital (in this case "c")
    <Component key={key}/>
}

The other way is to wrap the component in div like this

import React from 'react';
export default [
    <ComponentOne/>
    <ComponentTwo/>
];
// Then wrap in div
{components.map((component, key) => (
    <div key={key}>
      {component}
    </div>
}

Upvotes: 1

The Reason
The Reason

Reputation: 7973

It's pretty easy, just wrap your component into div and pass key there as i did below:

const Example = ({components}) => (
  <div>
    {components.map((component, i) => <div key={i}>{component}</div>)}    
  </div>
)

Worked example

Upvotes: 1

Simon Boudrias
Simon Boudrias

Reputation: 44589

Have you consider using the new React Fragments? (in v16)

This would be the simplest solution as it would by pass the whole array/key issue.

If you need to pass key, then I'd suggest to simply require the components to have the keys. This is how React works, so I wouldn't suggest you to hide this behavior behind an interface that might not be predictable.

If you really need to do this, then you can use React.cloneElement to clone the element and inject new properties:

React.cloneElement(element, { key: 'foo' });

Upvotes: 21

Related Questions