Search code examples
reactjsarraysasynchronousasync-awaitpromise

How to get the value from an asynchronous function for each element of an array and then use the values got to render in a React jsx component


I am trying to display some info that I fetched from an api. I can fetch and display the info as needed, but there is one information that requires another operation to be displayed.

So I get the info for each countries like this:

{
    "name": "Belgium",
    "topLevelDomain": [".be"],
    "alpha2Code": "BE",
    "alpha3Code": "BEL",
    "callingCodes": ["32"],
    "capital": "Brussels",
    "altSpellings": [
      "BE",
      "België",
      "Belgie",
      "Belgien",
      "Belgique",
      "Kingdom of Belgium",
      "Koninkrijk België",
      "Royaume de Belgique",
      "Königreich Belgien"
    ],
    "subregion": "Western Europe",
    "region": "Europe",
    "population": 11555997,
    "latlng": [50.83333333, 4],
    "demonym": "Belgian",
    "area": 30528,
    "gini": 27.2,
    "timezones": ["UTC+01:00"],
    "borders": ["FRA", "DEU", "LUX", "NLD"],
    "nativeName": "België",
    "numericCode": "056",
    "flags": {
      "svg": "https://flagcdn.com/be.svg",
      "png": "https://flagcdn.com/w320/be.png"
    },
    "currencies": [
      {
        "code": "EUR",
        "name": "Euro",
        "symbol": "€"
      }
    ],
    "languages": [
      {
        "iso639_1": "nl",
        "iso639_2": "nld",
        "name": "Dutch",
        "nativeName": "Nederlands"
      },
      {
        "iso639_1": "fr",
        "iso639_2": "fra",
        "name": "French",
        "nativeName": "français"
      },
      {
        "iso639_1": "de",
        "iso639_2": "deu",
        "name": "German",
        "nativeName": "Deutsch"
      }
    ],
    "translations": {
      "br": "Belgia",
      "pt": "Bélgica",
      "nl": "België",
      "hr": "Belgija",
      "fa": "بلژیک",
      "de": "Belgien",
      "es": "Bélgica",
      "fr": "Belgique",
      "ja": "ベルギー",
      "it": "Belgio",
      "hu": "Belgium"
    },
    "flag": "https://flagcdn.com/be.svg",
    "regionalBlocs": [
      {
        "acronym": "EU",
        "name": "European Union"
      }
    ],
    "cioc": "BEL",
    "independent": true
  }

As can be seen, the value of the bordering countries that i got are only codes, not the name "borders": ["FRA", "DEU", "LUX", "NLD"], so I need to tranform them into country names by calling to fetch the api again and get the name. But calling the api and return the value is an asynchronous function.

This is the api call to a local json file :

export async function getCountryByCode(code) {
    const response = await fetch('../data.json');
    const dataObject = await response.json();
    const countries = dataObject.filter( e=> e.alpha3Code==code);
    return countries[0]
}

this is how i get the value:

export async function loader({ params }) {
  console.log(params.code)
  return getCountryByCode(params.code)
}

At first, i simply used array.map, but obviously it didnt work:

 const borderCountriesElement = (country.borders) ? country.borders.map((border, index) => <button key={index}><Link to={`/${border}`}>{getCountryByCode(params.code)}</Link></button>) : (<span> No adjacent countries</span>);

Online they say that I should use Promise.all, but the problem is that I cannot access the value when i need to return jsx in the component, which needs to be outside the .then() block.

  let borderCountries = undefined
  let borderCountriesPromArray = undefined;

  if (country.borders) {
    borderCountriesPromArray = country.borders.map(border => {
      let borderProm = getCountryByCode(border);
      return borderProm
    })
  }

  Promise.all(borderCountriesPromArray).
    then(values =>
    {
      borderCountries = values.map((value)=>{
        return value.name
      })
      console.log(values)
    }
  )

I also tried useEffect(), but this function in useEffect() never got run somehow...

  useEffect(() => {
    (async () => {
      try {
        // await async "fetchBooks()" function
        const tempCountries = await getCountryByCode(params.code);
        setBorderCountries(tempCountries);
      } catch (err) {
        console.log('Error occured when fetching books');
      }
    })();
  }, []);

  const borderCountriesElement = borderCountries.map(
    (e)=>{
      return (
        <button>
          <Link to={`/${e.alpha3Code}`}>{e.name}</Link>
        </button>
      )
    }
  )

Could someone help me? I am very lost


Solution

  • Alrighty, so here's what I came up with. I haven't been able to test this, but hopefully the logic behind it helps you.

    export async function getCountryByCode(code) {
        const response = await fetch("../data.json");
        const dataObject = await response.json();
        const countries = dataObject.filter((e) => e.alpha3Code == code);
        return countries[0];
    }
    
    export async function getBorderCountries(country) {
        if (!country || !country.borders) {
            return null;
        }
    
        const borderCountries = await Promise.all(
            country.borders.map(async (border) => {
                const response = await fetch("your_endpoint?query=" + border);
                const data = await response.json();
                return data[0];
            })
        );
    
        return borderCountries;
    }
    
    export async function loader({ params }) {
        const country = await getCountryByCode(params.code);
        const borderCountries = await getBorderCountries(country);
        return { country, borderCountries };
    }
    

    Try this! In your loader function, the call to getBorderCountries will wait for the previous getCountryByCode API call to finish.