Récupération de Données (Data Fetching)

L'une des utilisations les plus courantes de useEffect est de récupérer des données depuis des APIs externes.

Rappel : fetch & async/await

Avant de plonger dans React, rappelons les bases de fetch.

Fetch Basique

javascript
// Avec Promises
fetch('https://jsonplaceholder.typicode.com/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Erreur:', error));

Avec async/await

javascript
async function fetchUsers() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Erreur:', error);
  }
}

fetchUsers();

fetch retourne une Promise

fetch() est asynchrone : il retourne une Promise qui se résout quand la réponse arrive.

Deux étapes :

  1. await fetch(url) → Attend la réponse HTTP
  2. await response.json() → Parse le JSON

N'oubliez pas le deuxième .json() !

Que se passe-t-il si on oublie d'appeler .json() sur la réponse du fetch ?

Pattern de Base : Fetch dans useEffect

javascript
import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);

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

    fetchUsers();
  }, []); // Tableau vide = fetch une seule fois

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

Pourquoi une fonction interne ?

Vous ne pouvez pas rendre useEffect directement async :

javascript
// ❌ Ne fonctionne pas !
useEffect(async () => {
  const data = await fetch(url);
}, []);

// ✅ Créez une fonction async interne
useEffect(() => {
  async function fetchData() {
    const data = await fetch(url);
  }
  fetchData();
}, []);

Raison : useEffect doit retourner undefined ou une fonction de cleanup, pas une Promise.

Pourquoi crée-t-on une fonction async INTERNE dans useEffect ?

Gérer les États : Loading, Error, Data

Une bonne UX nécessite 3 états :

ÉtatDescriptionUI
LoadingChargement en coursSpinner, skeleton
ErrorErreur survenueMessage d'erreur
SuccessDonnées chargéesAfficher les données

Exemple Complet avec États

javascript
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUsers() {
      try {
        setLoading(true);
        setError(null);

        const response = await fetch('https://jsonplaceholder.typicode.com/users');

        // Vérifier si la réponse est OK
        if (!response.ok) {
          throw new Error(`Erreur HTTP: ${response.status}`);
        }

        const data = await response.json();
        setUsers(data);

      } catch (err) {
        setError(err.message);
        console.error('Erreur de fetch:', err);

      } finally {
        setLoading(false);
      }
    }

    fetchUsers();
  }, []);

  // Gérer les différents états
  if (loading) {
    return <div>Chargement...</div>;
  }

  if (error) {
    return <div>Erreur : {error}</div>;
  }

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

Pattern try/catch/finally

try : Code qui peut échouer catch : Gérer l'erreur finally : Toujours exécuté (utilisez-le pour setLoading(false))

javascript
try {
  // Fetch data
} catch (error) {
  // Gérer erreur
} finally {
  setLoading(false); // Toujours appelé
}

Pourquoi mettre setLoading(false) dans le bloc finally plutôt que dans try et catch séparément ?

Fetch avec Dépendances

Souvent, vous devez re-fetch quand une prop ou un état change.

javascript
function UserPosts({ userId }) {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchPosts() {
      setLoading(true);

      const response = await fetch(
        `https://jsonplaceholder.typicode.com/posts?userId=${userId}`
      );
      const data = await response.json();

      setPosts(data);
      setLoading(false);
    }

    fetchPosts();
  }, [userId]); // Re-fetch quand userId change

  if (loading) return <div>Chargement...</div>;

  return (
    <div>
      <h2>Posts de l'utilisateur {userId}</h2>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

Timeline avec dépendances

userId = 1
  → Fetch posts for user 1
  → Afficher posts de user 1

userId = 2 (changement)
  → Cleanup (si défini)
  → Fetch posts for user 2
  → Afficher posts de user 2

React gère automatiquement le re-fetch !

Pour aller plus loin : AbortController

Si le composant disparait ou les dependances changent pendant le fetch, la requete en cours peut causer des problemes (affichage de donnees obsoletes). L'AbortController permet d'annuler ces requetes.

javascript
function UserData({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    async function fetchUser() {
      try {
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/users/${userId}`,
          { signal: controller.signal }
        );
        const data = await response.json();
        setUser(data);
      } catch (error) {
        if (error.name !== 'AbortError') {
          console.error('Erreur:', error);
        }
      }
    }

    fetchUser();

    // Cleanup : annuler la requete si userId change ou composant disparait
    return () => controller.abort();
  }, [userId]);

  return <div>{user?.name || 'Loading...'}</div>;
}

Pourquoi ? Sans annulation, si vous changez rapidement userId, une ancienne reponse lente peut arriver apres une nouvelle reponse rapide et ecraser les bonnes donnees. C'est ce qu'on appelle une race condition.

Que fait AbortController dans le contexte d'un fetch avec useEffect ?

Envie de reutiliser cette logique ?

Vous remarquerez que le pattern fetch + loading + error se repete souvent. En Seance 5, vous apprendrez a creer des custom hooks (comme useFetch) pour factoriser cette logique et eviter la repetition.

POST : Envoyer des Donnees

javascript
function CreatePost() {
  const [title, setTitle] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  async function handleSubmit(e) {
    e.preventDefault();

    try {
      setLoading(true);
      setError(null);

      const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          title,
          body: 'Lorem ipsum',
          userId: 1,
        }),
      });

      if (!response.ok) {
        throw new Error('Erreur lors de la création');
      }

      const newPost = await response.json();
      console.log('Post créé:', newPost);

      // Reset le formulaire
      setTitle('');

    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Titre du post"
        disabled={loading}
      />

      <button type="submit" disabled={loading}>
        {loading ? 'Envoi...' : 'Créer'}
      </button>

      {error && <p style={{ color: 'red' }}>Erreur : {error}</p>}
    </form>
  );
}

POST vs GET

GET (par défaut) : Récupérer des données

javascript
fetch(url) // GET implicite

POST : Envoyer des données

javascript
fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(data)
})

Autres méthodes : PUT, PATCH, DELETE

Quelle est la différence entre un fetch GET et un fetch POST ?

Pattern : Rafraîchir les Données

javascript
function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [refreshKey, setRefreshKey] = useState(0);

  useEffect(() => {
    async function fetchPosts() {
      setLoading(true);
      const response = await fetch('https://jsonplaceholder.typicode.com/posts');
      const data = await response.json();
      setPosts(data);
      setLoading(false);
    }

    fetchPosts();
  }, [refreshKey]); // Re-fetch quand refreshKey change

  function handleRefresh() {
    setRefreshKey(key => key + 1); // Change refreshKey → re-fetch !
  }

  return (
    <div>
      <button onClick={handleRefresh} disabled={loading}>
        {loading ? 'Chargement...' : 'Rafraîchir'}
      </button>

      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

APIs Publiques pour Pratiquer

APIs gratuites pour tester

JSONPlaceholder (pas d'auth requise)

javascript
// Users
https://jsonplaceholder.typicode.com/users

// Posts
https://jsonplaceholder.typicode.com/posts

// Posts d'un user
https://jsonplaceholder.typicode.com/posts?userId=1

Random User API

javascript
// 10 utilisateurs aléatoires
https://randomuser.me/api/?results=10

REST Countries

javascript
// Tous les pays
https://restcountries.com/v3.1/all

// Recherche par nom
https://restcountries.com/v3.1/name/france

Erreurs Courantes

Erreur 1 : Oublier .json()

javascript
// ❌ Mauvais
const data = await fetch(url);
console.log(data); // [object Response]

// ✅ Bon
const response = await fetch(url);
const data = await response.json(); // Parser le JSON
console.log(data); // { ... }

Erreur 2 : Ne Pas Vérifier response.ok

javascript
// ❌ Pas de gestion d'erreur HTTP
const response = await fetch(url);
const data = await response.json();

// ✅ Vérifier response.ok
const response = await fetch(url);
if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();

Récapitulatif

Comprendre, pas mémoriser

Ce qu'il faut retenir :

  • Fetch dans useEffect → fonction async interne + try/catch/finally
  • 3 etats a gererloading, error, data
  • [] → fetch au mount uniquement
  • [userId] → re-fetch quand userId change
  • response.ok → toujours verifier le statut HTTP

Avec la pratique, ce pattern deviendra automatique. Consultez la documentation MDN sur Fetch API si vous avez un doute.

Prochaine Étape

Maintenant que vous savez récupérer des données, découvrons comment créer des applications multi-pages avec React Router !