Rendering nested object properties from API call in react - javascript

I am trying to render text from an API call, text or numbers that are directly accesible from the axios.data object can render normally, nevertheless when inside the axios.data there is another object with its own properties I cannot render because an error shows, the error is 'undefined is not an object (evaluating 'coin.description.en')', over there description is an object; my code is
function SINGLE_COIN(props) {
const { id } = useParams()
console.log(id);
const SINGLE_API = `https://api.coingecko.com/api/v3/coins/${id}?tickers=true&market_data=true&community_data=true&developer_data=true&sparkline=true`
const [coin, setCoin] = useState({})
useEffect(() => {
axios
.get(SINGLE_API)
.then(res => {
setCoin(res.data)
console.log(res.data)
})
.catch(error => {
console.log(error)
})
}, [])
return (
<div>
<h2>{coin.name}</h2>
<div>{coin.coingecko_score}</div>
<div>{coin.liquidity_score}</div>
<div>{coin.description.en}</div>
<SINGLE_COIN_DATA coin={coin} />
</div>
)
}
Thanks!

For the initial render (data is not fetched yet), it will be empty. so nested property would be undefined.
so note the changes:
Example 1:
const [coin, setCoin] = useState(null);
..
return (
<div>
{coin ? (
<>
<h2>{coin.name}</h2>
<div>{coin.coingecko_score}</div>
<div>{coin.liquidity_score}</div>
<div>{coin.description.en}</div>
</>
) : null}
</div>
);
Example:2: Use the optional chaining while accessing nested property
return (
<div>
<h2>{coin?.name}</h2>
<div>{coin?.coingecko_score}</div>
<div>{coin?.liquidity_score}</div>
<div>{coin?.description?.en}</div>
</div>
);
And the complete code with : working example
export default function SINGLE_COIN() {
const { id } = useParams()
const SINGLE_API = `https://api.coingecko.com/api/v3/coins/${id}?tickers=true&market_data=true&community_data=true&developer_data=true&sparkline=true`;
const [coin, setCoin] = useState(null);
useEffect(() => {
axios
.get(SINGLE_API)
.then((res) => {
setCoin(res.data);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div>
{coin ? (
<>
<h2>{coin.name}</h2>
<div>{coin.coingecko_score}</div>
<div>{coin.liquidity_score}</div>
<div>{coin.description.en}</div>
</>
) : null}
</div>
);
}

Related

API fetch in React

the screenshots attached are the result of an API fetch (movie database).
From this results, as shown in the screenshots i am trying to reach for the "primaryImage.url" yet I keep on getting "Cannot read properties of undefined (reading 'url').
Here is my page:
const SearchPage = () => {
const params = useParams();
const { query } = params;
const [data, setData] = useState(null);
const fetchData = () => {
fetch(`${rapidApiUrl}/search/title/${query}${rapidSearchData}`, rapidApiOptions)
.then((response) => response.json())
.then((data) => {
setData(data.results);
})
.catch((err) => console.error(err));
};
useEffect(() => {
fetchData();
}, [query]);
console.log(data)
return (
<div>
{data &&
data.map((media) => (
<div key={media.id}>
{/* <img src={media.primaryImage.url} alt={media.titleText.text} /> */}
<p>{media.titleText.text}</p>
<p>{media.releaseYear.year}</p>
</div>
))}
</div>
);
};
export default SearchPage;
As you are looping through the data, some of the primaryImage values are objects, which do indeed have a url property, but some of them are null. When attempting to find the url property of null, it errors out. You probably want to check to see if primaryImage is null and handle it differently than if it has a value.
The problem is primaryImage property appears to be null for few cases. You should use optional chaining to avoid the errors.
return (
<div>
{data &&
data.map((media) => (
<div key={media.id}>
<img src={media.primaryImage?.url} alt={media.titleText?.text} /> */}
<p>{media.titleText.text}</p>
<p>{media.releaseYear.year}</p>
</div>
))}
</div>
);
You can read more about optional chaining here

Why will my fetch API call map one nested objects, but not the other?

I'm parsing data from the NASA API using React, and for some reason I can map one nested object within the return but not the other.
Here is my parent component:
import React, { useState } from 'react'
import './NasaAPI.scss'
import NasaImages from './NasaImages'
const NasaAPI = () => {
const [nasaData, setNasaData] = useState([])
const [nasaImage, setNasaImage] = useState("")
const [searchInput, setSearchInput] = useState("")
const [loading, setLoading] = useState(true)
const fetchData = async (e) => {
const data = await fetch(`https://images-api.nasa.gov/search?q=${searchInput}`)
.then(response => response.json())
.then(data => setNasaData(data.collection.items))
.catch(err => console.log(err))
.finally(setLoading(false))
}
const handleSubmit = (e) => {
e.preventDefault()
fetchData()
}
const handleChange = (e) => {
setSearchInput(e.target.value)
}
return (
<div>
<h2>Search NASA Images</h2>
<form onSubmit={handleSubmit}>
<input name="searchValue" type="text" value={searchInput} onChange={handleChange}></input>
<button value="Submit">Submit</button>
</form>
<section>
<NasaImages nasaData={nasaData} loading={loading}/>
</section>
</div>
)
}
export default NasaAPI
Here's where the issue is, in the child component:
import React from 'react'
const NasaImages = ({ nasaData }) => {
console.log(nasaData)
return (
<div>
<h2>This is a where the data go. 👇</h2>
{
nasaData && nasaData.map((data, idx) => {
return (
<div key={idx}>
<p>{data.href}</p>
<div>
{/* {data.links.map((data) => {
return <p>{data.href}</p>
})} */}
{data.data.map((data) => {
return <p>{data.description}</p>
})}
</div>
</div>
)
})
}
</div>
)
}
export default NasaImages
The current configuration works, and will display a data.description (data.data.map) mapping property. However, I want the commented code immediately above it to work which displays a data.href (data.links.map) property.
The JSON looks as follows:
So, the issue is that I can map one set of properties, data.data.map, but cannot access the other in the same object, data.links.map, without getting the error "TypeError: Cannot read property 'map' of undefined". Thank you in advance!
There exists a data element sans a links property, in other words there is some undefined data.links property and you can't map that. Use Optional Chaining operator on data.links when mapping, i.e. data.links?.map. Use this on any potentially undefined nested properties.
const NasaImages = ({ nasaData = [] }) => {
return (
<div>
<h2>This is a where the data go. 👇</h2>
{nasaData.map((data, idx) => (
<div key={idx}>
<p>{data.href}</p>
<div>
{data.links?.map((data, i) => <p key={i}>{data.href}</p>)}
{data.data?.map((data, i) => <p key={i}>{data.description}</p>)}
</div>
</div>
))}
</div>
)
}

TypeError: Cannot read property 'Countries' of undefined. Why an array of objects is not recognized and is not filtered?

I am getting an object from the API ('https://api.covid19api.com/summary'). This object has a
key
Countries with an array of objects and this array of objects I need to filter.
const filteredData = data.Countries.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
TypeError: Cannot read property 'Countries' of undefined.
Why an array of objects is not recognized and is not filtered?
In another file, the map method iterates over the same writing data.Countries without error.
const Home = () => {
const [data, setData] = useState();
const [searchfield, setSearchfield] = useState('')
useEffect(() => {
const fetch = async () => {
try{
const res = await axios.get('https://api.covid19api.com/summary');
setData(res.data);
}catch(error){
console.log(error);
}
};
fetch();
}, []);
const onSearchChange = (event) => {
setSearchfield(event.target.value)
}
const filteredData = data.Countries.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
return (
<div className="main-container">
<Header searchChange={onSearchChange}/>
<div className="wrapper">
<Card data={data}/>
{/*<div className="graph">
<h1>Global Pandemic Crisis Graph</h1>
<img src={COVID.image} alt=""/>
</div>*/}
<div className="countries">
<Countries data={filteredData}/>
</div>
</div>
{/*<Footer />*/}
</div>
)
}
When you are fetching the data from an api, you need to use optional chaining ? when applying any higher order function to an array just incase the data haven't been loaded. for example
const filteredData = data?.Countries.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
Issue
The initial data state is undefined, so data.Countries is undefined on the initial render.
const [data, setData] = useState();
Solution
Provide valid initial state and guard against later bad updates (if they happen).
const [data, setData] = useState({ Countries: [] });
...
const filteredData = data?.Countries?.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
You need to filter your data in a callback of the axios response, or it will be "undefined" because it hasn't finished fetching it.
let filteredData = useRef(null);
useEffect(() => {
if (typeof data !== "undefined")
filteredData.current = data.Countries.filter((dat) => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
});
}, [data]);
const fetch = async () => {
const res = await axios
.get("https://api.covid19api.com/summary")
.then((response) => {
// response.data should not be "undefined" here.
setData(response.data);
})
.catch((error) => {
// Error fallback stuff
console.error(error);
});
};
if (!filteredData.current) fetch();
Later in your code you can check whether or not it has been defined,
return (
<div className="main-container">
<div className="wrapper">
{filteredData.current !== null &&
filteredData.current.map((CountryMap, i) =>
<div key={i}>{CountryMap.Country}</div>
)
}
</div>
</div>
);

Object value is undefined but it's there React

I have the following code :
export default function ProjectView({filteredProjects,revenueGroups}) {
const [projects,setProjects] = useState();
useEffect(() => {
const aap = filteredProjects.map(filteredObject => {
getProjectFields(filteredObject.projectId).then(res => {
filteredObject.rekt = res.data[0].id;
})
return filteredObject;
})
setProjects(aap);
},[filteredProjects])
And the rendered component :
return (
<div className='main'>
{projects.map(project => (
<div className='view-container' key={project._id}>
{console.log(project)}
</div>
))}
</div>
)
This works fine , when i console.log(project) like above it shows the following :
{
projectName: "privaye"
rekt: "project:1b1126ebb28a2154feaad60b7a7437df"
__proto__: Object
}
when i console.log(projectName) it shows the name, but when i console.log(project.rekt) it's undefined...
eventhough its there when i console.log(project)
EDITED
I didn't notice the was a promise inside :P
useEffect(() => {
fetchThings()
},[filteredProjects])
const fetchThings = async () => {
const promArr = filteredProjects.map(filteredObject => {
return getProjectFields(filteredProject.project.id)
})
const filteredObjects = await Promise.all(promArr)
const projectsMod = filteredObjects.map(fo => ({
...fo,
rekt: fo.data[0].id,
}))
}
Maybe an approach like this will help you with the asyncronous problem
console.log isn't rendering anything so maybe React doesn't try to refresh this part of the DOM when projects state is updated.
You can stringify the object to check the content of the variable inside the return
return (
<div className='main'>
{projects.map(project => (
<div className='view-container' key={project._id}>
<span>{ JSON.stringify(project) }</span>
</div>
))}
</div>
)
You are running promise, after this promise will return value you set rekt, but promise will work god knows when, and most probably you check value before this promise resolved.
getProjectFields(filteredObject.projectId).then(res => {
// THIS CODE CAN RUN AFTER RENDERING
filteredObject.rekt = res.data[0].id;
})
so first you wait till promises will complete and only then set new state
const ourPreciousPromises = filteredProjects.map(filteredObject =>
getProjectFields(filteredObject.projectId).then(res => {
filteredObject.rekt = res.data[0].id;
return {...filteredObject}
})
})
Promise.all(ourPreciousPromises).then(newObjects =>
setProjects(newObjects)
)

How to map over a response from a REST call? [duplicate]

This question already has answers here:
what is right way to do API call in react js?
(14 answers)
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I have the following code where I am making a REST call and assigning the result to a variable.
Then I am using the result to map over and create components with props.
But at present it throws an error because the value for list is undefined.
I believe this is because the value of the list is not set yet when I am attempting to map due to axios async call not completed yet.
Thus 2 queries.
How should I use the response value. Is my method of assigning it to the variable 'list' correct or it should be done differently?
How do I wait for list to be populated and then map over it?
You can see how the response.data will look by looking at following endpoint: https://sampledata.free.beeceptor.com/data1
Sample response data:
[
{
"word": "Word of the Day",
"benevolent": "be nev o lent",
"adjective": "adjective",
"quote": "well meaning and kindly.<br/>a benevolent smile",
"learn": "LEARN MORE"
},
{
"word": "Word of the Day",
"benevolent": "be nev o lent",
"adjective": "adjective",
"quote": "well meaning and kindly.<br/>a benevolent smile",
"learn": "LEARN MORE"
}
]
Client code:
const App = () => {
// const cardData = useSelector(state => state.cardData)
let list;
useEffect(() => {
axios.get('https://sampledata.free.beeceptor.com/data1')
.then(response => {
list = response.data;
list.forEach(l => console.log(l))
})
.catch(error => {
console.log(error)
})
}, [])
return (
<>
<ButtonAppBar/>
<div className='container'>
<div className='row'>
{
list.map((data) => {
const {word, bene, adj, well, learn} = data;
return (
<div className='col-lg-3 col-md-6 format'>
<SimpleCard word={word} bene={bene} adj={adj} well={well} learn={learn} />
</div>
)
})
}
</div>
</div>
</>
);
}
export default App;
You need to make use of useState to store the data that you get from the API.
For example
const [state, setState] = useState({ list: [], error: undefined })
Because the API call is asynchronous and the data will not be available until the component mounts for the first time. You need to use a conditional to check for state.list.length otherwise it will throw an error cannot read property ..x of undefined.
const App = () => {
// create a state variable to store the data using useState
const [state, setState] = useState({ list: [], error: undefined });
useEffect(() => {
axios
.get("https://sampledata.free.beeceptor.com/data1")
.then(response => {
setState(prevState => ({
...prevState,
list: [...prevState.list, ...response.data]
}));
})
.catch(error => {
setState(prevState => ({ ...prevState, list: [], error: error }));
});
}, []);
return (
<>
<ButtonAppBar/>
<div className='container'>
{
// you can show a loading indicator while your data loads
!state.list.length && <div>The data is loading....</div>
}
<div className='row'>
{
state.list.length && state.list.map((data) => {
const {word, bene, adj, well, learn} = data;
return (
<div className='col-lg-3 col-md-6 format'>
<SimpleCard word={word} bene={bene} adj={adj} well={well} learn={learn} />
</div>
)
})
}
</div>
</div>
</>
);
}
You could benefit from using useState hook here.
For example:
const App = () => {
const [list, setList] = useState([]);
useEffect(() => {
axios.get('https://sampledata.free.beeceptor.com/data1')
.then(response => {
setList(response.data);
})
.catch(error => {
console.log(error)
})
}, [])
return (
<>
<ButtonAppBar/>
<div className='container'>
<div className='row'>
{
list.map((data) => {
const {word, bene, adj, well, learn} = data;
return (
<div className='col-lg-3 col-md-6 format'>
<SimpleCard word={word} bene={bene} adj={adj} well={well} learn={learn} />
</div>
)
})
}
</div>
</div>
</>
);
}
export default App;
Do not use let to save fetched values instead use state or props in case you want to generate UI from that. In react component rerender if state or props value changed.
Reason of getting error is, you are doing asynchronous call and because of that your component is parallely rendering and inside the return list will be null and it will throw error .
Correct way is :
const App = () => {
const [list, setlist]= React.useState([])
useEffect(() => {
axios.get('https://sampledata.free.beeceptor.com/data1')
.then(response => {
setlist (response.data)
})
.catch(error => {
console.log(error)
})
}, [])
return (
<>
<ButtonAppBar/>
<div className='container'>
<div className='row'>
{
list.map((data) => {
const {word, bene, adj, well, learn} = data;
return (
<div className='col-lg-3 col-md-6 format'>
<SimpleCard word={word} bene={bene} adj={adj} well={well} learn={learn} />
</div>
)
})
}
</div>
</div>
</>
);
}
export default App;
This can be solved in two ways (since you are using hooks)
useRef() (I would not recommend doing this)
useState() (as the example I have given)
I will show you by using the useState method, but you should keep in mind that since it's a state it will re-render (I don't think it will be an issue here).
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const App = () => {
let [list, setList] = useState(<>LOADING</>);
useEffect(() => {
// You can use your link here
// I have created corsenabled.herokuapp.com just to bypass the CORS issue. It's only for testing and educational purpose only. No intention to infringe any copyrights or other legal matters
// I have used jsonplaceholder.typicode.com as an example
axios.get('https://corsenabled.herokuapp.com/get?to=https://jsonplaceholder.typicode.com/posts')
.then(response => {
let tempData = response.data;
let anotherData = tempData.map(data => {
return (<div>{data.userId}<br/>{data.id}<br/>{data.title}<br/>{data.body} <br/><br/></div>)
})
// tempData = tempData.map(data => <div> {JSON.stringify(data)} </div>)
console.log(tempData)
setList(anotherData)
})
.catch(error => {
console.log(error)
})
}, [])
return (
<>
<div className='container'>
<div className='row'>
{
list
}
</div>
</div>
</>
);
}
export default App;

Categories

Resources