Reputation: 33
I want to load planet data from an API using React and put this data into a Material UI card component. Unfortunately the planet API I'm using doesn't have photographs, which I want to also display in the card. For this, I'm using another API.
API for fetching planet data: https://api.le-systeme-solaire.net/rest
API for fetching planet images: https://images-api.nasa.gov/search
I have figured out how to render each planet component with its respective info into a card, but don't know how to map each NASA image to the respective planet:
This is the code for the App.js component:
import React, {useState, useEffect} from 'react';
import './App.css';
import axios from 'axios'
import Block from "./Block"
function App() {
const [planets, setPlanets] = useState([])
const [images, setImages] =useState([])
const getPlanets = async()=>{
const data = await fetch
('https://api.le-systeme-solaire.net/rest/bodies') //Await for first fetch promise to resolve
const planets = await(data.json()) //Await for data.json to resolve and save this planets
setPlanets(planets.bodies.filter
(planet =>planet.isPlanet && planet.name.indexOf('1')===-1))
}
const getImages = (name)=>{
axios.get('https://images-api.nasa.gov/search',{
params:{
q: name
}
})
.then(res =>
setImages(images.concat(res.data.collection.items[0].links[0].href)))
}
console.log(planets)
useEffect(()=>{
getPlanets();
},[])
useEffect(()=>{
if(planets){
planets.map(planet =>getImages(planet.englishName))
}
},[])
console.log(images)
console.log(planets.length)
return (
<div className="App">
{planets.map(planet =>
(<Block
key = {planet.id}
id = {planet.id}
name = {planet.englishName}
dateDiscovered = {planet.discoveryDate}
mass = {planet.mass.massValue}
massExp = {planet.mass.massExponent}
image = ???
/>)
)}
</div>
);
}
export default App;
The code for Block.js is as follows:
import React, {useState, useEffect} from 'react'
import './Block.css'
import {Card, CardContent} from '@material-ui/core'
function Block(props){
return (
<div>
<Card>
<CardContent>
<div className = "planetInfo">
<h4>{props.name}</h4>
<h5>Mass: {props.mass}*10^{props.massExp} kg</h5>
<h5>Discovery Date: {props.dateDiscovered}</h5>
<h5>ID: {props.id}</h5>
<img src = {props.image}/>
</div>
</CardContent>
</Card>
</div>
)
}
export default Block
I have a feeling my structure isn't right. Any help would be appreciated.
Upvotes: 3
Views: 537
Reputation: 33
import React, {useState, useEffect} from 'react';
import './App.css';
import axios from 'axios'
import Block from "./Block"
function App() {
const [planets, setPlanets] = useState([])
const [images, setImages] =useState([])
const getPlanets = async()=>{
const data = await fetch
('https://api.le-systeme-solaire.net/rest/bodies') //Await for first fetch promise to resolve
const planets = await(data.json()) //Await for data.json to resolve and save this planets
setPlanets(planets.bodies.filter
(planet =>planet.isPlanet && planet.name.indexOf('1')===-1))
}
useEffect(()=>{
getPlanets();
},[])
useEffect(() => {
async function fetchAllImages() {
const allImages = [];
for await (const planet of planets) {
const data = await axios.get('https://images-api.nasa.gov/search',{
params:{
q: planet.englishName
}})
const res = await data.data.collection.items[0].links[0].href
allImages.push(res);
}
setImages(allImages);
}
fetchAllImages();
}, [planets])
console.log(images)
return (
<div className="App">
{planets.map((planet,index) =>
(<Block
key = {planet.id}
id = {planet.id}
name = {planet.englishName}
dateDiscovered = {planet.discoveryDate}
mass = {planet.mass.massValue}
massExp = {planet.mass.massExponent}
image = {images[index]}
/>))}
</div>
);
}
export default App;
Upvotes: 0
Reputation: 367
I would add the images to the planet array, and remove the images array altogether.
On these lines...
if(planets){
planets.map(planet =>getImages(planet.englishName))
}
},[])
Your getImages
function is fetching and storing the images in images
But since you are already mapping through your planets to get the planet names, instead of storing those images in a separate variable, return/store them in the planets array.
Maybe something like this...
if(planets){
planets.map(planet => {
const image = getImages(planet.englishName)
return {...planet, image}
})
},[])
So now this line image = ???
is image = planet.image
And maybe do it all with one useEffect
with something like this...
const getPlanets = async() => {
const response = await fetch('https://api.le-systeme-solaire.net/rest/bodies')
const data = await data.json()
const planets = data.bodies.filter(planet =>planet.isPlanet && planet.name.indexOf('1') === -1)
return planets
}
const getImages = async (name) => {
const response = await axios.get('https://images-api.nasa.gov/search', {params:{q: name}})
const images = response.concat(res.data.collection.items[0].links[0].href)
return images
}
useEffect(()=>{
getPlanets()
.then(planets => Promise.all(planets.map(async (planet) => {
const image = await getImages(planet.englishName)
return {...planet, image }
})))
.then(planets => setPlanets(planets))
},[])
*note I have not tested the above code but hopefully close to what you need.
Upvotes: 1
Reputation: 202605
I suggest combining the two data fetches into a single effect hook callback. First fetch and filter the list of celestial bodies for true planets, then fetch the images and map the planets and images into a single array of planet data. Now simply pass image={planet.image}
to Block
.
function App() {
const [planets, setPlanets] = useState([]);
const getPlanets = async () => {
const data = await fetch("https://api.le-systeme-solaire.net/rest/bodies"); //Await for first fetch promise to resolve
const planets = await data.json(); //Await for data.json to resolve and save this planets
// filter planet data
const filteredPlanets = planets.bodies.filter(
(planet) => planet.isPlanet && planet.name.indexOf("1") === -1
);
// define function here to avoid needing it as dependency
// return GET request response image value
const getImages = (name) => {
return axios
.get("https://images-api.nasa.gov/search", {
params: {
q: name
}
})
.then((res) => res.data.collection.items[0].links[0].href);
};
// Fetch and await all images
const planetImages = await Promise.all(
filteredPlanets.map((planet) => getImages(planet.englishName))
);
// Merge planets and images arrays
const planetsWithImages = filteredPlanets.map((planet, i) => ({
...planet,
image: planetImages[i]
}));
setPlanets(planetsWithImages);
};
useEffect(() => {
getPlanets();
}, []);
return (
<div className="App">
{planets.map((planet) => (
<Block
key={planet.id}
id={planet.id}
name={planet.englishName}
dateDiscovered={planet.discoveryDate}
mass={planet.mass.massValue}
massExp={planet.mass.massExponent}
image={planet.image} // <-- pass planet.image
/>
))}
</div>
);
}
Upvotes: 1
Reputation: 33
It's a really difficult problem to handle multiple API calls together. I don't exactly know if this will work but why don't you change
const [images, setImages] =useState([])
to const [images, setImages] =useState({})
and
axios.get('https://images-api.nasa.gov/search',{
params:{
q: name
}})
.then(res => setImages(images.concat(res.data.collection.items[0].links[0].href)))
}
to
axios.get('https://images-api.nasa.gov/search',{
params:{
q: name
}})
.then(res =>{
images[name] = res.data.collection.items[0].links[0].href));
setImages({...images});
})
}
Then you can loop with planets
array picking URLs from images
object based on plant name.
Upvotes: 1
Reputation: 4938
You could useEffect
and when the planets array changes, then you could iterate through the name of the planets in the planets array and call the image API.
React.useEffect(() => {
async function fetchAllImages() {
const allImages = [];
for await (const planet of planets) {
const { data } = axios.get('https://images-api.nasa.gov/search',{
params:{
q: planet.name
}})
allImages.push(data);
}
setImages(allImages);
}
fetchAllImages();
}, [planets])
Upvotes: 1
Reputation: 306
You can easily map planets and images based on their array index - given that:
you retrieve them in order
And since there are 8 planets there will be 8 images; So your Array.map
function will look like:
{planets.map((planet,index) =>
(<Block
key = {planet.id}
id = {planet.id}
name = {planet.englishName}
dateDiscovered = {planet.discoveryDate}
mass = {planet.mass.massValue}
massExp = {planet.mass.massExponent}
image = images[index]
/>))}
Upvotes: 1