Comment différer le chargement des données en React ?

Comment activer le chargement différé des données avec Remix (defer)
En développant une application React, vous avez surement déjà eu avoir à charger des données depuis une API externe. Pour ce faire, plusieurs solutions s'offrent à vous :
- les charger côté client (dans un useEffect)
- les charger côté serveur (avec un framework comme Remix ou NextJS)
Mais il existe une troisième option (si vous utilisez Remix) : Déclencher le chargement des donnés côté serveur, mais ne pas attendre la réponse du serveur pour afficher la page à l'utilisateur. Découvrons comment avec des exemples :
Chargement côté client
Voici la syntaxe pour des données côté client
1import { useEffect, useState } from 'react';23type UserType = { id: number; email: string };45const loadUsers = async () => {6const response = await fetch('https://jsonplaceholder.typicode.com/users');7const users = (await response.json()) as UserType[];8return users;9};1011const Component = () => {12const [users, setUsers] = useState<UserType[]>([]);13useEffect(() => {14loadUsers().then((data) => setUsers(data));15}, []);1617return (18<ul>19{users.map((user) => (20<li key={user.id}>{user.email}</li>21))}22</ul>23);24};
On affiche une liste d'utilisateurs, suite à un appel à l'API de JsonPlaceholder. Comme le chargement se fait côté client, la page va se charger très vite. Cependant, le visiteur ne verra pas tout de suite la liste des utilisateurs. Il faut attendre que l'API externe envoie une réponse positive, contenant la donnée.
Il y aura ensuite du Cumulative Layout Shift (CLS). Au chargement de la donnée, la liste va s'afficher et les éléments en dessous de la liste seront décallés vers le bas.
Certaines applications affichent même des indicateurs de chargement pour expliquer au visiteur qu'il y aura bientôt la donnée.
Pour remédier à ce problème, il suffit de charger la donnée côté serveur. Elle apporte trois améliorations :
- Plus besoin d'afficher d'indicateur de chargement
- Plus besoin de gérer la logique de chargement des données côté client
- Pas de CLS (La liste aura toujours été là, il n'y aura plus de décallage
Chargement côté serveur avec Remix
Pour charger notre liste côté serveur en utilisant Remix, il suffit de déplacer la logique de chargement dans une méthode loader.
1import { json } from '@remix-run/node';2import { useLoaderData } from '@remix-run/react';3type UserType = { id: number; email: string };4const loadUsers = async () => {5const response = await fetch('https://jsonplaceholder.typicode.com/users');6const users = (await response.json()) as UserType[];7return users;8};9export const loader = async () => {10const users = await loadUsers();11return json(users);12};13const Component = () => {14const users = useLoaderData<typeof loader>();15return (16<ul>17{users.map((user) => (18<li key={user.id}>{user.email}</li>19))}20</ul>21);22};23export default Component;
Nous utilisons ensuite le hook spécial de Remix, useLoaderData pour récupérer les données renvoyées par le serveur (suite à l'exécution de la méthode loader).
Malgré les avantages énumérés plus haut, cette optimisation nous rajoute un inconvénient :
Nous devons attendre que les données soient chargées avant d'afficher la page. Le visiteur devra attendre plus longtemps devant une page blanche. Sauf que l'équipe de développement de Remix y a pensé. Nous pouvons utiliser la méthode defer pour les requêtes lentes et secondaires.
Implémenter le chargement différé de Remix
Pour charger nos données en différé, nous avons besoin de remplacer la méthode json de Remix par une méthode *defer.
De plus, au lieu d'utiliser await pour attendre le chargement de la donnée directement, nous l'appelons sans le mot-clé. Cela signifie que n'attendons pas la réponse de l'API avant de renvoyer notre donnée.
1import { defer } from '@remix-run/node';2import { Await, useLoaderData } from '@remix-run/react';3import { Suspense } from 'react';4type UserType = { id: number; email: string };5const loadUsers = async () => {6const response = await fetch('https://jsonplaceholder.typicode.com/users');7const users = (await response.json()) as UserType[];8return users;9};10export const loader = async () => {11const usersPromise = loadUsers();12return defer({ usersPromise });13};14const Component = () => {15const { usersPromise } = useLoaderData<typeof loader>();16return (17<Suspense fallback={<span>Chargement des utilisateurs</span>}>18<Await19resolve={usersPromise}20errorElement={<span>Erreur lors du chargement des utilisateurs</span>}21>22{(users) => (23<ul>24{users.map((user) => (25<li key={user.id}>{user.email}</li>26))}27</ul>28)}29</Await>30</Suspense>31);32};33export default Component;
Nous avons également utilisé le composant Suspense, qui est nécessaire pour utiliser le chargement différé. Analysons maintenant la syntaxe du composant Await. Durant le chargement des données, le composant Suspense affiche un message de chargement (déclaré dans le prop fallback). Une fois le chargement terminé, le composant Await qui reçoit en prop notre promesse usersPromise va nous afficher le composant enfant : notre liste d'utilisateurs.