Liam
Liam

Reputation: 33

Handle two APIs at the same time

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

Answers (6)

Liam
Liam

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

Jason
Jason

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

Drew Reese
Drew Reese

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

Edit handle-two-apis-at-the-same-time

Upvotes: 1

Rahul Kant
Rahul Kant

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

Prateek Thapa
Prateek Thapa

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

theSereneRebel
theSereneRebel

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

Related Questions