Wat is Data Fetching?

Data fetching is het ophalen van data van een server/API. In React gebruik je de fetch() functie binnen een useEffect hook.

Typisch patroon:

  1. Component mount → useEffect
  2. Fetch data van API
  3. Zet data in state
  4. Component rendert opnieuw met data

Basis Fetch met .then()

import { useState, useEffect } from 'react';

const Users = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(response => response.json())
      .then(data => setUsers(data));
  }, []); // Lege array = 1x uitvoeren

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

export default Users;

Stappen:

  1. fetch(url) - Haal data op
  2. .then(res => res.json()) - Converteer naar JSON
  3. .then(data => setUsers(data)) - Zet in state

Fetch met async/await (beter!)

Async/await is moderner en leesbaarder dan .then():

import { useState, useEffect } from 'react';

const Users = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const fetchUsers = async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      const data = await response.json();
      setUsers(data);
    };

    fetchUsers();
  }, []);

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

Let op: Je kunt useEffect zelf niet async maken! Maak een aparte async functie binnen useEffect.

Met Loading en Error Handling

import { useState, useEffect } from 'react';

const Users = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        setLoading(true);
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        
        if (!response.ok) {
          throw new Error('Er ging iets mis');
        }
        
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

Zie het in actie — de drie states

Een API-call kost tijd. In die tijd moet je gebruiker iets zien. Links zie je wat je app-gebruiker ziet, rechts wat er in je state gebeurt. Klik op "Fetch users" en kijk hoe de drie fases voorbij komen.

Wat de gebruiker ziet
Klik op "Fetch users" om data te laden
De state in je code
loading
false
error
null
data
null
Timeline
Klik op "Fetch users" om te starten...

De drie fases die je net zag:

  1. Loading — request gestart, wachten op antwoord. Toon een spinner of skeleton.
  2. Success — data binnen. Toon de data.
  3. Error — er ging iets fout. Toon een nette foutmelding.

Zet de toggle "Laat volgende request falen" aan en fetch opnieuw — zie de error state in actie.

POST Request (data versturen)

Om data naar een API te versturen gebruik je een POST request:

const CreateUser = () => {
  const [name, setName] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);

    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ name })
      });

      const data = await response.json();
      console.log('User created:', data);
      setName(''); // Reset form
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Naam"
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Bezig...' : 'Maak User'}
      </button>
    </form>
  );
};

POST request opties:

  • method: 'POST' - Type request
  • headers - Metadata (bijv. Content-Type)
  • body - Data als JSON string

Praktische Voorbeelden

Single Item Fetchen

const UserDetail = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchUser = async () => {
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
      const data = await response.json();
      setUser(data);
      setLoading(false);
    };

    fetchUser();
  }, [userId]); // Re-fetch als userId verandert

  if (loading) return <p>Loading...</p>;
  if (!user) return <p>User not found</p>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
};

Update (PUT/PATCH)

const updateUser = async (userId, newData) => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(newData)
  });

  const data = await response.json();
  return data;
};

Delete

const deleteUser = async (userId) => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, {
    method: 'DELETE'
  });

  if (response.ok) {
    console.log('User deleted');
  }
};

Refetch Button

const Users = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);

  const fetchUsers = async () => {
    setLoading(true);
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const data = await response.json();
    setUsers(data);
    setLoading(false);
  };

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

  return (
    <div>
      <button onClick={fetchUsers} disabled={loading}>
        {loading ? 'Loading...' : 'Refresh'}
      </button>
      
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};