map is not a function on array React - reactjs

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

Related

NextJS can't figure out why nothing renders from simple prop passing to display a p tag? (Data fetching and displau)

I'm doing some work in NextJS and I needed to render a card so I created the api endpoint setup the query debugged incorrect client state on the page and now it appears that my state variables are correct but I am still not rendering what I would expect.
I'm trying to render a JSON object that looks like this:
[{"id":"cla02oxnl0000txe0vzncxq7o","creator":"cl9x4lmhv00021snptuymv0vr","broken_item":"","issue_desc":"","loc":"","active":true,"prio":"Low"}]
And I can see a valid JSON object in the list of tickets I want to render:
Client side in the console by logging the tickets array.
But I must have some syntax or misunderstanding of how I'm allowed to render here is my code for the page render:
if (!currentSession) {
return (<div> No Session</div>)
}
return (
<div>
{console.log(tickets)}
{tickets.map(({creator,broken_item, issue_desc, loc, prio,active}) => {
<TicketGUI creator={creator} broken_item={broken_item} issue_desc={issue_desc} loc ={loc} prio={prio}/>
})}
</div>
)
And the component that is trying to render it's own props just looks like this:
export default function TicketGUI(props){
return <p> {props} </p>
}
I had the syntax slightly off but my main issue is that I forgot to return something inside the anonymous function inside the map. This will fix the issue and render:
return (
<div>
{}
{tickets.map(
({creator,broken_item, issue_desc, loc, prio,active,creatorName}) => {
return(
<li key={listLength++}>
<TicketGUI creator={creator} broken_item={broken_item} issue_desc={issue_desc} loc ={loc} prio={prio} active={active} name={creatorName}/>
</li>
)
}
)}
</div>
)

Why does my app work when I comment and un-comment it

import React from 'react'
import CoinItem from './CoinItem'
const Coins = (props) => {
return (
<div className='container'>
<div>
<div className='heading'>
<p>#</p>
<p className='coin-name'>Coins</p>
<p>Price</p>
<p>24h</p>
<p className = 'hide-mobile'>Volume</p>
<p className = 'hide-mobile'>Market Cap</p>
</div>
{props.coins.map(coins => {
return (
<CoinItem coins={coins} key={coins.id} />
)
})}
</div>
</div>
)
}
export default Coins
With this code, my app won't render and I get the error, "Uncaught TypeError: props.coins.map is not a function." However, it directs me to the line where I create a div with the class name of the heading.
However, when I comment out:
{props.coins.map(coins => {
return (
<CoinItem coins={coins} key={coins.id} />
)
})}
and uncomment it again, my app renders perfectly with the API working, but once again, when I refresh, the app de-renders.
How do I fix this?
CodeSandbox link: https://codesandbox.io/s/busy-cloud-pcfne0?file=/src/components/Coins.js:409-512
A few things you can do is:
Ensure that the "coins" prop exists.
If it exists, make sure it is an Array and not an Object. You cannot use .map() on an Object. it is an Array method.
To avoid the code running a .map() when coins is null/undefined, add a ? after props.coins so that the .map() runs only if coins is defined.
{props.coins?.map(coin => { /*YOUR CODE HERE*/ })

How to conditionally render a prop initialized as an empty object?

My application has a user input a query to make a request to the Pokemon API and the response is rendered. Prior to the user making a request, it shows No Pokemon yet, please submit a Pokemon.
I initialized pokemonCharacter as an empty object being passed to the PokemonInfo component. However, my conditional render logic is not working.
Please let me know how to solve this. https://codesandbox.io/s/pokedex-5j1jf
The following are my failed attempts.
Attempt #1
let myDiv;
if (pokemonCharacter && pokemonCharacter.length > 0) {
myDiv = <div>
<p>{pokemonCharacter.name}</p>
<img src={pokemonCharacter.sprites.front_default} alt="pokemon" />
</div>
} else {
myDiv = <p>No Pokemon yet, please submit a Pokemon!</p>
}
return ({myDiv})
Attempt #2
{(pokemonCharacter && pokemonCharacter.length > 0) ?
<div>
<p>{pokemonCharacter.name}</p>
<img src={pokemonCharacter.sprites.front_default} alt="pokemon" />
</div>
: <p>No Pokemon yet, please submit a Pokemon!</p>}
Attempt #3
const list = pokemonCharacter => {
if (!pokemonCharacter) {
return null;
}
if (!pokemonCharacter.length) {
return <p>No Pokemon yet, please submit a Pokemon!</p>
} else {
return (
<div>
<p>{pokemonCharacter.name}</p>
<img src={pokemonCharacter.sprites.front_default} alt="pokemon" />
</div>
)
}
}
return (
{list}
);
Where you are checking the loading status, just check if the status is false or not then display the component.
{ loading
? <p>Loading...</p>
: <PokemonInfo pokemonCharacter={pokemonCharacter} />
}
I solved this by checking if pokemonCharacter.name is truthy in the PokemonInfo component, as opposed to just checking if pokemonCharacter the object as a whole is truthy.
{pokemonCharacter.name ?
<div>
<p>{pokemonCharacter.name}</p>
<img src={pokemonCharacter.sprites.front_default} alt="pokemon" />
</div>
: <p>No Pokemon yet, please submit a Pokemon!</p>}

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.

Create a custom Hook for splitting strings (React JS)

I'm working with React JS (hooks) for recently. For a project, I need to split many strings in different divs. So, I created a function to this, that saves me some time! My actual question is : Should I create a custom Hook instead of my function ?
Of course, the actual code works, but as a beginner, I don't know if my way is clean. I need feedbacks cause my main goal to write the best and clear code as possible.
// Splitting Texts
const splitText = str => {
return (
<div>
{str.split(' ').map((item, index) => {
return (
<div key={index}>
{item}
</div>
);
})}
</div>
);
};
// Main App
export default function App() {
const name = splitText('Lucie Bachman');
const description = splitText('Hey, this is my first post on StackOverflow!');
return (
<div className="App">
<h1>{name}</h1>
<h2>{description}</h2>
</div>
);
}
Expected results :
<h1>
<div>
<div>Lucie</div>
<div>Bachman</div>
</div>
</h1>
I'm super excited to have joined the community!
Thanks to you, and take care.
Lucie Bachman
A custom hook is something that uses out of the box react hooks to actually provide a logic and return data.
If the function returns JSX, its actually just a function or can be used as a functional component
Since you only want to split string once you can convert it into a component and use React.memo to optimize rendering
// Splitting Texts
const SplitText = React.memo(({str}) => {
return (
<div>
{str.split(' ').map((item, index) => {
return (
<div key={index}>
{item}
</div>
);
})}
</div>
);
});
// Main App
export default function App() {
return (
<div className="App">
<h1><SplitText str={'Lucie Bachman'} /></h1>
<h2><SplitText str={'Hey, this is my first post on StackOverflow!''} /></h2>
</div>
);
}

Resources