Let's first make this "wrong" first - the problem where the UI locks up during transitional times.

Let's make a Score.jsx file

const loadingUrl = "/images/loading.webp";
export default function Score({
  isPending,
  home,
  away,
  awayName,
  homeName,
  awayImage,
  homeImage,
}) {
  return (
    <div className="score">
      <div>
        <h2>{isPending ? "HOME" : homeName}</h2>
        <h3>{isPending ? "–" : home}</h3>
        <img src={isPending ? loadingUrl : homeImage} alt="home team logo" />
      </div>
      <div>
        <h2>{isPending ? "AWAY" : awayName}</h2>
        <h3>{isPending ? "–" : away}</h3>
        <img src={isPending ? loadingUrl : awayImage} alt="away team logo" />
      </div>
    </div>
  );
}
  • Key here is that if the score is pending, we show a dash. That will visually indicate to the user we haven't loaded it yet.

Okay, let's make a quick function fetch from the API. Make a file called getScore.js

export default async function getScore(game) {
  const response = await fetch("/score?game=" + game);
  const score = await response.json();
  return score;
}

No surprises here.

Let's go modify our App.jsx. Put this in there

import { useState, useEffect } from "react";
import Score from "./Score";
import getScore from "./getScore";

export default function App() {
  const [isPending, setIsPending] = useState(true);
  const [game, setGame] = useState(1);
  const [score, setScore] = useState({ home: "–", away: "–" });

  async function getNewScore(game) {
    setIsPending(true);
    setGame(game);
    const newScore = await getScore(game);
    setScore(newScore);
    setIsPending(false);
  }

  useEffect(() => {
    getNewScore(game);
  }, []);

  return (
    <div className="app">
      <h1>Game {game}</h1>
      <select
        disabled={isPending}
        onChange={(e) => {
          getNewScore(e.target.value);
        }}
      >
        <option value={1}>Game 1</option>
        <option value={2}>Game 2</option>
        <option value={3}>Game 3</option>
        <option value={4}>Game 4</option>
        <option value={5}>Game 5</option>
        <option value={6}>Game 6</option>
        <option value={7}>Game 7</option>
      </select>
      <div className={`loading-container ${isPending ? "loading" : ""}`}>
        {" "}
        <span className="spinner">⚽️</span>
      </div>
      <div>
        <Score
          isPending={isPending}
          homeImage={score.homeImage}
          homeName={score.homeName}
          awayImage={score.awayImage}
          awayName={score.awayName}
          home={score.home}
          away={score.away}
        />
      </div>
    </div>
  );
}
  • So this works, and this is how most people would have coded this - just wait until the API request finishes.
  • Why do we need to disable the select while stuff is loading? Because if we don't people can make multiple requests in that time, and they'll return in a jumbled order and it'll freak people out. We have to make sure it finishes it first so we don't have a UI that's responding to old requests.