J'essaie d'utiliser la nouvelle fonctionnalité expérimentale de React Suspense pour la récupération de données.

Voici mon crochet simple useApi qui (si je comprends bien Suspense) renvoie le résultat d'un appel fetch ou lance la promesse de suspension. (légèrement modifié l'exemple documenté)

function useApi(path) {
  const ref = React.useRef({ time: +new Date() });
  if (!ref.current.suspender) {
    ref.current.suspender = fetch(path).then(
      data => ref.current.data = data,
      error => ref.current.error = error,
    );
  }
  if (ref.current.data) return ref.current.data;
  if (ref.current.error) return ref.current.error;
  throw ref.current.suspender;
}

J'utilise ce crochet simplement comme ceci :

function Child({ path }) {
  const data = useApi(path);
  return "ok";
}
export default function App() {
  return (
    <Suspense fallback="Loading…">
      <Child path="/some-path" />
    </Suspense>
  );
}

Cela ne se résout jamais.

Je pense que le problème est que useRef ne fonctionne pas tout à fait comme il est censé le faire.

Si j'initialise la référence avec une valeur aléatoire, elle ne conserve pas cette valeur et est plutôt réinitialisée avec une autre valeur aléatoire :

const ref = React.useRef({ time: +new Date() });
console.log(ref.current.time)
1602067347386
1602067348447
1602067349822
1602067350895
...

Il y a quelque chose de bizarre à lancer le suspender qui provoque la réinitialisation du useRef à chaque appel.

throw ref.current.suspender;

Si je supprime cette ligne, useRef fonctionne comme prévu, mais évidemment Suspense ne fonctionne pas.

Une autre façon de le faire fonctionner est d'utiliser une sorte de mise en cache personnalisée en dehors de React, comme :

const globalCache = {}
function useApi(path) {
  const cached = globalCache[path] || (globalCache[path] = {});
  if (!cached.suspender) {
    cached.suspender = ...
  }
  if (cached.data) ...;
  if (cached.error) ...;
  throw cached.suspender;
}

Cela le fait également fonctionner, mais je préférerais utiliser quelque chose que React lui-même fournit en termes de mise en cache des données spécifiques aux composants.

Est-ce que j'ai raté quelque chose sur la façon dont useRef est censé ou non fonctionner avec Suspense ?

Repro : https://codesandbox.io/s/falling-paper-shps2

1
laggingreflex 7 oct. 2020 à 13:53

1 réponse

J'ai été aux prises avec le même problème, mais je pense qu'il est en fait possible d'obtenir ce que vous voulez. J'ai regardé les implémentations de react-async et SWR et j'ai remarqué que react-async ne se lance pas sur le premier rendu, mais il utilise useEffect(...) pour démarrer l'opération async, combiné avec un setState qui déclenche un autre rendu, puis lance la promesse sur les rendus suivants (jusqu'à ce qu'il se résolve). Je crois que SWR se comporte en fait de la même manière, avec une différence mineure ; SWR utilise useLayoutEffect (avec un recours à useEffect pour le rendu côté serveur), ce qui présente un avantage majeur : le rendu initial sans données ne se produit jamais.

Cela signifie que le composant parent doit encore faire face à l'abondance de données. Le premier rendu peut être utilisé pour démarrer la promesse, mais doit toujours revenir sans lancer pour éviter la boucle infinie. Ce n'est qu'au deuxième rendu que la promesse sera lancée, ce qui suspendra réellement le rendu.

1
mycroes 13 janv. 2021 à 15:19