Spreading props to a list of Components - reactjs

I'm trying to send a bunch of props to a Component.
In console.logs I noted that everything is working as I expected, every object has its correct value, every spread operation is working. But my Cards doesn't show in the page.
Is this way correct?
return (
<div>
{this.state.articles.forEach((card) => {
<ArticleCard {...card} />
})}
</div>
)
Image showing the problem

Array.forEach does not return anything. You need to use Array.map. Also you should be returning the component to be rendered in the callback.
return (
<div>
{this.state.articles.map((card) => (
<ArticleCard {...card} />
)}
</div>
)

Instead of using forEach, you should use map function on array, and with return keyword to return each and every ArticleCard
return(
<div>
{this.state.articles.map((card) => {
return <ArticleCard {...card} />
})}
</div>
)

Related

map is not a function on array React

I'm getting an error that map is not a function on my data.
I'm getting a response back from an api that is returning an array of objects. When I don't refresh the page I can view the results displayed just fine and even navigate to view them individually (when I click on see more). However, when I refresh the page I get the
error of "Map is not a function" on my props even though the results are displaying in the console log.
I'm lost here and can't figure out why it's doing that.
componentDidMount() {
this.props.getCanyons();
}
render() {
const { canyons } = this.props;
console.log(canyons)
return (
<section>
{canyons.map(canyon => (
<section key={canyon.canyon_id}>
<h3>{canyon.canyon_name}</h3>
<img src={canyon.canyon_pic} alt={canyon.canyon_name} />
<Link key={canyon.canyon_id} to={`/canyon/${canyon.canyon_id}`}>
<button>See More</button>
</Link>
</section>
))}
</section>
);
}
}
When the api failed or having lag time to get response, it may be undefined. This kind of checking prevent you to from such problem.
return (
{canyons && canyons.map(canyon => (
...skipped code
))}
)
Typescript provide feature of adding a ? before try to access the related Object type variable
//In typescript
{canyons?.map(canyon => (
...skipped code
))}
componentDidMount() {
this.props.getCanyons();
}
render() {
const { canyons } = this.props;
console.log(canyons)
return (
<section>
{ canyons !== '' || canyons.length > 0 ? //change here
canyons.map(canyon => (
<section key={canyon.canyon_id}>
<h3>{canyon.canyon_name}</h3>
<img src={canyon.canyon_pic} alt={canyon.canyon_name} />
<Link key={canyon.canyon_id} to={`/canyon/${canyon.canyon_id}`}>
<button>See More</button>
</Link>
</section>
))
:
null
}
</section>
);
}
}
Please follow the change. It should works for you...
Many browsers provide a live view when using console.log(). When the request not finished 'canyons' is undefined. Use
console.log(JSON.parse(JSON.stringify(obj)))
For this problem, try to set default value or check variable first

Unexpected Behavior After State Change in React Component

RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
return(
<div className="results_wrapper">
{selected.map((r,i)=>{
let openState = (this.state.selectedImage==i)?true:false;
return(
<RenderPanel panelType={PanelType.large} openState={openState} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
this.setState({selectedImage:2})
console.log('wtfff'+this.state.selectedImage)
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})}
</div>
)
}
When I change the state of 'selectedImage', I expect the variable 'openState' to render differently within my map() function. But it does not do anything.
Console.log shows that the state did successfully change.
And what is even stranger, is if I run "this.setState({selectedImage:2})" within componentsDidMount(), then everything renders exactly as expected.
Why is this not responding to my state change?
Update
I have tried setting openState in my component state variable, but this does not help either:
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
let html = selected.map((r,i)=>{
return(
<RenderPanel key={i} panelType={PanelType.large} openState={this.state.openState[i]} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
let openState = this.state.openState.map(()=>false)
let index = i+1
openState[index] = true;
this.setState({openState:openState},()=>console.log(this.state.openState[i+1]))
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})
return(
<div className="results_wrapper">
{html}
</div>
)
}
https://codesandbox.io/s/ecstatic-bas-1v3p9?file=/src/Search.tsx
To test, just hit enter at the search box. Then click on 1 of 3 of the results. When you click 'Next', it should close the pane, and open the next one. That is what I'm trying to accomplish here.
#Spitz was on the right path with his answer, though didn't follow through to the full solution.
The issue you are having is that the panel's useBoolean doesn't update it's state based on the openState value passed down.
If you add the following code to panel.tsx, then everything will work as you described:
React.useEffect(()=>{
if(openState){
openPanel()
}else{
dismissPanel();
}
},[openState, openPanel,dismissPanel])
What this is doing is setting up an effect to synchronize the isOpen state in the RenderPanel with the openState that's passed as a prop to the RenderPanel. That way while the panel controls itself for the most part, if the parent changes the openState, it'll update.
Working sandbox
I believe it's because you set openState in your map function, after it has already run. I understand you think the function should rerender and then the loop will run once more, but I think you'll need to set openState in a function outside of render.
The problem is that even though you can access this.state from the component, which is a member of a class component, there's nothing that would make the component re-render. Making components inside other components is an anti-pattern and produces unexpected effects - as you've seen.
The solution here is to either move RenderImages into a separate component altogether and pass required data via props or context, or turn it into a normal function and call it as a function in the parent component's render().
The latter would mean instead of <RenderImages/>, you'd do this.RenderImages(). And also since it's not a component anymore but just a function that returns JSX, I'd probably rename it to renderImages.
I tire to look at it again and again, but couldn't wrap my head around why it wasn't working with any clean approach.
That being said, I was able to make it work with a "hack", that is to explicitly call openIt method for selectedImage after rendering is completed.
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter((x) =>
this.state.selectedGroups.includes(x.domain)
);
return (
<div className="results_wrapper">
{selected.map((r, i) => {
let openState = this.state.selectedImage === i ? true : false;
return (
<RenderPanel
key={i}
panelType={PanelType.medium}
openState={openState}
title={r.domain + ".TheCommonVein.net"}
preview={(openIt) => {
/* This is where I am making explicit call */
if (openState) {
setTimeout(() => openIt());
}
/* changes end */
return (
<div
className="result"
onClick={openIt}
style={{ boxShadow: theme.effects.elevation8 }}
>
<img src={r.url} />
</div>
);
}}
content={(closeIt) => (
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain, r.parent)}
<div
onClick={() => {
closeIt();
this.setState({
selectedImage: i + 1
});
}}
>
[Next>>]
</div>
<img src={r.url} />
</div>
)}
/>
);
})}
</div>
);
};
take a look at this codesandbox.

React.Children.only expected to receive a single React element child Stuck

Get an error Error: React.Children.only expected to receive a single React element child.
Really stuck, Anybody help?
componentDidMount() {
axios.get(this.props.url).then((res) => {
const data = res.data._embedded.districts;
this.setState({ data });
console.log(this.state.data);
});
}
render() {
console.log("render");
if (this.props.terr === "districts") {
return (
<FeatureGroup>
{this.state.data.map((data) => {
return (
<GeoJSON
key={data.name}
data={data.geometry}
style={this.myStyle}
>
<Popup>{data.name}</Popup>
</GeoJSON>
);
})}
</FeatureGroup>
);
}
}
}
I got same error. In my case there was an empty space inside component with tag. Empty space {""} inside was considered another component and since <Link
accepts only one component I got that error.
<Link href="/"> {" "} // I removed empty space
<a className="navbar-brand">GitTix</a>
</Link>
Without seeing all the code it is hard to tell. I have a tingling that your this.state.data is null or empty in your constructor and the first time you are trying to render your map function isn't returning any data. componentDidMount will run after it has already been rendered.
try checking to see if there is data in your data first.
{this.state.data.length && <FeatureGroup>
{this.state.data.map((data) => {
return (
<GeoJSON
key={data.name}
data={data.geometry}
style={this.myStyle}
>
<Popup>{data.name}</Popup>
</GeoJSON>
);
})}
</FeatureGroup>}
As the error describes, FeatureGroup is expecting a single element as a child. Currently you have one to many elements given the Array.prototype.map you are using to process an array and generate elements. Likely this is coming from prop-types on FeatureGroup that specify how many children are allowed. This is not uncommon, elements like Provider from react-redux expect a single child. At minimum wrap the output of the Array.prototype.map with an element:
<FeatureGroup>
<div>
{this.state.data.map((data) => {
return (
<GeoJSON
key={data.name}
data={data.geometry}
style={this.myStyle}
>
<Popup>{data.name}</Popup>
</GeoJSON>
);
})}
</div>
</FeatureGroup>
Or you can use React.Fragment:
<FeatureGroup>
<React.Fragment>
{this.state.data.map((data) => {
return (
<GeoJSON
key={data.name}
data={data.geometry}
style={this.myStyle}
>
<Popup>{data.name}</Popup>
</GeoJSON>
);
})}
</React.Fragment>
</FeatureGroup>
Hopefully that helps!

React Reveal not working for array of data

Can't use React Reveal on array of data with .map() to produce effect from documentation.
https://www.react-reveal.com/examples/common/
Their documentation gives a nice example
<Fade left cascade>
<div>
<h2>React Reveal</h2>
<h2>React Reveal</h2>
<h2>React Reveal</h2>
</div>
</Fade>
I want to produce the same CASCADE effect with my data
<React.Fragment>
{projects.filter(project => project.category === category)
.map((project, index) => {
return (
<ProjectThumb key={index} project={project}
showDetails={showDetails}/>
)
})}
</React.Fragment>
The effect I'm getting is that the entire ProjectThumb component list fades in in one group, I need them to fade in individually and as i scroll. Thanks in advance.
Pass react-reveal props to your React component. It will work.
<Fade left cascade>
<div>
{
projects
.filter(project => project.category === category)
.map((project, index) => (
<ProjectThumb key={index} project={project} showDetails={showDetails} />
))
}
</div>
</Fade>
In your ProjectThumb.js
const ProjectThumb = props => {
return <Whatever {...props}>{...}</Whatever>
}

onClick does not work for custom component

Why does onClick not work directly on my custom element as shown here? The function chosenResource does not get invoked.
return (
<div>
{
resources.map(resource => {
return (
<SelectListItem key={resource.id} onClick={this.chosenResource.bind(this)} resource={resource} />
)
})
}
</div>
)
However, if I encapsulate my custom element in a div element and have the onClick on the div element then it works.
return (
<div>
{
resources.map(resource => {
return (
<div key={resource.id} onClick={this.chosenResource.bind(this)}>
<SelectListItem resource={resource} />
</div>
)
})
}
</div>
)
What is wrong with the first approach?
#nrgwsth is correct, if you still want to stick with your first approach, you may use:
return (
<div>
{
resources.map(resource => {
return (
<SelectListItem key={resource.id} customClickEvent={this.chosenResource.bind(this)} resource={resource} />
)
})
}
</div>
)
Then, in your SelectListItem's render() function, use like this:
return (
<div onClick={this.props.customClickEvent}>
...
</div>
)
You can provide onClick and other events only on DOM elements.
In react, we have components(ex. SelectListItem) and elements(ex.div). Elements are the basic building blocks for the component.
Elements are transpiled into plain javascript objects which represent DOM elements where we can bind the dom events like onCLick, onHover etc.
However Component returns the instance that contains a bunch of elements but does not correspond to the DOM element.
SO if you want to bind DOM event we should do it on the react elements, not in components.
In the above code, onClick event is passed down as props to the component which can be seen by logging the props of the compoenet.

Resources