Catégories
Plugin et site web

Comment créer un crochet React personnalisé pour récupérer et mettre en cache des données – Smashing Magazine

A propos de l'auteur

Ingénieur front-end passionné de performances et de technologies de pointe.
Plus à propos
Ademola

Il est fort probable que de nombreux composants de votre application React devront appeler une API pour récupérer des données qui seront affichées pour vos utilisateurs. Il est déjà possible de le faire en utilisant le componentDidMount() méthode de cycle de vie, mais avec l'introduction des crochets, vous pouvez créer un crochet personnalisé qui récupérera et mettra en cache les données pour vous. C’est ce que ce didacticiel couvrira.

Si vous êtes un novice de React Hooks, vous pouvez commencer par consulter la documentation officielle pour la comprendre. Après cela, je recommanderais de lire «Prise en main de React Hooks API» de Shedrack Akintayo. Pour vous assurer de suivre, il y a aussi un article écrit par Adeneye David Abiodun qui couvre les meilleures pratiques avec React Hooks qui, je suis sûr, vous sera utile.

Tout au long de cet article, nous utiliserons l'API Hacker News Search pour créer un hook personnalisé que nous pouvons utiliser pour récupérer des données. Bien que ce didacticiel couvre l'API Hacker News Search, le crochet fonctionnera de manière à ce qu'il renvoie la réponse de tout valide Lien API que nous lui transmettons.

Meilleures pratiques avec React

React est une fantastique bibliothèque JavaScript pour créer des interfaces utilisateur riches. Il fournit une excellente abstraction de composants pour organiser vos interfaces en un code qui fonctionne bien, et il y a à peu près tout ce que vous pouvez l'utiliser. Lire plus d'articles sur React →

Récupération de données dans un composant React

Avant les hooks React, il était classique de récupérer les données initiales componentDidMount() méthode de cycle de vie et données basées sur les changements d’hélice ou d’état componentDidUpdate() méthode du cycle de vie.

Voici comment ça fonctionne:

componentDidMount() {
  const fetchData = async () => {
    const response = await fetch(
      `https://hn.algolia.com/api/v1/search?query=JavaScript`
    );
    const data = await response.json();
    this.setState({ data });
  };
  
  fetchData();
}


componentDidUpdate(previousProps, previousState) {
    if (previousState.query !== this.state.query) {
      const fetchData = async () => {
        const response = await fetch(
          `https://hn.algolia.com/api/v1/search?query=${this.state.query}`
        );
        const data = await response.json();
        this.setState({ data });
      };

      fetchData();
    }
  }

le componentDidMount la méthode du cycle de vie est invoquée dès que le composant est monté, et lorsque cela est fait, ce que nous avons fait était de faire une demande de recherche de «JavaScript» via l'API Hacker News et de mettre à jour l'état en fonction de la réponse.

le componentDidUpdate La méthode du cycle de vie, en revanche, est invoquée en cas de modification du composant. Nous avons comparé la requête précédente dans l'état avec la requête actuelle pour éviter que la méthode ne soit invoquée chaque fois que nous définissons «data» dans l'état. L'utilisation des crochets nous permet de combiner les deux méthodes de cycle de vie de manière plus propre, ce qui signifie que nous n'aurons pas besoin de deux méthodes de cycle de vie pour le montage et la mise à jour du composant.

Récupération de données avec useEffect Crochet

le useEffect le crochet est appelé dès que le composant est monté. Si nous avons besoin que le hook soit réexécuté en fonction de certains changements d’hélice ou d’état, nous devons les transmettre au tableau de dépendances (qui est le deuxième argument du useEffect crochet).

Voyons comment récupérer des données avec des hooks:

import { useState, useEffect } from 'react';

const (status, setStatus) = useState('idle');
const (query, setQuery) = useState('');
const (data, setData) = useState(());

useEffect(() => {
    if (!query) return;

    const fetchData = async () => {
        setStatus('fetching');
        const response = await fetch(
            `https://hn.algolia.com/api/v1/search?query=${query}`
        );
        const data = await response.json();
        setData(data.hits);
        setStatus('fetched');
    };

    fetchData();
}, (query));

Dans l'exemple ci-dessus, nous avons réussi query comme une dépendance à notre useEffect crochet. En faisant cela, nous disons useEffect pour suivre les changements de requête. Si le précédent query n'est pas la même que la valeur actuelle, la valeur useEffect être invoqué à nouveau.

Cela dit, nous définissons également plusieurs status sur le composant selon les besoins, car cela transmettra mieux un message à l'écran en fonction de certains états finis status. dans le tourner au ralenti état, nous pourrions faire savoir aux utilisateurs qu'ils pourraient utiliser le champ de recherche pour commencer. dans le aller chercher état, nous pourrions montrer un fileur. Et, dans le récupéré état, nous rendrons les données.

Il est important de définir les données avant d'essayer de définir le statut sur fetched afin d'éviter un scintillement dû au fait que les données sont vides pendant que vous définissez le fetched statut.

Création d'un crochet personnalisé

"Un hook personnalisé est une fonction JavaScript dont le nom commence par" use "et qui peut appeler d'autres hooks."

– React Docs

C'est vraiment ce que c'est, et avec une fonction JavaScript, il vous permet de réutiliser un morceau de code dans plusieurs parties de votre application.

La définition des React Docs l'a révélée, mais voyons comment cela fonctionne dans la pratique avec un compteur personnalisé:

const useCounter = (initialState = 0) => {
      const (count, setCount) = useState(initialState);
      const add = () => setCount(count + 1);
      const subtract = () => setCount(count - 1);
      return { count, add, subtract };
};

Ici, nous avons une fonction régulière où nous prenons un argument facultatif, définissons la valeur à notre état, ainsi que l'ajout de add et le subtract méthodes qui pourraient être utilisées pour le mettre à jour.

Partout dans notre application où nous avons besoin d'un compteur, nous pouvons appeler useCounter comme une fonction régulière et passer un initialState nous savons donc par où commencer à compter. Lorsque nous n'avons pas d'état initial, nous passons par défaut à 0.

Voici comment cela fonctionne dans la pratique:

import { useCounter } from './customHookPath';

const { count, add, subtract } = useCounter(100);

eventHandler(() => {
  add(); // or subtract();
});

Ce que nous avons fait ici, c'était d'importer notre hook personnalisé à partir du fichier dans lequel nous l'avons déclaré, afin que nous puissions l'utiliser dans notre application. Nous définissons son état initial à 100, donc chaque fois que nous appelons add(), ça augmente count par 1, et chaque fois que nous appelons subtract(), il diminue count par 1.

Créer useFetch Crochet

Maintenant que nous avons appris à créer un hook personnalisé simple, extrayons notre logique pour récupérer des données dans un hook personnalisé.

const useFetch = (query) => {
    const (status, setStatus) = useState('idle');
    const (data, setData) = useState(());

    useEffect(() => {
        if (!query) return;

        const fetchData = async () => {
            setStatus('fetching');
            const response = await fetch(
                `https://hn.algolia.com/api/v1/search?query=${query}`
            );
            const data = await response.json();
            setData(data.hits);
            setStatus('fetched');
        };

        fetchData();
    }, (query));

    return { status, data };
};

C'est à peu près la même chose que nous avons fait ci-dessus, à l'exception que c'est une fonction qui prend en query et retourne status et data. Et c’est un useFetch crochet que nous pourrions utiliser dans plusieurs composants de notre application React.

Cela fonctionne, mais le problème avec cette implémentation est maintenant, elle est spécifique à Hacker News donc nous pourrions simplement l'appeler useHackerNews. Ce que nous avons l'intention de faire, c'est de créer un useFetch crochet qui peut être utilisé pour appeler n'importe quelle URL. Réorganisons-le pour prendre une URL à la place!

const useFetch = (url) => {
    const (status, setStatus) = useState('idle');
    const (data, setData) = useState(());

    useEffect(() => {
        if (!url) return;
        const fetchData = async () => {
            setStatus('fetching');
            const response = await fetch(url);
            const data = await response.json();
            setData(data);
            setStatus('fetched');
        };

        fetchData();
    }, (url));

    return { status, data };
};

Maintenant, notre hook useFetch est générique et nous pouvons l'utiliser comme nous le voulons dans nos différents composants.

Voici une façon de le consommer:

const (query, setQuery) = useState('');

const url = query && `https://hn.algolia.com/api/v1/search?query=${query}`;
const { status, data } = useFetch(url);

Dans ce cas, si la valeur de query est truthy, nous allons de l'avant pour définir l'URL et si ce n'est pas le cas, nous acceptons de passer indéfini car il serait traité dans notre crochet. L'effet tentera de s'exécuter une fois, malgré tout.

Mémorisation des données extraites

La mémorisation est une technique que nous utiliserions pour nous assurer de ne pas toucher hackernews point de terminaison si nous avons fait une sorte de demande pour le récupérer à une phase initiale. Le stockage du résultat d'appels de récupération coûteux permettra aux utilisateurs d'économiser du temps de chargement, augmentant ainsi les performances globales.

Remarque: Pour plus de contexte, vous pouvez consulter l'explication de Wikipedia sur la mémorisation.

Voyons comment nous pourrions faire cela!

const cache = {};

const useFetch = (url) => {
    const (status, setStatus) = useState('idle');
    const (data, setData) = useState(());

    useEffect(() => {
        if (!url) return;

        const fetchData = async () => {
            setStatus('fetching');
            if (cache(url)) {
                const data = cache(url);
                setData(data);
                setStatus('fetched');
            } else {
                const response = await fetch(url);
                const data = await response.json();
                cache(url) = data; // set response in cache;
                setData(data);
                setStatus('fetched');
            }
        };

        fetchData();
    }, (url));

    return { status, data };
};

Ici, nous mappons les URL à leurs données. Donc, si nous faisons une demande pour récupérer des données existantes, nous définissons les données à partir de notre cache local, sinon, nous allons faire la demande et définir le résultat dans le cache. Cela garantit que nous ne faisons pas d'appel API lorsque nous avons les données à notre disposition localement. Nous remarquerons également que nous supprimons l'effet si l'URL est falsy, il s'assure donc que nous ne procédons pas à l'extraction de données qui n'existent pas. Nous ne pouvons pas le faire avant le useEffect hook car cela ira à l'encontre d'une des règles des hooks, qui est de toujours appeler des hooks au niveau supérieur.

Déclarant cache dans une portée différente fonctionne mais cela rend notre crochet aller à l'encontre du principe d'une fonction pure. En outre, nous voulons également nous assurer que React aide à nettoyer notre gâchis lorsque nous ne voulons plus utiliser le composant. Nous allons explorer useRef pour nous aider à y parvenir.

Mémorisation des données avec useRef

"useRef est comme une boîte qui peut contenir une valeur mutable dans son .current property. "

– React Docs

Avec useRef, nous pouvons définir et récupérer facilement des valeurs modifiables et sa valeur persiste tout au long du cycle de vie du composant.

Remplaçons notre implémentation du cache par des useRef la magie!

const useFetch = (url) => {
    const cache = useRef({});
    const (status, setStatus) = useState('idle');
    const (data, setData) = useState(());

    useEffect(() => {
        if (!url) return;
        const fetchData = async () => {
            setStatus('fetching');
            if (cache.current(url)) {
                const data = cache.current(url);
                setData(data);
                setStatus('fetched');
            } else {
                const response = await fetch(url);
                const data = await response.json();
                cache.current(url) = data; // set response in cache;
                setData(data);
                setStatus('fetched');
            }
        };

        fetchData();
    }, (url));

    return { status, data };
};

Ici, notre cache est maintenant dans notre useFetch crochet avec un objet vide comme valeur initiale.

Emballer

Eh bien, j'ai déclaré que la définition des données avant de définir le statut récupéré était une bonne idée, mais il y a également deux problèmes potentiels que nous pourrions avoir avec cela:

  1. Notre test unitaire peut échouer car le tableau de données n'est pas vide pendant que nous sommes en train de récupérer. React pourrait en fait changer d'état de lot, mais il ne peut pas le faire s'il est déclenché de manière asynchrone;
  2. Notre application restitue plus qu'elle ne devrait.

Faisons un dernier nettoyage de notre useFetch crochet., Nous allons commencer par changer notre useStates à un useReducer. Voyons comment cela fonctionne!

const initialState = {
    status: 'idle',
    error: null,
    data: (),
};

const (state, dispatch) = useReducer((state, action) => {
    switch (action.type) {
        case 'FETCHING':
            return { ...initialState, status: 'fetching' };
        case 'FETCHED':
            return { ...initialState, status: 'fetched', data: action.payload };
        case 'FETCH_ERROR':
            return { ...initialState, status: 'error', error: action.payload };
        default:
            return state;
    }
}, initialState);

Ici, nous avons ajouté un état initial qui est la valeur initiale que nous avons transmise à chacun de nos individus useStates. Dans notre useReducer, nous vérifions le type d'action que nous souhaitons effectuer et définissons les valeurs appropriées à l'état en fonction de cela.

Cela résout les deux problèmes dont nous avons discuté précédemment, car nous pouvons maintenant définir l'état et les données en même temps afin d'éviter les états impossibles et les rendus inutiles.

Il ne reste plus qu'une chose: nettoyer nos effets secondaires. Fetch implémente l'API Promise, en ce sens qu'elle pourrait être résolue ou rejetée. Si notre hook essaie de faire une mise à jour alors que le composant est démonté à cause de certains Promise vient d'être résolu, React reviendrait Can't perform a React state update on an unmounted component.

Voyons comment nous pouvons résoudre ce problème avec useEffect nettoyer!

useEffect(() => {
    let cancelRequest = false;
    if (!url) return;

    const fetchData = async () => {
        dispatch({ type: 'FETCHING' });
        if (cache.current(url)) {
            const data = cache.current(url);
            dispatch({ type: 'FETCHED', payload: data });
        } else {
            try {
                const response = await fetch(url);
                const data = await response.json();
                cache.current(url) = data;
                if (cancelRequest) return;
                dispatch({ type: 'FETCHED', payload: data });
            } catch (error) {
                if (cancelRequest) return;
                dispatch({ type: 'FETCH_ERROR', payload: error.message });
            }
        }
    };

    fetchData();

    return function cleanup() {
        cancelRequest = true;
    };
}, (url));

Ici, nous mettons cancelRequest à true après l'avoir défini à l'intérieur de l'effet. Donc, avant d'essayer de faire des changements d'état, nous confirmons d'abord si le composant a été démonté. S'il a été démonté, nous ignorons la mise à jour de l'état et s'il n'a pas été démonté, nous mettons à jour l'état. Cela résoudra le Mise à jour de l'état React erreur, et également empêcher les conditions de concurrence dans nos composants.

Conclusion

Nous avons exploré plusieurs concepts de hooks pour aider à extraire et mettre en cache des données dans nos composants. Nous avons également nettoyé notre useEffect crochet qui permet d'éviter un bon nombre de problèmes dans notre application.

Si vous avez des questions, n'hésitez pas à les déposer dans la section commentaires ci-dessous!

Références

Smashing Editorial(ks, ra, yk, il)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *