Combiner useEffect & Routing

Le vrai pouvoir vient de la combinaison de useEffect et du routing pour creer des applications multi-pages avec donnees dynamiques.

L'idee cle

Les dependances de useEffect peuvent venir de useParams() ou useSearchParams(). Quand l'URL change, React met a jour ces valeurs, ce qui re-declenche automatiquement vos effets.

javascript
const { userId } = useParams();

useEffect(() => {
  // Ce code se re-execute automatiquement quand l'URL change !
  fetch(`/api/users/${userId}`);
}, [userId]);

Pattern Principal : Fetch Base sur les Params d'URL

Le pattern le plus courant : recuperer des donnees differentes selon l'URL.

javascript
import { useParams } from 'react-router-dom';
import { useState, useEffect } from 'react';

function UserProfile() {
  const { userId } = useParams();
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchUser() {
      setLoading(true);
      // Pour la clarte, error handling omis - voir section Data Fetching
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/users/${userId}`
      );
      const data = await response.json();
      setUser(data);
      setLoading(false);
    }

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

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

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

// Route
<Route path="/users/:userId" element={<UserProfile />} />

Timeline :

1. User navigue vers /users/1
   → useParams() retourne { userId: "1" }
   → useEffect se declenche
   → Fetch user 1

2. User navigue vers /users/2
   → useParams() retourne { userId: "2" }
   → userId a change → useEffect se re-declenche
   → Fetch user 2

Dependance critique

Toujours mettre les params d'URL dans les dependances de useEffect !

javascript
// ✅ Bon : re-fetch si userId change
useEffect(() => {
  fetch(`/api/users/${userId}`);
}, [userId]);

// ❌ Mauvais : fetch une seule fois, ignore les changements d'URL
useEffect(() => {
  fetch(`/api/users/${userId}`);
}, []);

Pourquoi faut-il mettre userId dans les dependances de useEffect quand on fetch des donnees basees sur l'URL ?

Pattern : Fetch avec Search Params

Les search params (?page=2&sort=name) peuvent aussi servir de dependances pour useEffect. Utile pour la pagination :

javascript
import { useSearchParams } from 'react-router-dom';

function PostList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const [posts, setPosts] = useState([]);

  const page = parseInt(searchParams.get('page') || '1', 10);
  const limit = 10;

  useEffect(() => {
    async function fetchPosts() {
      const start = (page - 1) * limit;
      // Pour la clarte, error handling omis - voir section Data Fetching
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${limit}`
      );
      const data = await response.json();
      setPosts(data);
    }

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

  return (
    <div>
      <h1>Posts - Page {page}</h1>

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

      <div>
        <button
          onClick={() => page > 1 && setSearchParams({ page: page - 1 })}
          disabled={page === 1}
        >
          Precedent
        </button>

        <span>Page {page}</span>

        <button onClick={() => setSearchParams({ page: page + 1 })}>
          Suivant
        </button>
      </div>
    </div>
  );
}

URLs :

/posts → Page 1
/posts?page=2 → Page 2
/posts?page=3 → Page 3

Exemple Complet : Annuaire Utilisateurs

Voici un exemple qui combine tout : routing, fetch avec useParams, et Promise.all pour la performance.

javascript
import { Routes, Route, Link, useParams } from 'react-router-dom';
import { useState, useEffect } from 'react';

// Page 1 : Liste des utilisateurs
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchUsers() {
      // Pour la clarte, error handling omis - voir section Data Fetching
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      const data = await response.json();
      setUsers(data);
      setLoading(false);
    }

    fetchUsers();
  }, []);

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

  return (
    <div>
      <h1>Utilisateurs</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            <Link to={`/users/${user.id}`}>
              {user.name}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

// Page 2 : Profil utilisateur avec ses posts
function UserProfile() {
  const { userId } = useParams();
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

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

      // Fetch en parallele avec Promise.all
      const [userResponse, postsResponse] = await Promise.all([
        fetch(`https://jsonplaceholder.typicode.com/users/${userId}`),
        fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
      ]);

      const userData = await userResponse.json();
      const postsData = await postsResponse.json();

      setUser(userData);
      setPosts(postsData);
      setLoading(false);
    }

    fetchUserData();
  }, [userId]); // Re-fetch si on navigue vers un autre utilisateur

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

  return (
    <div>
      <Link to="/users">Retour a la liste</Link>

      <h1>{user.name}</h1>
      <p>Email : {user.email}</p>

      <h2>Articles de {user.name}</h2>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

// App
function App() {
  return (
    <Routes>
      <Route path="/users" element={<UserList />} />
      <Route path="/users/:userId" element={<UserProfile />} />
    </Routes>
  );
}

Que se passe-t-il quand on navigue de /users/1 a /users/2 avec le meme composant UserProfile ?

Eviter les race conditions

Quand l'utilisateur change de page rapidement, les anciennes requetes peuvent arriver apres les nouvelles et causer des affichages incorrects. Utilisez le pattern AbortController vu dans la section Data Fetching pour annuler les requetes obsoletes dans le cleanup de useEffect.

Recapitulatif

Comprendre, pas memoriser

Ce qu'il faut retenir :

  • Dependances depuis l'URLuseParams() et useSearchParams() fournissent des valeurs reactives pour useEffect
  • Fetch + paramsuseEffect([id]) re-fetch automatiquement quand l'URL change
  • PaginationuseSearchParams() + useEffect([page]) pour naviguer entre les pages
  • Promise.all → lancer plusieurs fetch en parallele pour la performance

C'est en combinant useEffect et routing que vous creez des applications React completes !

Prochaine Etape

Vous maitrisez maintenant useEffect et le routing ! Passons a la pratique avec des exercices concrets pour consolider vos connaissances.