Fermer

juillet 13, 2020

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


À propos de l'auteur

Ingénieur front-end passionné par les performances et les technologies de pointe.
En savoir plus sur
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 la méthode de cycle de vie componentDidMount () 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 débutant dans React Hooks, vous pouvez commencer par vérifier la documentation officielle de pour en avoir une idée. Après cela, je recommanderais de lire « Mise en route de l'API React Hooks » de Shedrack Akintayo. Pour vous assurer que vous suivez, 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 Hacker News Search API 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 lien API valide que nous lui transmettons.

Meilleures pratiques avec React [19659007] 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 dans le composant composantDidMount () méthode de cycle de vie et des données basées sur prop ou changements d'état dans componentDidUpdate () méthode de cycle de vie.

Voici comment cela fonctionne:

 componentDidMount () {
  const fetchData = async () => {
    réponse const = attendre la récupération (
      `https: //hn.algolia.com/api/v1/search? query = JavaScript`
    );
    const data = attente de response.json ();
    this.setState ({data});
  };
  
  fetchData ();
}


componentDidUpdate (previousProps, previousState) {
    if (previousState.query! == this.state.query) {
      const fetchData = async () => {
        réponse const = attendre la récupération (
          `https://hn.algolia.com/api/v1/search?query=$ {this.state.query}`
        );
        const data = attente de response.json ();
        this.setState ({data});
      };

      fetchData ();
    }
  }

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

La méthode de cycle de vie componentDidUpdate 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. Une chose que nous obtenons de l'utilisation des crochets est 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 Hook

Le useEffect 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 de prop ou d'état, nous devons les transmettre au tableau de dépendances (qui est le deuxième argument du hook useEffect ).

Explorons comment pour récupérer des données avec des hooks:

 import {useState, useEffect} de 'react';

const [status, setStatus] = useState ('inactif');
const [query, setQuery] = useState ('');
const [data, setData] = useState ([]);

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

    const fetchData = async () => {
        setStatus ('récupération');
        réponse const = attendre la récupération (
            `https://hn.algolia.com/api/v1/search?query=$ {query}`
        );
        const data = attente de response.json ();
        setData (data.hits);
        setStatus ('récupéré');
    };

    fetchData ();
}, [query]);

Dans l'exemple ci-dessus, nous avons passé la requête en tant que dépendance à notre crochet useEffect . Ce faisant, nous indiquons useEffect pour suivre les modifications de requête. Si la valeur de la requête précédente n'est pas la même que la valeur actuelle, la useEffect est de nouveau invoquée.

Cela dit, nous définissons également plusieurs statuts sur le composant selon les besoins, car cela transmettra mieux un message à l'écran en fonction du statut de certains états finis . Dans l'état inactif nous pouvions faire savoir aux utilisateurs qu'ils pouvaient utiliser la zone de recherche pour commencer. Dans l'état allant chercher nous pourrions montrer une fileuse . Et, dans l'état récupéré nous rendrons les données.

Il est important de définir les données avant d'essayer de définir le statut sur récupéré afin d'éviter un scintillement. qui se produit lorsque les données sont vides pendant que vous définissez le statut récupéré .

Création d'un crochet personnalisé

«Un crochet personnalisé est une fonction JavaScript dont le nom commence par« utiliser » et cela 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. [19659005] La définition des React Docs l'a révélée mais voyons comment cela fonctionne dans la pratique avec un compteur de hook personnalisé:

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

Ici, nous avons une fonction régulière où nous prenons un argument facultatif, définissons la valeur à notre état, ainsi que les méthodes add et soustract qui pourraient être

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 afin que nous sachions où commencer à compter à partir de . Lorsque nous n'avons pas d'état initial, nous passons par défaut à 0.

Voici comment cela fonctionne dans la pratique:

 import {useCounter} de './customHookPath';

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

eventHandler (() => {
  ajouter(); // ou soustraire ();
});

Ce que nous avons fait ici a été d'importer notre hook personnalisé à partir du fichier dans lequel nous l'avons déclaré, afin de pouvoir l'utiliser dans notre application. Nous définissons son état initial à 100, donc chaque fois que nous appelons add () il augmente le nombre de 1, et chaque fois que nous appelons soustrait () il diminue compter par 1.

Créer useFetch Hook

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

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

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

        const fetchData = async () => {
            setStatus ('récupération');
            réponse const = attendre la récupération (
                `https://hn.algolia.com/api/v1/search?query=$ {query}`
            );
            const data = attente de response.json ();
            setData (data.hits);
            setStatus ('récupéré');
        };

        fetchData ();
    }, [query]);

    return {status, data};
};

C'est à peu près la même chose que nous avons fait ci-dessus à l'exception qu'il s'agit d'une fonction qui prend en la requête et renvoie le statut et les données . Et, c'est un crochet useFetch que nous pourrions utiliser dans plusieurs composants de notre application React.

Cela fonctionne, mais le problème avec cette implémentation est maintenant, il est spécifique à Hacker News donc nous pourrions simplement l'appeler useHackerNews . Ce que nous avons l'intention de faire est de créer un hook useFetch 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 ('inactif');
    const [data, setData] = useState ([]);

    useEffect (() => {
        if (! url) return;
        const fetchData = async () => {
            setStatus ('récupération');
            réponse const = attente de récupération (url);
            const data = attente de response.json ();
            setData (données);
            setStatus ('récupéré');
        };

        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 requête est véridique nous allons de l'avant pour définir l'URL et si ce n'est pas le cas, nous pouvons passer indéfini comme il le ferait. être manipulé dans notre crochet. L'effet tentera de s'exécuter une fois, malgré tout.

Mémorisation des données récupérées

La mémorisation est une technique que nous utiliserions pour nous assurer que nous n'atteignons pas le point d'extrémité hackernews si nous avons créé 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. .

Explorons comment nous pourrions faire cela!

 const cache = {};

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

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

        const fetchData = async () => {
            setStatus ('récupération');
            if (cache [url]) {
                const data = cache [url];
                setData (données);
                setStatus ('récupéré');
            } autre {
                réponse const = attente de récupération (url);
                const data = attente de response.json ();
                cache [url] = données; // définir la réponse dans le cache;
                setData (données);
                setStatus ('récupéré');
            }
        };

        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 falsifiée de sorte qu'il s'assure que nous ne procédons pas à l'extraction de données qui n'existent pas. Nous ne pouvons pas le faire avant le hook useEffect car cela irait à l'encontre d'une des règles des hooks, qui est d'appeler toujours les hooks au niveau supérieur.

Déclarer 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 un mutable value in its .current property . ”

React Docs

Avec useRef nous pouvons définir et récupérer facilement des valeurs mutables et sa valeur persiste tout au long de

Remplaçons notre implémentation de cache par un peu de magie useRef !

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

    useEffect (() => {
        if (! url) return;
        const fetchData = async () => {
            setStatus ('récupération');
            if (cache.current [url]) {
                const data = cache.current [url];
                setData (données);
                setStatus ('récupéré');
            } autre {
                réponse const = attente de récupération (url);
                const data = attente de response.json ();
                cache.current [url] = données; // définir la réponse dans le cache;
                setData (données);
                setStatus ('récupéré');
            }
        };

        fetchData ();
    }, [url]);

    return {status, data};
};

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

Wrapping Up

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

  1. Notre test unitaire pourrait échouer car le tableau de données n'est pas vide pendant que nous sommes dans l'état de récupération. React pourrait en fait changer d'état par lots, 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 nettoyage final de notre useFetch hook., Nous allons commencer par changer nos useState en un useReducer . Voyons comment cela fonctionne!

 const initialState = {
    État inactif',
    erreur: null,
    données: [],
};

const [state, dispatch] = useReducer ((état, action) => {
    commutateur (action.type) {
        cas 'FETCHING':
            return {... initialState, status: 'fetching'};
        cas 'FETCHED':
            return {... initialState, status: 'fetched', data: action.payload};
        cas 'FETCH_ERROR':
            return {... initialState, status: 'error', error: action.payload};
        défaut:
            état de retour;
    }
}, Etat initial);

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

Cela résout les deux problèmes dont nous avons discuté précédemment, car nous arrivons maintenant à définir l'état et les données en même temps afin d'éviter des états impossibles et des 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 tente de faire une mise à jour alors que le composant a été démonté à cause d'une promesse qui vient d'être résolue, React retournerait Impossible d'effectuer une mise à jour de l'état React sur un composant non monté. [19659005] Voyons comment résoudre ce problème avec le nettoyage useEffect !

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

    const fetchData = async () => {
        expédition ({type: 'FETCHING'});
        if (cache.current [url]) {
            const data = cache.current [url];
            dispatch ({type: 'FETCHED', charge utile: données});
        } autre {
            essayez {
                réponse const = attente de récupération (url);
                const data = attente de response.json ();
                cache.current [url] = données;
                if (cancelRequest) return;
                dispatch ({type: 'FETCHED', charge utile: données});
            } catch (erreur) {
                if (cancelRequest) return;
                dispatch ({type: 'FETCH_ERROR', charge utile: error.message});
            }
        }
    };

    fetchData ();

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

Ici, nous avons défini cancelRequest sur 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 l'erreur React state update et empêchera également les conditions de concurrence dans nos composants.

Conclusion

Nous avons exploré plusieurs concepts de hooks pour aider à extraire et mettre en cache les données dans nos composants. Nous avons également nettoyé notre crochet useEffect 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! [19659069] Références

 Éditorial fracassant (ks, ra, yk, il)




Source link