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

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!

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

Add a chidren at first of component children

I create an wrapper for Picker component And I want to add a child in condition
I try this code
render() {
const { children, ...rest } = this.props;
return (
<Picker {...rest}>
{rest.selectedValue === undefined ? (
<Picker.Item value="1" label="1" />
) : null}
{children}
</Picker>
);
}
And I get this error
TypeError: null is not an object(evaluating 'o.props')
I also try with undefiened instead of null and short circuit condition but I get error in that case too.
Also note If I remove condition and just add an element before children its work. something like this
<Picker.Item value="1" label="1" />
{children}
UPDATED
I found the problem is when condition is false for example this make error
{false && (<Picker.Item value="1" label="1" />)}
expo project https://snack.expo.io/#cooper47/mad-waffle
( change picker and see the error )
I think the problem is when I concat children with undefined ( result of condtion ) because when I try this I get same error
<Picker {...rest}>
{undefined} // result of short circuit
{children}
</Picker>
Why I get this error with conditon and get no error without condition?
How I can add an element before this.props.children?
Is there anyway add element at first of children? for example children.add(<Picker.Item...>)
I found out actually its a bug in react-native
Here is the relative issue https://github.com/facebook/react-native/issues/25141#issuecomment-498651856
Note: There is no problem with the condition, the error depends on the wrapper (Picker) internal calls.
My guess is that Picker internal calls its children props (expects Picker.Item), it may check your condition props when it falsy and therefore throw an error of undefined props.
<Picker>
{condition && <Picker.Item value="1" label="1" />}
// ^ Picker wrapper may check null.props, undefined.props, false.props etc
{children}
</Picker>
In this case, make the condition outside the render:
const conditionChildren = condition ? (
<> <Picker /> {children} </> ) : (children);
return {conditionChildren}
How I can add an element before this.props.children?
Use React.createElement or call the component inline:
function ConditionChildren({ children, condition }) {
return (
<>
{condition && <Picker />}
// or {condition && React.createElement(Picker)}
{children}
</>
);
}
Is there anyway add an element at first of children? for example children.add()
const addFirstToChildren = [
React.createElement(Picker),
...React.Children.toArray(children)
];
return {addFirstToChildren}
Check the demo with the generic solution:
Try below code for PureComponent
import React, { PureComponent } from 'react'
import { View, Text, TouchableOpacity, Picker } from 'react-native'
export default class SelectBox extends React.PureComponent {
constructor(props) {
super(props)
}
render() {
const { options, value, onChageText } = this.props
let Alloptions = options.map((item, key) => {
return (<Picker.Item label={item.name} value={item.value} key={key} />)
})
return (
<Picker selectedValue={value} onValueChange={onChageText}>
{/* If you want to add default option here */}
{/* <Picker.Item label="Select Location..." value="" /> */}
{Alloptions}
</Picker>
)
}
}
and your Component Like
<SelectBox
options={[{name:'aaa',value:'11'},{name:'bbbb',value:'22'}]}
value={''}
onChageText={this.selectLocation}
/>
selectLocation(itemValue, itemIndex) {
...your code
}
Thanks

Spreading props to a list of Components

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

Resources