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

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;

Related

React Map function not rendering on browser, but it is console.logging

I'm working on a React homework assignment, working with the pokemon API. In this particular component, I'm accessing the API to return a list of all the pokemon names and render them to the browser.
I've called the API and mapped it out, and it seems to work when I console.log the names, but I'm not sure what I'm doing wrong that it is not rendering onto the actual browser, and could definitely use some help. Code:
import React, { useEffect, useState } from "react";
function PokedexHome(){
const [pokedexList, setPokedexList] = useState(undefined);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
useEffect(() => {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=100&offset=0`)
.then(response => response.json())
.then(data => {
setPokedexList(data)
setIsLoading(false);
},
error => {
setHasError(true)
setIsLoading(false)
}
);
},[]);
if(isLoading){
return <p>Loading...</p>
}
if(hasError){
return <p>An error has occurred, please try again later</p>
}
pokedexList.results.map((pokemon) => {
console.log(pokemon.name)
return <div className="list-container">
<p>{pokemon.name}</p>
</div>
})
};
export default PokedexHome
if you have a list then your PokedexHome returns void :)
so, first of all, you are missing a return before the map.
second, (if nothing changed lately) you can't return an array of components, you need to return a single component, which can be a Fragment (a React component without UI representation, created for this purpose)
return (
<>
{
pokedexList.results.map((pokemon) => {
console.log(pokemon.name)
return <div className="list-container">
<p>{pokemon.name}</p>
</div>
})
}
</>
)
You miss the return keyword in front if pokedexList. It should be like this. And also add () on your return
return pokedexList.results.map((pokemon) => {
return (
<div className="list-container">
<p>{pokemon.name}</p>
</div>
)
})
};
Your component need to return a JSX, so add return and wrap your list with <></>.
import React, { useEffect, useState } from 'react';
function PokedexHome() {
const [pokedexList, setPokedexList] = useState(undefined);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
useEffect(() => {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=100&offset=0`)
.then((response) => response.json())
.then(
(data) => {
setPokedexList(data);
setIsLoading(false);
},
(error) => {
setHasError(true);
setIsLoading(false);
},
);
}, []);
if (isLoading) {
return <p>Loading...</p>;
}
if (hasError) {
return <p>An error has occurred, please try again later</p>;
}
return (
<>
{pokedexList.results.map((pokemon) => {
console.log(pokemon.name);
return (
<div className="list-container">
<p>{pokemon.name}</p>
</div>
);
})}
</>
);
}
The most probable issue I can think of here is, you are not wrapping the whole JSX in () round braces. It specifies that you want to return something. With main function return statement of course
Example code
Try wrapping it like this.
return pokedexList.results.map((pokemon) => (
<div className="list-container">
<p>{pokemon.name}</p>
</div>
))

Rendering nested object properties from API call in react

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

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

Cannot read property 'updated' of undefined

When I use setApi(data.time); in the fetch section I can normally do console.log(api.updated);, but why I can not do just like what I wrote in the code below?
CodeSandbox
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [api, setApi] = useState({});
useEffect(() => {
fetch("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => res.json())
.then((data) => {
setApi(data);
});
}, []);
console.log(api.time.updated);
return (
<div className="App">
<h1>Currency Exchange</h1>
{/* <p>Time: {api.time.updated}</p>
<u>
<li>Code: {api.bpi.USD.code}</li>
<li>Rate: {api.bpi.USD.rate}</li>
</u> */}
</div>
);
}
Before the request is complete api will be an empty object. api.time will then be undefined, and trying to access property updated on that will give rise to your error.
You could use the logical AND && operator to make sure api.time is set.
const [api, setApi] = useState({});
useEffect(() => {
fetch("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => res.json())
.then((data) => {
setApi(data);
});
}, []);
console.log(api.time && api.time.updated);
Your revised code:
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [api, setApi] = useState({});
useEffect(() => {
fetch("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => res.json())
.then((data) => {
setApi(data);
});
}, []);
return (
<div className="App">
<h1>Currency Exchange</h1>
{api.time?.updated && <><p>Time: {api.time.updated}</p>
<u>
<li>Code: {api.bpi.USD.code}</li>
<li>Rate: {api.bpi.USD.rate}</li>
</u></>}
</div>
);
}
Simple way to check and ensure the API resolved with the appropriate data, then show the designated information.
Note that there's a reason why you have .then for your API query - it takes time to come back. Your code was executing before the api state could be filled with the response.
With the logical && operator, there's a simple way to look at it with React and I use it all the time.
If I have a loader component or something I want to show only when the loading variable is true, I can do something like this:
{loading && <Loader />}
The code to the right of the && will ONLY run if the left side is true. Since it's AND, if the first part is false it doesn't matter what the other parts are and they're skipped.
you can use a extra state to check loading data and display it when fetch done:
export default function App() {
//add loading to check api request and data to save result
const [api, setApi] = useState({ loading: false, data: undefined });
useEffect(() => {
setApi({ loading: true });
fetch("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => res.json())
.then((data) => {
setApi({ loading: false, data: data });
});
}, []);
return (
<>
{!api.loading && api.data ? (//check if data is loaded
<div className="App">
<h1>Currency Exchange</h1>
<p>Time: {api.data.time.updated}</p>
<u>
<li>Code: {api.data.bpi.USD.code}</li>
<li>Rate: {api.data.bpi.USD.rate}</li>
</u>
</div>
) : (
<>Loading data...</>//show a message while loading data(or <></> if want not display something)
)}
</>
);
}

Can I use two useEffect and have map inside a map

I am new to React and would like some help with the following problem. I current have this code.
import React, { useState, useEffect } from "react";
function FetchData() {
const [repos, setRepos] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetch("https://api.github.com/orgs/org_name/repos")
.then((res) => res.json())
.then((data) => {
setRepos(data);
})
.then(() => {
setIsLoading(false);
})
.catch((err) => console.log(err));
}, []);
return (
<div>
{repos.map((repo) => (
<div key={repo.id}>
<div>
<h2>Name: {repo.name}</h2>
<p>Top 5 Contributors</p>
))}
My above codes work fine, but my problem now is that I would like to add the top 5 contributors to the repository and to access that I have to go to https://api.github.com/repos/org_name/{repos}/contributors, and to get to that, I first have to use repo.contributor_url Should I use another useEffect and map to show the top 5 contributors?
Edit
Basically I want to do something like this.
useEffect(() => {
fetch(`${repos.contributors_url}`)
.then((res) => res.json())
.then((data) => {
setContributors(data);
console.log(data);
})
.catch((err) => console.log(err));
}, []);
...
<p> Top 5 Contributors: </p>
<ul>
{contributors.map((c, i) => {
<li key={i}>{c.name}</li>
)}
</ul>
Since you are new to React. React used to have class based components to handle state and those class based components had special functions called- Life-Cycle-Methods. But from React 16.8 onwards React Community came up with React-Hooks and functional components can now be used to handle state and useState() and useEffect() are examples of Hooks.
Now useEffect() alone is used to do perform life-cycle method's work.
The way you have used useEffect() in your code is simulating componentDidMount() as you have kept the 2nd argument as an empty array []
We can use other life-cycle methods like componentDidUpdate() and componetnWillUnmount() using useEffect() Hook itself.
Then based on your requirement you can use useEffect() Hook as many times as required by your Component.
Coming to Updated part of your question now:
So, you basically need to do promise chaining. We know that fetch() is promise based,so when one asynchronous call is resolved and we get the first data, within your useEffect() hook only, you need to make another asynchronous request using the second url-end point to get the respective data.
Here is the updated code now: Try this
import React, { useState, useEffect } from 'react';
function FetchData() {
const [repos, setRepos] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [contributors, setContributors] = useState([]);
const [isContributorLoading, setIsContributorLoading] = useState(true);
useEffect(() => {
fetch('https://api.github.com/orgs/{org}/repos')
.then((res) => res.json())
.then((data) => {
setRepos(data); // Data 1(repos) is received
// Now We make another API call to get Data 2 (contributors)
return fetch('https://api.github.com/repos/{org}/{repos}/contributors');
})
.then((res) => res.json()) // Chaining promise,handling 2nd Fetch request
.then((data2) => {
console.log(data2);
setContributors(data2);
})
.then(() => {
setIsLoading(false);
})
.catch((err) => console.log(err));
}, []);
return (
<div>
{ repos.length && repos.map((repo) => (
<div key={repo.id}>
<div>
<h2>Name: {repo.name}</h2>
</div>
</div>
))}
<p> Top 5 Contributors: </p>
<ul>
{contributors.length && contributors.map((c, i) => {
return <li key={i}>{c.name}</li>
)}
</ul>
</div>
);
}
So, basically you need to learn a bit more about how to use Hooks especially useEffect(), for now. Do some googling stuff, It would not be good if I tell you everything now. Give it a shot then.
You can directly call apis inside one useEffect.
import React, { useState, useEffect } from "react";
function App() {
const [repos, setRepos] = useState([]);
const [contributor, setContributor] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function caller() {
try {
setIsLoading(true);
const response = await fetch(
"https://api.github.com/orgs/octokit/repos"
);
const result = await response.json();
const contri = [];
console.log(result);
result.forEach((item) => {
contri.push(fetch(`${item.contributors_url}`));
});
Promise.all(contri)
.then((contributorResults) => contributorResults)
.then((responses) => {
console.log(responses);
return Promise.all(responses.map((r) => r.json()));
})
.then((cont) => {
setContributor([...cont])
});
setRepos(result);
} catch (err) {
console.log(err);
} finally {
setIsLoading(false);
}
}
caller();
}, []);
return (
<div>
{repos.map((repo,index) => (
<div key={repo.id}>
<h2> Name: {repo.name} </h2>
{ contributor[`${index}`] && contributor[`${index}`].slice(0,5).map(item => {
return <div key={item.id}>
<div>{item.login}</div>
</div>
})}
</div>
))}
{isLoading && <div>...loading</div>}
</div>
);
}
export default App;

Categories

Resources