React render array of components - arrays

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.

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

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

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

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.

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

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

You can also do like that :
{components.map(component => component)}
It mappes your components to display them one by one.

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.

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

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

Related

How to pass in props to an element that changes in size depending on prop

I am having a hard time wrapping my head around props. I'd like to use a card shape in different components, and it be resized depending on which component it's in. For instance, if it's in a component at the bottom of the page, it will be big-sized; if in a component at the top of the page, then small-sized. I hope this makes sense. I just have the following code:
import React from 'react'
const Card = () => {
return (
<div className='card'>
</div>
)
}
export default Card;
I don't know where to put the props and how I would go about to pass them in, I really need it dumbed down for me, maybe an example. Any help would be appreciated.
on parent component pass your props
<Card ComponentA={true) ComponentB={false}/>
or simply
<Card ComponentA/>
on Card component
const Card = ({ComponentA}) => {
return (
<div className={ComponentA ? "big-card" : "small-card"}>
</div>
)
}
you can also use props this way if you prefer
const Card = (props) => {
return (
<div className={props.ComponentA ? "big-card" : "small-card"}>
</div>
)
}

Sending a React.FunctionComponent<React.SVGProps<SVGSVGElement>> as a prop to another component

I'm attempting to import a React functionComponent from an SVG and then send that to another component as a prop to render that svg. With the setup below, this compiles fine, but eventually crashes when trying to render the svg in browser with:
Error: Objects are not valid as a React child (found: object with keys {$$typeof, render}). If you meant to render a collection of children, use an array instead.
Classes below are simplified. But the gist of what I'm trying to do is:
In overlay.tsx:
import { ReactComponent as icon } from "/icon.svg";
import CustomItem from "/customItem";
const Overlay: React.FC<OverlayProps> = () => {
return (
<div>
<CustomItem icon={icon}/>
</div>
);
export default Overlay;
}
and in customItem.tsx:
import React from "react";
export interface CustomItemProps {
icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = ({icon}) => {
return (
<div>
{icon}
</div>
);
};
export default ApplicationsDropdownItem;
I assume my problem is somewhere around the syntax of {icon}, but I can not for the life of me find out what I'm suppose to use instead.
Answer
The icon you are importing is a component, therefore it must be called to render the JSX.
<Icon {...props}/> (correct) or {Icon(props)} (not recomended)
Since it is a component, you should also name it Icon and not icon.
Take a look at this blog post that explains SVGR.
TL;DR - Best approach for rendering components
A. Call the component in your render method with component syntax <MyComponent/> not MyComponent().
B. Instantiate your component as a variable, and pass that to your render method's JSX block.
More info
#DustInCompetent brought to light the issue of calling a component as a function inside a JSX block.
As explained here and here, that will lead to react not registering a components hooks and lead to state and other problems.
If you are implementing a High Level Component (HOC), then you should not call a component within the render method (return statement in functional components), as this leads to problems for similar registration issues of the component.
import React from "react";
import { ReactComponent as SampleIcon } from "/sample_icon.svg";
export interface CustomItemProps {
Icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = (props) => {
const Temp = props.Icon as React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
return (
<div>
<Temp/>
</div>
);
};
<CustomItem Icon={SampleIcon}/>
I think you should use <Icon /> instead of {icon} because it's a component.

TypeError: Cannot read property 'map' of undefined Reactjs

Newbie to React and I need help again - everything was fine untill i shifted the code from App.js to separate component - and b/c it is the stateless component, i am using props and map function to access the value and state from App.js but it is not happy -- help please
import React from 'react';
const Recipes = props => (
<div>
{props.recipes.map(recipe => (
<div key={recipe.recipe_id}>
<img src={recipe.image_url} alt={recipe.title} />
<p>{recipe.title}</p>
</div>
))}
</div>
);
export default Recipes;
This just means that you don't pass in recipes properly as a prop where you render <Recipes />. Either recipes is null or incorrectly formatted.
ex:
// App.js
import React from 'react';
const App = () => {
const recipes = [{
recipe_id: '<id>',
image_url: '<some url>',
title: 'Lé title'
}];
// recipes could be null/undefined or not even passed as a prop
return (
<Recipes recipes={recipes} />
);
}
export default App;
So it's hard to know exactly what's happening without being able to see how you are passing down props, and exactly what data they contain. The error you are getting implies that you aren't actually sending the recipes array correctly.
I honestly never use stateless functions in react anymore, because PureComponent generally preforms better because of it's built in shouldComponentUpdate which prevents unnecessary re-renders. So here's how I would write that component:
import React, { PureComponent } from 'react';
class Recipes extends PureComonent {
recipeList = () => {
const recipes = this.props;
const recipeArray = recipes.map((recipe) => {
<div key={recipe.recipe_id}>
<img src={recipe.image_url} alt={recipe.title} />
<p>{recipe.title}</p>
</div>
});
return recipeArray;
}
render () {
return () {
<div>
{this.recipeList()}
</div>
}
}
}
export default Recipes;
That being said, my guess about the way you wrote your component is that if you were to console out props you would find that it was actually equal to recipes, which is why recipes.recipes is undefined.

Adding additional props to a component passed as a prop

I am using React Table along with React Custom Scrollbars in a react-redux application. To connect these two I need to override the TbodyComponent in react table such that I can wrap the default tbodycomponent with the scrollbars and pass additional props to tweak rendering. Here's some stripped down code:
import React from 'react'
import ReactTable from 'react-table'
import {ReactTableDefaults} from 'react-table'
import { Scrollbars } from 'react-custom-scrollbars'
const TableBody = props => {
//get additional props beyond just props.children here
const {autoHeight} = props
return (
<Scrollbars
style={{
height: '100vh'
}}
>
<ReactTableDefaults.TbodyComponent>
{props.children}
</ReactTableDefaults.TbodyComponent>
</Scrollbars>
)
}
const Table = props => {
//props stuff would go here
return (
<div className="react-table-wrapper">
<ReactTable {...props}
TbodyComponent={TableBody} //this works
//TbodyComponent={(props) => {return (<TableBody autoHeight={props.autoHeight} children={props.children} />)}} //this doesn't
data={data}
columns={columns}
...
/>
</div>
)
}
I'm guessing I'm not understanding the proper way to pass a component in the TbodyComponent property, props.children, or something along those lines. This method just ends up looping forever.
In this example, how could I get the autoHeight prop to pass?
Update: Experimented with createElement and cloneElement and still receive the 130 error.
The solution to this was to convert the TableBody stateless component into a full component, that is
class TableBody extends React.Component {
instead of
const TableBody = props => {
That's what React-Table was expecting.
Is there a reason why this wouldn't work?
TbodyComponent={<TableBody autoHeight={props.autoHeight} />}
Also, I don't think you need to pass props.children - that should happen by default.
For reference, I looked at the answer provided here to a similar question: https://stackoverflow.com/a/39655113/8060919
In the case of react-table you could pass the props via the getTbodyProps. It must be a function returning the props as an object.
<ReactTable TbodyComponent={TableBody} getTbodyProps={()=>({autoHeight})}/>
See in the code
Try this
<ReactTable TbodyComponent={v => <DropTbody test={null} />}

React: Is it bad practice to import a child component directly rather than pass in as a dependency?

I may be over thinking this, but I am curious if importing a child component directly is bad practice with regards to coupling and testing.
Below is a simple example:
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
<Header></Header>
<div>{this.props.importantContent}</div>
</div>
)
}
}
To me it looks like there is now coupling between Widget and Header. With regards to testing, I don't see an easy way to mock the Header component when testing the Widget component.
How do other larger React apps handle cases like this? Should I pass Header in as a prop? If using react-redux, I can inject header with the Connect method like below to reduce boilerplate. Is that sound?
import { connect } from 'react-redux';
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
{this.props.header}
<div>{this.props.importantContent}</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
header: Header
}
}
export default connect(mapStateToProps)(Widget)
I am interested is simple doing what the community is generally doing. I see that one solution is doing shallow rendering to test on the main part of the component and not the child components using something like Enzyme.
Thoughts or other ideas?
Passing elements / components as props is a good idea. Having default props is a good idea too:
const Widget = ({
header = <div>Default Header.. </div>,
content = <div>Default Content.. </div>
}) =>
<div>
{header}
{content}
</div>
Then elsewhere in your app:
<Widget header={<Header title="Foo" />} content="content from props" />
No need to inject using connect
You can also pass a component, not just an element if you want to interact with props / send data back to parent:
const Widget = ({
Header = props => <div>Default Header.. </div>,
Content = props => <div>Default Content.. </div>
}) =>
<div>
<Header />
<Content />
</div>
Elsewhere:
<Widget Header={Header} Content={props => <Content />} />
As long as the component always renders the same thing it can be directly rendered as a child rather than the parent.
If all other portions of the Component remain constant and only the Header can be different across pages then you could actually implement it as an HOC instead of passing it as a props
const MyCompFactory = ({CustomHeader = DefaultHeader}) => {
return class Widget extends React.Component {
render() {
return (
<div>
<CustomHeader/>
<div>{this.props.importantContent}</div>
</div>
)
}
}
}
and use it like
const CustomComponent = MyCompFactory({CustomComponent: Header})
as long as testing is concerned in your case, you could just shallow render your component and then Search if the Header component is rendered something like
import Header from 'path/to/header'
const component = shallow(
<Widget {...customProps}/>
)
test('test' , () => {
expect(component.find(Header).exists()).toBe(true)
})

Resources