Home > Software engineering >  React: I believe I have a GOTCHA for a double async fetch call. I click the button once part of the
React: I believe I have a GOTCHA for a double async fetch call. I click the button once part of the

Time:09-07

Question:

I am developing a small app that is a memory game of Formula One Drivers to practice React. It makes a call to an API to get the driver info then I have to make a second API call to Wikipedia to get the driver images. When I submit the year and click the button it will only load half the information Image 1 & getDrivers function. When I click the button again it will load the images Image 2 & getDriversImgs function / retrievingImgUrl.

I believe I am encountering a GOTCHA or doing something fundamentally wrong. I am not sure in my setDrivers call in the retrievingImgUrl() function if it isn't updating because it is a reference to an array even though I use map and it should be returning a new array?

Or is this something where I need to use useEffect or useCallback to have the code rerender in one go?

Any advice on how to fix the bug and if you could point me in a direction to possibly clean up these fetch calls or would you consider this clean code (like conceptually chaining fetch calls together in smaller functions or should I make it one big function)?

import { Fragment, useState, useEffect } from "react";

// Components
import Header from "./components/header/Header";
import CardList from "./components/main/CardList";
import Modal from "./components/UI/Modal";

// CSS
import classes from "./App.module.css";

function App() {
  const [drivers, setDrivers] = useState([]);

  const getDrivers = async (year) => {
    const response = await fetch(
      "https://ergast.com/api/f1/"   year   "/drivers.json"
    );
    const data = await response.json();

    let driverInfo = [];
    data.MRData.DriverTable.Drivers.map((driver) => {
      driverInfo.push({
        id: driver.code,
        firstName: driver.givenName,
        lastName: driver.familyName,
        wikipedia: driver.url,
        image: null,
      });
    });
    setDrivers(driverInfo);
    getDriversImgs();
  };

  async function getDriversImgs() {
    console.log(drivers);
    const responses = await Promise.all(
      drivers.map((driver) => {
        let wikiPageName = driver.wikipedia.split("/").slice(-1).toString();
        let wiki_url =
          "https://en.wikipedia.org/w/api.php?origin=*&action=query&titles="  
          wikiPageName  
          "&prop=pageimages&format=json&pithumbsize=500";
        return fetch(wiki_url);
      })
    );
    const urls = await Promise.all(responses.map((r) => r.json())).then(
      (json) => retrievingImgUrl(json)
    );
    setDrivers((prev) => {
      return prev.map((item, idx) => {
        return { ...item, image: urls[idx] };
      });
    });
  }

  const retrievingImgUrl = async (data) => {
    console.log(data);
    const strippingData = data.map((d) => {
      return d.query.pages;
    });
    const urls = strippingData.map((d) => {
      const k = Object.keys(d)[0];
      try {
        return d[k].thumbnail.source;
      } catch {
        return null;
      }
    });
    return urls;
  };


  return (
    <Fragment>
      <Header getDrivers={getDrivers} />
      <CardList drivers={drivers} />
    </Fragment>
  );
}

export default App; 

Image 1 (clicked button once): enter image description here

Image 2 (clicked button twice): enter image description here

Object20Object error:

const Header = (props) => {
  const driverYear = useRef();

  const driverYearHandler = (e) => {
    e.preventDefault();
    console.log(driverYear);
    const year = driverYear.current.value;
    console.log(typeof year);
    props.getDrivers(year.toString());
  };

  return (
    <header className={classes.header}>
      <Title />
      <form onSubmit={driverYearHandler}>
        {/* <label htmlFor="year">Enter Year:</label> */}
        <input
          type="text"
          id="year"
          ref={driverYear}
          placeholder="Enter Year:"
        />
        <button onClick={props.getDrivers}>Get Drivers</button>
      </form>
    </header>
  );
};

export default Header;

Console Error: enter image description here enter image description here

UPDATED FETCH CALL

const getDrivers = async (year) => {
    console.log("Running more than once??");
    const url = "https://ergast.com/api/f1/"   year   "/drivers.json";
    const response = await fetch(url);
    const data = await response.json();

    let driverInfo = [];
    data.MRData.DriverTable.Drivers.map((driver) => {
      driverInfo.push({
        id: driver.code,
        firstName: driver.givenName,
        lastName: driver.familyName,
        wikipedia: driver.url,
        image: null,
      });
    });
    getDriversImgs(driverInfo).then((data) => setDrivers(data));
    console.log("Here is driver info", driverInfo);
  };

  const getDriversImgs = async (driverInfo) => {
    const responses = await Promise.all(
      driverInfo.map((driver) => {
        let wikiPageName = driver.wikipedia.split("/").slice(-1).toString();
        let wiki_url =
          "https://en.wikipedia.org/w/api.php?origin=*&action=query&titles="  
          wikiPageName  
          "&prop=pageimages&format=json&pithumbsize=500";
        return fetch(wiki_url);
      })
    );
    const urls = await Promise.all(responses.map((r) => r.json())).then(
      (json) => retrievingImgUrl(json)
    );
    return driverInfo.map((item, idx) => {
      return { ...item, image: urls[idx] };
    });
  };

  const retrievingImgUrl = async (data) => {
    const strippingData = data.map((d) => {
      return d.query.pages;
    });
    const urls = strippingData.map((d) => {
      const k = Object.keys(d)[0];
      try {
        return d[k].thumbnail.source;
      } catch {
        return null;
      }
    });
    return urls;
  };

CodePudding user response:

This is likely happening because of a small misunderstanding with setState. You are calling getDriversImgs() just after setDrivers() is called, but any set state function is asynchronous. It is likely not done setting before you look for the driver's image.

The simplest solution in my opinion will be to not setDrivers until you've correlated an image to each driver. You already have all of your driverInfo in an array, so iterating through that array and finding the image for the driver should be quite straightforward.

After you've created a driverInfo array that includes the driver's image, then you can use setDrivers which will render it to the DOM.

  • Related