WebService aanroepen

Tot nu toe hebben we de data rechtstreeks van de applicatie code zelf gebruikt. In de meeste dynamische webapplicaties gebruik je een externe web service met een of andere dataopslag (SQL-database, files, ...). In dit deel gaan we onze webapplicatie aanpassen zodat die gebruik maakt van de Movie Web Service.

Bij het oproepen van een web service krijg je nooit direct het antwoord terug. Er zal altijd een kleine vertraging opzitten omdat deze via het internet zal verlopen, daarom moet deze asynchroon gebeuren. Soms wordt zo'n web service call ook een AJAX call genoemd.

Er zijn veel verschillende libraries om AXJAX calls te maken in React applicaties. Hieronder vindt je een lijst van veel gebruikte libraries:

  • Axios

  • Fetch (ingebouwd in elke browser)

  • Superagent

  • React-axios

  • Use-http

  • React-request

We kunnen hier in principe eender welke library gebruiken maar we gaan Fetch gebruiken omdat deze geen externe library nodig heeft en ingebouwd is in elke moderne browser.

Omdat onze games niet meer in het begin beschikbaar zijn in onze applicatie zorgen we er nu voor dat de state op een lege array wordt gezet bij het initializeren van het App component.

const [games, setGames] = useState<Game[]>([]);

We gaan nu even terug naar wat we gezien hebben in het onderdeel side-effects. We hebben daar de useEffect hook gezien en we hebben daar afgesproken dat alle dingen die bestaat uit communicatie met een externe service via effects moet gebeuren. We hebben daar ook gezien dat we altijd een lege array mee moeten geven als we deze effect maar 1 keer willen laten uitvoeren bij de eerste render van het component.

useEffect(() => {
  setTimeout(() => {
    setGames([
    {
      id: 0,
      name: "World of Warcraft",
      releaseYear: 2004,
      sales: 0
    },
    {
      id: 1,
      name: "Valheim",
      releaseYear: 2021,
      sales: 0
    },
    {
      id: 2,
      name: "Minecraft",
      releaseYear: 2011,
      sales: 0
    }
    ]);
  },1000);
},[])

Je ziet hier dat we voorlopig toch gewoon onze initiële array hier opvullen met onze voorbeeld data. We vullen deze wel pas op na 1 seconde. Dit doen we aan de hand van de javascript setTimeout functie. We doen dit omdat we soms een API call willen nabootsen om dingen eerst te testen of omdat de API op dat moment nog niet klaar is. De reden dat we pas de setGame gebruiken na 1 seconde is omdat we een tragere netwerk request willen naboosten die ongeveer 1 seconde duurt.

Als je nu naar onze webapplicatie naar de /list path zou gaan met je browser zal je zien dat de lijst eerst leeg zal zijn en dan na een seconde gevuld zal worden. Dit kan voor de gebruiker een beetje raar zijn, daarom gaan we nu een extra updating state invoeren zodat we een loading indicator kunnen laten zien aan de gebruiker terwijl de data laadt.

Bij aanvang van onze nagemaakte API call zetten we updating op true en nadat de data is aangepast dan zetten we deze terug op false.

const [updating, setUpdating] = useState(true);

useEffect(() => {
  setUpdating(true);
  setTimeout(() => {
    setGames([
      {
        id: 0,
        name: "World of Warcraft",
        releaseYear: 2004,
        sales: 0
      },
      {
        id: 1,
        name: "Valheim",
        releaseYear: 2021,
        sales: 0
      },
      {
        id: 2,
        name: "Minecraft",
        releaseYear: 2011,
        sales: 0
      }
    ]);
    setUpdating(false);
  },1000);
},[])

We kunnen nu nakijken of updating op true staat voordat we de rest van onze layout gaan renderen. Als updating true is dan tonen we een div met de text 'Loading data' en anders tonen we gewoon onze Switch van onze routing.

return (
  <Router>
    <div>
      <Header />
      {updating ? <div className={styles.container}>Loading data</div> :
      <div className={styles.container}>
        <Switch>
          <Route path="/list">
            <ListPage games={games} handleAdd={handleAdd} />
          </Route>
          <Route path="/detail/:id">
            <DetailPage games={games} />
          </Route>
          <Route path="/" exact>
            <HomePage />
          </Route>
        </Switch>

      </div>}
    </div>
  </Router>
);

We kunnen deze loading text ook nog vervangen met een loading spinner aan de hand van wat css magie:

{updating ? <div className={styles.loaderContainer}><div className={styles.loader}></div></div> :

en in onze App.module.css maken we dan deze classes aan zodat we deze loader kunnen laten draaien aan de hand van keyframes

.loaderContainer {
  display: flex;
  align-items: center;
  height: 100vh;
  justify-content: center;
}

.loader {
  border: 4px solid #f3f3f3; /* Light grey */
  border-top: 4px solid #3498db; /* Blue */
  border-radius: 50%;
  width: 64px;
  height: 64px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

We gaan nu de eigenlijke web service aanspreken om de games op te halen. We maken een aparte functie voor het ophalen van games van de API. De getGames functie duiden we aan als async functie omdat we er gebruik gaan maken van async/await omdat er asynchrone code in deze functie staat (het ophalen van de games via de API).

Net als bij de nagebootste call van hiervoor zetten we eerst de updating state eerst op true. Vervolgens doen we de fetch call naar http://localhost:3001/games met de GET methode. Deze fetch methode geeft de response terug als stream, dus deze moet nog helemaal omgezet worden naar json aan de hand van de response.json() methode. Beide fetch en json zijn functies die een promise terug geven dus hiervoor kan het await keyword geplaatst worden zodat er gewacht wordt tot de promise afgehandeld is. Als we het resultaat volledig hebben gebruiken we de setGames methode om de state van de games aan te passen.

const getGames = async () => {
  setUpdating(true);

  let response = await fetch('http://localhost:3001/games',{
    method: 'GET', // *GET, POST, PUT, DELETE, etc.
  });
  let json = await response.json();

  setGames(json as Game[]);

  setUpdating(false);
}

Nu kunnen we deze methode aanroepen in onze effect

useEffect(() => {
  getGames();
},[]);

Met Fetch kunnen we uiteraard ook POST requests doen naast GET requests. Zo kunnen we heel eenvoudig onze handleAdd functie aanpassen zodat die nu een Game toevoegt aan de hand van de POST op /games. Net als bij de getGames zetten we eerst setUpdating op true zodat de loader getoond wordt. We geven nu een method: POST mee aan de fetch functie en plaatsen we het game object in de body. We moeten het wel eerst naar string omzetten. De POST op /games geeft een json terug met alle games in zodat we deze response kunnen gebruiken om onze games state te updaten.

const handleAdd = async (game: Game) => {
  setUpdating(true);
  let response = await fetch('http://localhost:3001/games',{
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(game)
  });
  let json = await response.json();
  setGames(json as Game[]);
  setUpdating(false);
}

Last updated

Was this helpful?