Dev
Dev

Reputation: 385

Make API request for each array element based on the first API response

I'm currently learning how to work with async/await in React JS with hooks. I'm trying to make two API calls to https://restcountries.eu/. The firs call needs to get information about chosen country's borders. Output is ["CAN", "MEX"]. However, I need to get full names of these borders. That's why I want to make the second call. Since I have an array with two items, I need to make a new API call for each of them. I have been stuck with this for a while. What is the proper way to make the second call considering that I have an array from the first call?

import React, { useEffect, useState } from "react";
import axios from "axios";

const App = () => {
    const [countryDetails, setCountryDetails] = useState({});
    const [borderNames, setBorderNames] = useState([]);

   //FIRST API CALL
    useEffect(() => {
        const fetchData = async () => {
            try {
                const response = await axios(
                    `https://restcountries.eu/rest/v2/name/usa?fullText=true`
                );
                setCountryDetails(response.data[0]);
            } catch (err) {
                console.log(err);
            }
        };
        fetchData();
    }, []);


    //SECONS API CALL - doens't work
    useEffect(() => {
        let borderName = [];
        countryDetails.borders.forEach(async border => {
            try {
                const response = await axios(
                    `https://restcountries.eu/rest/v2/alpha?codes=${border}`
                );
                borderName.push(response.data[0].name);
            } catch (err) {
                console.log(err);
            }
        });
     setBorderNames(borderName);
    }, [countryDetails]);

    console.log(countryDetails.borders); //Output: ["CAN", "MEX"]
    console.log(borderNames); //Output needs to be: ["Canada", "Mexico"]
 
    return (
        <div>
            <h2>App</h2>
        </div>
    );
};

export default App;

Upvotes: 0

Views: 1564

Answers (3)

Joshua
Joshua

Reputation: 651

A simple approach would be:

useEffect(() => {
   const exec = async () => {
     const borderNames = await Promise.all(countryDetails.borders.map(border => {
       return axios.get(`https://restcountries.eu/rest/v2/alpha?codes=${border}`
     }))

     setBorderNames(borderNames.map(({data}) => data[0].name));
   };

   exec();
}, [countryDetails]);

Upvotes: 2

b3hr4d
b3hr4d

Reputation: 4528

Your structure has some minor issue, check this example you will find out:

const App = () => {
  const [countryDetails, setCountryDetails] = React.useState({});
  const [borderNames, setBorderNames] = React.useState([]);

  React.useEffect(() => {
    fetch(`https://restcountries.eu/rest/v2/name/usa?fullText=true`)
      .then((res) => res.json())
      .then((data) => setCountryDetails(data[0]))
      .catch((err) => console.log(err));
  }, []);

  React.useEffect(() => {
    let borderName = [];
    if (countryDetails.borders) {
      countryDetails.borders.forEach((border) =>
        fetch(`https://restcountries.eu/rest/v2/alpha?codes=${border}`)
          .then((res) => res.json())
          .then((data) => {
            borderName.push(data[0].name);
            if (borderName.length === countryDetails.borders.length)
              setBorderNames(borderName);
          })
          .catch((err) => console.log(err))
      );
    }
  }, [countryDetails]);
  
  return (
    <div>
      <p>{JSON.stringify(countryDetails.borders)}</p>
      <p>{JSON.stringify(borderNames)}</p>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

<div id="react"></div>

Upvotes: -1

k-wasilewski
k-wasilewski

Reputation: 4623

Your second fetch is async and so, because you don't await it, the useEffect sets the borderNames as an empty array (initial value), before the response arrives.

Apart from awaiting the response, you can just make this change inside your try block:

const response = await axios(
   `https://restcountries.eu/rest/v2/alpha?codes=${border}`);

setBorderNames(prevState => prevState.push(response.data[0].name));

Upvotes: 0

Related Questions