Les composants basés sur les classes React sont désordonnés, déroutants, difficiles pour les humains et les machines. Mais avant React 16.8, les composants basés sur les classes étaient obligatoires pour tous les projets nécessitant des états, des méthodes de cycle de vie et de nombreuses autres fonctionnalités importantes. Tout cela a changé avec l'introduction des crochets dans React 16.8. Les crochets changent la donne. Ils ont simplifié React, l'ont rendu plus propre, plus facile à écrire et à déboguer, et ont également réduit la courbe d'apprentissage.
Les crochets sont simplement des fonctions qui vous permettent de vous accrocher à ou d'utiliser les fonctionnalités de React. Ils ont été introduits lors de la React Conf 2018 pour résoudre trois problèmes majeurs des composants de classe : l'enfer du wrapper, les composants énormes et les classes déroutantes. Les crochets donnent du pouvoir aux composants fonctionnels de React, ce qui permet de développer une application entière avec lui. Heureusement, les crochets ont résolu tous les problèmes de manière simple et efficace tout en créant de la place pour des fonctionnalités plus intéressantes dans React. Les hooks ne remplacent pas les concepts et les classes React déjà existants, ils fournissent simplement une API pour y accéder directement.
L'équipe React a introduit plusieurs hooks dans React 16.8. Cependant, vous pouvez également utiliser des crochets de fournisseurs tiers dans votre application ou même créer un crochet personnalisé. Dans ce tutoriel, nous examinerons quelques crochets utiles dans React et comment les utiliser. Nous passerons en revue plusieurs exemples de code de chaque hook et découvrirons également comment créer un hook personnalisé.
Remarque : Ce didacticiel nécessite une compréhension de base de Javascript (ES6+) et de React.[19659007]Plus après le saut ! Continuez à lire ci-dessous ↓
Motivation derrière les crochets
Comme indiqué précédemment , des hooks ont été créés pour résoudre trois problèmes : l'enfer du wrapper, des composants énormes et des classes déroutantes. Examinons chacun d'eux plus en détail.
Wrapper Hell
Les applications complexes construites avec des composants de classe s'exécutent facilement dans l'enfer du wrapper. Si vous examinez l'application dans React Dev Tools, vous remarquerez des composants profondément imbriqués. Cela rend très difficile le travail avec les composants ou leur débogage. Bien que ces problèmes puissent être résolus avec des composants d'ordre supérieur et render propsils vous obligent à modifier un peu votre code. Cela pourrait prêter à confusion dans une application complexe.
Les hooks sont faciles à partager, vous n'avez pas besoin de modifier vos composants avant de réutiliser la logique.
Un bon exemple est l'utilisation du Redux connect Higher Order Component (HOC) pour s'abonner au magasin Redux. Comme tous les HOC, pour utiliser le HOC de connexion, vous devez exporter le composant avec les fonctions d'ordre supérieur définies. Dans le cas de connectnous aurons quelque chose de cette forme.
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)Where mapStateToProps et mapDispatchToProps sont des fonctions à définir.
Alors qu'à l'époque des Hooks, on peut facilement obtenir le même résultat de manière nette et succincte en utilisant les hooks Redux useSelector et useDispatch.
Composants énormes
Les composants de classe contiennent généralement des effets secondaires et une logique avec état. Au fur et à mesure que l'application devient de plus en plus complexe, il est courant que le composant devienne désordonné et déroutant. En effet, les effets secondaires devraient être organisés par méthodes de cycle de vie plutôt que par fonctionnalité. Bien qu'il soit possible de diviser les composants et de les simplifier, cela introduit souvent un niveau d'abstraction plus élevé.
Les crochets organisent les effets secondaires par fonctionnalité et il est possible de diviser un composant en morceaux en fonction de la fonctionnalité. Classes
Les classes sont généralement un concept plus difficile que les fonctions. Les composants basés sur les classes React sont verbeux et un peu difficiles pour les débutants. Si vous débutez avec Javascript, vous pourriez trouver des fonctions plus faciles à utiliser en raison de leur syntaxe légère par rapport aux classes. La syntaxe peut être déroutante ; parfois, il est possible d'oublier de lier un gestionnaire d'événements qui pourrait casser le code.
React résout ce problème avec des composants fonctionnels et des crochets, permettant aux développeurs de se concentrer sur le projet plutôt que sur la syntaxe du code.
Par exemple, ce qui suit deux composants React donneront exactement le même résultat.
import React, { Component } from "react";
exporter la classe par défaut L'application étend le composant {
constructeur (accessoires) {
super(accessoires);
this.état = {
nombre : 0
} ;
this.incrementNumber = this.incrementNumber.bind(this);
}
incrémentNumber() {
this.setState({ num : this.state.num + 1 });
}
rendu() {
revenir (
{this.state.num}
);
}
}import React, { useState } de "react" ;
exporter la fonction par défaut App() {
const [num, setNum] = useState(0);
fonction incrémentNombre() {
setNum(num + 1);
}
revenir (
{num}
);
}Le premier exemple est un composant basé sur les classes tandis que le second est un composant fonctionnel. Bien qu'il s'agisse d'un exemple simple, notez à quel point le premier exemple est faux par rapport au second. qui s'appliquent à eux. Voici quelques-unes des règles qui s'appliquent aux crochets.
- La convention de dénomination des crochets doit commencer par le préfixe
use. Ainsi, nous pouvons avoiruseStateuseEffectetc. Si vous utilisez des éditeurs de code modernes comme Atom et VSCode, le plugin ESLint pourrait être une fonctionnalité très utile pour les hooks React. Le plugin fournit des avertissements et des conseils utiles sur les meilleures pratiques. - Les hooks doivent être appelés au niveau supérieur d'un composant, avant l'instruction return. Ils ne peuvent pas être appelés à l'intérieur d'une instruction conditionnelle, d'une boucle ou de fonctions imbriquées.
- Les hooks doivent être appelés à partir d'une fonction React (à l'intérieur d'un composant React ou d'un autre hook). Il ne doit pas être appelé à partir d'une fonction Vanilla JS. Comme les autres hooks intégrés, ce hook doit être importé de
reactpour être utilisé dans notre application.import {useState} from 'react'Pour initialiser l'état, nous devons déclarer les deux l'état et sa fonction de mise à jour et passer une valeur initiale.
const [state, updaterFn] = useState('')Nous sommes libres d'appeler notre fonction d'état et de mise à jour comme bon nous semble mais par convention, le premier élément de la array sera notre état tandis que le deuxième élément sera la fonction de mise à jour. C'est une pratique courante de préfixer notre fonction de mise à jour avec le préfixe set suivi du nom de notre état sous forme de cas de chameau.
Par exemple, définissons un état pour contenir les valeurs de comptage.
const [count, setCount] = useState(0)Remarquez que la valeur initiale de notre état
countest définie sur0et non sur une chaîne vide. En d'autres termes, nous pouvons initialiser notre état sur n'importe quel type de variables JavaScript, à savoir nombre, chaîne, booléen, tableau, objet et même BigInt. Il existe une nette différence entre la définition d'états avec le hookuseStateet les états de composants basés sur les classes. Il est à noter que le hookuseStaterenvoie un tableau, également connu sous le nom de variables d'état et dans l'exemple ci-dessus, nous avons déstructuré le tableau enstateet la fonctionupdater.Rendu des composants
La définition des états avec le hook
useStateprovoque le réaffichage du composant correspondant. Cependant, cela ne se produit que si React détecte une différence entre l'état précédent ou ancien et le nouvel état. React effectue la comparaison des états à l'aide de l'algorithme JavascriptObject.is.Setting States With
useStateNotre état
countpeut être défini aux nouvelles valeurs d'état en passant simplement la nouvelle valeur à la fonction de mise à joursetCountcomme suitsetCount(newValue).Cette méthode fonctionne lorsque nous ne voulons pas référencer la précédente valeur de l'état. Si nous souhaitons faire cela, nous devons passer une fonction à la fonction
setCount.En supposant que nous voulions ajouter 5 à notre variable
countchaque fois qu'un bouton est cliqué, nous pourrait faire ce qui suit.importer {useState} depuis 'react' const CountExample = () => { // initialise notre état de comptage const [count, setCount] = useState(0) // ajoute 5 à l'état précédent du compte const handleClick = () =>{ setCount(prevCount => prevCount + 5) } revenir() } export default CountExample{count}
Dans le code ci-dessus, nous avons d'abord importé le hook
useStatedereactpuis initialisé l'étatcountavec une valeur par défaut de 0. Nous avons créé un gestionnaireonClickpour incrémenter la valeur decountde 5 chaque fois que le bouton est cliqué. Ensuite, nous avons affiché le résultat dans une baliseh1.Définition des tableaux et des états des objets
Les états des tableaux et des objets peuvent être définis de la même manière que les autres types de données. Cependant, si nous souhaitons conserver les valeurs déjà existantes, nous devons utiliser l'opérateur de propagation ES6 lors de la définition des états.
L'opérateur de propagation en Javascript est utilisé pour créer un nouvel objet à partir d'un objet déjà existant. Ceci est utile ici car
Reactcompare les états avec l'opérationObject.ispuis effectue un nouveau rendu en conséquence.Considérons le code ci-dessous pour définir les états lors d'un clic sur un bouton.
importer {useState} depuis 'react' const StateExample = () => { //initialiser nos états de tableau et d'objet const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num : 1, nom : 'Desmond'}) // définit arr sur les nouvelles valeurs du tableau const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // définit obj sur les nouvelles valeurs de l'objet const handleObjClick = () =>{ const newObj = {nom : 'Ifeanyi', âge : 25} setObj({...obj, ...newObj}) } revenir() } export default StateExampleDans le code ci-dessus, nous avons créé deux états
arretobjet les avons initialisés à des valeurs de tableau et d'objet respectivement. Nous avons ensuite créé des gestionnairesonClickappeléshandleArrClickethandleObjClickpour définir respectivement les états du tableau et de l'objet. LorsquehandleArrClickse déclenche, nous appelonssetArret utilisons l'opérateur de propagation ES6 pour répartir les valeurs de tableau déjà existantes et y ajouternewArr.Nous avons fait de même. chose pour le gestionnaire
handleObjClick. Ici, nous avons appelésetObjdiffusé les valeurs d'objet existantes à l'aide de l'opérateur de diffusion ES6 et mis à jour les valeurs denameetage.Async Nature Of
useStateComme nous l'avons déjà vu, nous définissons les états avec
useStateen passant une nouvelle valeur à la fonction de mise à jour. Si le programme de mise à jour est appelé plusieurs fois, les nouvelles valeurs seront ajoutées à une file d'attente et le nouveau rendu est effectué en conséquence à l'aide de la comparaison JavaScriptObject.is.Les états sont mis à jour de manière asynchrone. Cela signifie que le nouvel état est d'abord ajouté à un état en attente et par la suite, l'état est mis à jour. Ainsi, vous pouvez toujours obtenir l'ancienne valeur d'état si vous accédez à l'état dès qu'il est défini.
Considérons l'exemple suivant pour observer ce comportement.
Dans le code ci-dessus, nous avons créé un
countà l'aide du hookuseState. Nous avons ensuite créé un gestionnaireonClickpour incrémenter l'étatcountchaque fois que le bouton est cliqué.
Notez que bien que l'étatcountait augmenté, comme indiqué dans la baliseh2l'état précédent est toujours enregistré dans la console. Cela est dû à la nature asynchrone du hook.Si nous souhaitons obtenir le nouvel état, nous pouvons le gérer de la même manière que nous gérerions les fonctions asynchrones. Voici une façon de le faire.
Ici, nous avons stocké
newCountValuecréé pour stocker la valeur de comptage mise à jour, puis défini l'étatcountavec la valeur mise à jour. Ensuite, nous avons enregistré la valeur de comptage mise à jour dans la console.Le
useEffectHookuseEffectest un autre hook React important utilisé dans la plupart des projets. Il fait la même chose que les méthodes de cycle de viecomponentDidMountcomponentWillUnmountetcomponentDidUpdatedu composant basé sur les classes.useEffectnous offre la possibilité d'écrire des codes impératifs qui peuvent avoir des effets secondaires sur l'application. Des exemples de tels effets incluent la journalisation, les abonnements, les mutations, etc.L'utilisateur peut décider quand
useEffects'exécutera, cependant, s'il n'est pas défini, les effets secondaires s'exécuteront à chaque rendu ou nouveau rendu .Considérez l'exemple ci-dessous.
import {useState, useEffect} depuis 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log (nombre) }) revenir(...) }Dans le code ci-dessus, nous avons simplement enregistré
countdans leuseEffect. Cela s'exécutera après chaque rendu du composant.Parfois, nous pouvons vouloir exécuter le hook une fois (sur le montage) dans notre composant. Nous pouvons y parvenir en fournissant un deuxième paramètre au crochet
useEffect.import {useState, useEffect} à partir de 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) revenir() }{count}
...Le hook
useEffecta deux paramètres, le premier paramètre est la fonction que nous voulons exécuter tandis que le deuxième paramètre est un tableau de dépendances. Si le deuxième paramètre n'est pas fourni, le hook s'exécutera en continu.En passant un crochet vide au deuxième paramètre du hook, nous demandons à React d'exécuter le hook
useEffectune seule fois, sur le support. Cela affichera la valeur1dans la baliseh1car le nombre sera mis à jour une fois, de 0 à 1, lors du montage du composant.Nous pourrions également faire notre effet secondaire. exécuter chaque fois que certaines valeurs dépendantes changent. Cela peut être fait en passant ces valeurs dans la liste des dépendances.
Par exemple, nous pourrions faire en sorte que
useEffects'exécute chaque fois quecountchange comme suit.import { useState, useEffect } de "react" ; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(compte); }, [count]); revenir (); } ; export default App;Le
useEffectci-dessus s'exécutera lorsque l'une de ces deux conditions est remplie.- Au montage — après le rendu du composant.
- Lorsque la valeur de
comptechange.
Au montage, l'expression
console.logs'exécutera et consigneracountà 0. Une fois lecountmis à jour, le deuxième la condition est remplie, donc leuseEffects'exécute à nouveau, cela continuera chaque fois que le bouton est cliqué.Une fois que nous avons fourni le deuxième argument à
useEffectil est prévu que nous passons tout les dépendances à celui-ci. Si vous avez installéESLINTune erreur de lint s'affichera si aucune dépendance n'est transmise à la liste de paramètres. Cela pourrait également faire en sorte que l'effet secondaire se comporte de manière inattendue, surtout si cela dépend des paramètres qui ne sont pas transmis.Cleaning Up The Effect
useEffectnous permet également de nettoyer les ressources avant le démontage du composant . Cela peut être nécessaire pour éviter les fuites de mémoire et rendre l'application plus efficace. Pour ce faire, nous renvoyons la fonction de nettoyage à la fin du hook.useEffect(() => { console.log('monté') return () => console.log('démontage... nettoyer ici') })Le crochet
useEffectci-dessus enregistreramountlorsque le composant est monté. Démontage… nettoyer ici sera consigné lors du démontage du composant. Cela peut se produire lorsque le composant est supprimé de l'interface utilisateur.Le processus de nettoyage suit généralement le formulaire ci-dessous.
useEffect(() => { //L'effet que nous avons l'intention de faire effet //Nous retournons ensuite le nettoyage return() => le nettoyage/désabonnement })Bien que vous ne trouviez pas autant de cas d'utilisation pour les abonnements
useEffectil est utile pour les abonnements et les minuteurs. En particulier, lorsqu'il s'agit de sockets Web, vous devrez peut-être vous désabonner du réseau pour économiser des ressources et améliorer les performances lors du démontage du composant.Récupération et récupération de données avec
useEffectL'une des utilisations les plus courantes les cas du crochet
useEffectrécupèrent et prélèvent des données à partir d'une API.Pour illustrer cela, nous utiliserons de fausses données utilisateur que j'ai créées à partir de
JSONPlaceholderpour récupérer des données avec leJSONPlaceholderhook useEffect.import { useEffect, useState } à partir de "react" ; importer des axios depuis "axios" ; exporter la fonction par défaut App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users" ; useEffect(() => { const fetchUsers = async () => { const { data } = wait axios.get(endPoint); setUsers(données); } ; fetchUsers(); }, []); revenir ({users.map((utilisateur) => (); }))}{user.name}
Occupation : {user.job}
Sexe : {user.sex}
Dans le code ci-dessus, nous avons créé un état
usersà l'aide du hookuseState. Ensuite, nous avons récupéré les données d'une API à l'aide d'Axios. Il s'agit d'un processus asynchrone, et nous avons donc utilisé la fonction async/await, nous aurions également pu utiliser le point puis la syntaxe. Puisque nous avons récupéré une liste d'utilisateurs, nous l'avons simplement mappée pour afficher les données.Notez que nous avons passé un paramètre vide au hook. Cela garantit qu'il n'est appelé qu'une seule fois lors du montage du composant.
Nous pouvons également récupérer les données lorsque certaines conditions changent. Nous allons le montrer dans le code ci-dessous.
import { useEffect, useState } from "react" ; importer des axios depuis "axios" ; exporter la fonction par défaut App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users" ; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = wait axios.get(`${endPoint}/${currentID}`}); setUser(données); } ; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); } ; revenir (); }{user.name}
Occupation : {user.job}
Sexe : {user.sex}
Ici, nous avons créé deux hooks
useEffect. Dans le premier, nous avons utilisé la syntaxe point puis pour obtenir tous les utilisateurs de notre API. Cela est nécessaire pour déterminer le nombre d'utilisateurs.Nous avons ensuite créé un autre hook
useEffectpour obtenir un utilisateur basé sur leid. CeuseEffectrécupèrera les données chaque fois que l'identifiant changera. Pour garantir cela, nous avons passé l'iddans la liste des dépendances.Ensuite, nous avons créé des fonctions pour mettre à jour la valeur de notre
idchaque fois que les boutons sont cliqués. Une fois la valeur deidmodifiée,useEffects'exécutera à nouveau et récupèrera les données.Si nous le souhaitons, nous pouvons même nettoyer ou annuler le jeton basé sur la promesse dans Axios, nous pourrions le faire avec la méthode de nettoyage décrite ci-dessus.
useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = wait axios.get(`${endPoint}/${num}`, { cancelToken : source.token }); setUser(données); } ; fetchUsers(); return() => source.cancel(); }, [num]);Ici, nous avons passé le jeton Axios comme deuxième paramètre à
axios.get. Lorsque le composant est démonté, nous avons ensuite annulé l'abonnement en appelant la méthode cancel de l'objet source.Le crochet
useReducerLe crochet
useReducerest un crochet React très utile qui fait une chose similaire au crochetuseState. Selon la documentation Reactce hook doit être utilisé pour gérer une logique plus complexe que le hookuseState. Il convient de noter que le hookuseStateest implémenté en interne avec le hook useReducer.Le hook prend un réducteur comme argument et peut éventuellement prendre l'état initial et une fonction init comme arguments.
const [state, dispatch] = useReducer(reducer, initialState, init)Ici,
initest une fonction et elle est utilisée chaque fois que nous voulons créer l'état initial paresseusement.Regardons comment implémentez le hook
useReduceren créant une simple application à faire comme indiqué dans le bac à sable ci-dessous.Exemple de Todo Tout d'abord, nous devons créer notre réducteur pour contenir les états.
export. const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; réducteur const = (état, action) => { commutateur (action.type) { cas ADD_TODO : const newTodo = { id : action.id, texte : action.texte, terminé : faux } ; retour [...state, newTodo]; cas REMOVE_TODO : return state.filter((todo) => todo.id !== action.id); cas COMPLETE_TODO : const completeTodo = state.map((todo) => { si (todo.id === action.id) { revenir { ...à faire, terminé : !todo.completed } ; } autre { retourner à faire; } }); retourner completeTodo; défaut: état de retour ; } } ; réducteur par défaut d'exportation ;Nous avons créé trois constantes correspondant à nos types d'action. Nous aurions pu utiliser des chaînes directement mais cette méthode est préférable pour éviter les fautes de frappe.
Ensuite, nous avons créé notre fonction de réduction. Comme dans
Reduxle réducteur doit prendre l'état et l'objet d'action. Mais contrairement à Redux, nous n'avons pas besoin d'initialiser notre réducteur ici. le contexte peut permettre à une application plus importante de déclencher des actions, de mettre à jourstateet de l'écouter.Ensuite, nous avons utilisé les instructions
switchpour vérifier le type d'action transmis par l'utilisateur. Si le type d'action estADD_TODOon veut passer une nouvelle to-do et si c'estREMOVE_TODOon veut filtrer les to-dos et supprimer celle qui correspond auidtransmis par l'utilisateur. S'il s'agit deCOMPLETE_TODOnous souhaitons mapper les tâches à effectuer et basculer celle avec l'idtransmis par l'utilisateur.Voici le
App.jsdans lequel nous avons implémenté lereducer.import { useReducer, useState } à partir de "react" ; importer "./styles.css" ; réducteur d'importation, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } de "./reducer" ; exporter la fonction par défaut App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //On pourrait aussi passer un tableau vide comme état initial //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); envoi({ tapez : ADD_TODO, id : nouvelId, texte : texte }); Définir le texte(""); } ; const removeTodo = (id) => { dispatch({ type : REMOVE_TODO, id }); } ; const completeTodo = (id) => { dispatch({ type : COMPLET_TODO, id }); } ; revenir (); }Exemple à faire
{state.map((todo) => ())}{todo.text}
removeTodo(todo.id)}>✕ completeTodo(todo.id)}>✓Ici, nous avons créé un formulaire contenant un élément input, pour collecter l'entrée de l'utilisateur, et un bouton pour déclencher l'action. Lorsque le formulaire est soumis, nous avons envoyé une action de type
ADD_TODOen passant un nouvel identifiant et un texte à faire. Nous avons créé un nouvel identifiant en incrémentant la valeur de l'identifiant précédent de 1. Nous avons ensuite effacé la zone de texte de saisie. Pour supprimer et terminer la tâche, nous avons simplement envoyé les actions appropriées. Ceux-ci ont déjà été implémentés dans le réducteur comme indiqué ci-dessus.Cependant, la magie opère car nous utilisons le crochet
useReducer. Ce hook accepte le réducteur et l'état initial et renvoie l'état et la fonction de répartition. Ici, la fonction dispatch a le même objectif que la fonction setter pour le hookuseStateet nous pouvons l'appeler comme nous voulons au lieu dedispatch.Pour afficher le to-do éléments, nous avons simplement mappé la liste des tâches renvoyées dans notre objet d'état, comme indiqué dans le code ci-dessus.
Cela montre la puissance du crochet
useReducer. Nous pourrions également obtenir cette fonctionnalité avec le hookuseStatemais comme vous pouvez le voir dans l'exemple ci-dessus, le hookuseReducernous a aidé à garder les choses plus propres.useReducerest souvent bénéfique lorsque l'objet d'état est une structure complexe et est mis à jour de différentes manières par rapport à un simple remplacement de valeur. De plus, une fois que ces fonctions de mise à jour deviennent plus compliquées,useReducerpermet de conserver facilement toute cette complexité dans une fonction de réduction (qui est une fonction JS pure), ce qui facilite l'écriture de tests pour la seule fonction de réduction.Nous aurions également pu passer le troisième argument au hook
useReducerpour créer l'état initial paresseusement. Cela signifie que nous pourrions calculer l'état initial dans une fonctioninit.Par exemple, nous pourrions créer une fonction
initcomme suit :const initFunc = () = > [ { id: id, text: "First Item", completed: false } ]puis transmettez-le à notre hook
useReducer.const [state, dispatch] = useReducer(reducer, initialState, initFunc)Si nous faisons cela, le
initFuncremplacera leinitialStateque nous avons fourni et l'état initial sera calculé paresseusement.Le crochet
useContextL'API React Context fournit un moyen de partager des états ou des données dans l'arborescence des composants React. L'API est disponible dans React, en tant que fonctionnalité expérimentale, depuis un certain temps, mais son utilisation est devenue sûre dans React 16.3.0. L'API facilite le partage de données entre les composants tout en éliminant le perçage des accessoires.
Bien que vous puissiez appliquer le contexte React à l'ensemble de votre application, il est également possible de l'appliquer à une partie de l'application.
Pour utiliser le crochet, vous devez d'abord créer un contexte à l'aide de
React.createContextet ce contexte peut ensuite être transmis au hook.Pour démontrer l'utilisation du hook
useContextcréons une application simple cela augmentera la taille de la police dans toute notre application.Créons notre contexte dans le fichier
context.js.import { createContext } à partir de "react" ; //Ici, nous définissons le fontSize initial sur 16. const fontSizeContext = createContext(16); export default fontSizeContext;Ici, nous avons créé un contexte et lui avons passé une valeur initiale de
16puis nous avons exporté le contexte. Ensuite, connectons notre contexte à notre application.import FontSizeContext from "./context"; importer { useState } de "react" ; importer PageOne à partir de "./PageOne" ; importer PageTwo à partir de "./PageTwo" ; const App = () => { const [size, setSize] = useState(16); revenir ( ); } ; export default App;Dans le code ci-dessus, nous avons enveloppé l'intégralité de notre arborescence de composants avec
FontSizeContext.Provideret passésizeà sa valeur prop. Ici,sizeest un état créé avec le hookuseState. Cela nous permet de changer la valeur prop chaque fois que l'étatsizechange. En enveloppant le composant entier avec leProvidernous pouvons accéder au contexte n'importe où dans notre application.Par exemple, nous avons accédé au contexte dans et . En conséquence, la taille de la police augmentera dans ces deux composants lorsque nous l'augmentons à partir du fichier
App.js. Nous pouvons augmenter ou diminuer la taille de la police à partir des boutons comme indiqué ci-dessus et une fois que nous le faisons, la taille de la police change dans toute l'application.import { useContext } from "react"; importer le contexte de "./context" ; const PageOne = () => { taille const = useContext(context); renvoieContenu de la première page
; } ; export default PageOne;Ici, nous avons accédé au contexte à l'aide du hook
useContextde notre composantPageOne. Nous avons ensuite utilisé ce contexte pour définir notre propriété font-size. Une procédure similaire s'applique au fichierPageTwo.js.Les thèmes ou d'autres configurations de niveau d'application d'ordre supérieur sont de bons candidats pour les contextes.
Utilisation de
useContextEtuseReducerWhen used with the
useReducerhook,useContextallows us to create our own state management system. We can create global states and easily manage them in our application.Let’s improve our to-do application using the context API.
As usual, we need to create a
todoContextin thetodoContext.jsfile.import { createContext } from "react"; const initialState = []; export default createContext(initialState);Here we created the context, passing an initial value of an empty array. Then we exported the context.
Let’s refactor our
App.jsfile by separating the to-do list and items.import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return (); }Todo Example
Here, we wrapped our
App.jsfile with theTodoContext.Providerthen we passed the return values of ourtodoReducerto it. This makes the reducer’s state anddispatchfunction to be accessible throughout our application.We then separated the to-do display into a component
TodoList. We did this without prop drilling, thanks to the Context API. Let’s take a look at theTodoList.jsfile.import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ({state.map((todo) => ( ))}); }; export default TodoList;Using array destructuring, we can access the state (leaving the dispatch function) from the context using the
useContexthook. We can then map through the state and display the to-do items. We still extracted this in aTodocomponent. The ES6+ map function requires us to pass a unique key and since we need the specific to-do, we pass it alongside as well.Let’s take a look at the
Todocomponent.import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return (); }; export default Todo;{todo.text}
removeTodo(todo.id)}>✕ completeTodo(todo.id)}>✓Again using array destructuring, we accessed the dispatch function from the context. This allows us to define the
completeTodoandremoveTodofunction as already discussed in theuseReducersection. With thetodoprop passed fromtodoList.jswe can display a to-do item. We can also mark it as completed and remove the to-do as we deem fit.It is also possible to nest more than one context provider in the root of our application. This means that we can use more than one context to perform different functions in an application.
To demonstrate this, let’s add theming to the to-do example.
Here’s what we’ll be building.
Again, we have to create
themeContext. To do this, create athemeContext.jsfile and add the following codes.import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);Here, we created a context and passed
colors.lightas the initial value. Let’s define the colors with this property in thecolors.jsfile.const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;In the code above, we created a
colorsobject containing light and dark properties. Each property hasbackgroundColorandcolorobject.Next, we create the
themeReducerto handle the theme states.import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;Like all reducers, the
themeReducertakes the state and the action. It then uses theswitchstatement to determine the current action. If it’s of typeLIGHTwe simply assignColors.lightprops and if it’s of typeDARKwe displayColors.darkprops. We could have easily done this with theuseStatehook but we chooseuseReducerto drive the point home.Having set up the
themeReducerwe can then integrate it in ourApp.jsfile.import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return (); }Todo Example
In the above code, we added a few things to our already existing to-do application. We began by importing the
ThemeContextthemeReducerThemeTogglerandColors. We created a reducer using theuseReducerhook, passing thethemeReducerand an initial value ofColors.lightto it. This returned thethemeStateandthemeDispatchto us.We then nested our component with the provider function from the
ThemeContextpassing thethemeStateand thedispatchfunctions to it. We also added theme styles to it by spreading out thethemeStates. This works because thecolorsobject already defined properties similar to what the JSX styles will accept.However, the actual theme toggling happens in the
ThemeTogglercomponent. Let’s take a look at it.import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return (); }; export default ThemeToggler;In this component, we used the
useContexthook to retrieve the values we passed to theThemeContext.Providerfrom ourApp.jsfile. As shown above, these values include theThemeStatedispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a stateshowLightto determine the current theme. This allows us to easily change the button text depending on the current theme.The
useMemoHookThe
useMemohook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed,useMemowill just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.The hook can be used as follows:
const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])Let’s consider three cases of the
useMemohook.- When the dependency values, a and b remain the same.
TheuseMemohook will return the already computed memoized value without recomputation. - When the dependency values, a and b change.
The hook will recompute the value. - When no dependency value is passed.
The hook will recompute the value.
Let’s take a look at an example to demonstrate this concept.
In the example below, we’ll be computing the PAYE and Income after PAYE of a company’s employees with fake data from JSONPlaceholder.
The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.
This is shown in the sandbox below.
First, we queried the API to get the employees’ data. We also get data for each employee (with respect to their employee id).
const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);We used
axiosand theasync/awaitmethod in the firstuseEffectand then the dot then syntax in the second. These two approaches work in the same way.Next, using the employee data we got from above, let’s calculate the relief variables:
const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);This is a fairly complex calculation and so we had to wrap it in a
useMemohook to memoize or optimize it. Memoizing it this way will ensure that the calculation will not be recomputed if we tried to access the same employee again.Furthermore, using the tax relief values obtained above, we’d like to calculate the PAYE and income after PAYE.
const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);We performed tax calculation (a fairly complex calculation) using the above-computed tax variables and then memoized it with the
useMemohook.The complete code is available on here.
This follows the tax calculation procedure given here. We first computed the tax relief considering income, number of children, and number of dependent relatives. Then, we multiplied the taxable income by the PIT rates in steps. While the calculation in question is not entirely necessary for this tutorial, it is provided to show us why
useMemomay be necessary. This is also a fairly complex calculation and so we may need to memorize it withuseMemoas shown above.After calculating the values, we simply displayed the result.
Note the following about the
useMemohook.useMemoshould be used only when it is necessary to optimize the computation. In other words, when recomputation is expensive.- It is advisable to first write the calculation without memorization and only memorize it if it is causing performance issues.
- Unnecessary and irrelevant use of the
useMemohook may even compound the performance issues. - Sometimes, too much memoization can also cause performance issues.
The
useCallbackHookuseCallbackserves the same purpose asuseMemobut it returns a memoized callback instead of a memoized value. In other words,useCallbackis the same as passinguseMemowithout a function call.For instance, consider the following codes below.
import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return(...) } export default MemoizationExampleIn the above example, both
memoResultandcallbackResultwill give the same value of12. Here,useCallbackwill return a memoized value. However, we could also make it return a memoized callback by passing it as a function.The
useCallbackbelow will return a memoized callback.... const callbackResult = useCallback(() => a + b, [a, b]) ...We can then trigger the callback when an action is performed or in a
useEffecthook.import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return () } export default memoizationExampleIn the above code, we defined a callback function using the
useCallbackhook. We then called the callback in auseEffecthook when the component mounts and also when a button is clicked.Both the
useEffectand the button click yield the same result.Note that the concepts, do’s, and don’ts that apply to the
useMemohook also apply to theuseCallbackhook. We can recreate theuseMemoexample withuseCallback.The
useRefHookuseRefreturns an object that can persist in an application. The hook has only one property,currentand we can easily pass an argument to it.It serves the same purpose a
createRefused in class-based components. We can create a reference with this hook as follows:const newRef = useRef('')Here we created a new ref called
newRefand passed an empty string to it.This hook is used mainly for two purposes:
- Accessing or manipulating the DOM, and
- Storing mutable states — this is useful when we don’t want the component to rerender when a value change.
Manipulating the DOM
When passed to a DOM element, the ref object points to that element and can be used to access its DOM attributes and properties.
Here is a very simple example to demonstrate this concept.
import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return() } export default RefExampleThis is a h1 element
In the example above, we defined
headingRefusing theuseRefhook passing an empty string. We then set the ref in theh1tag by passingref = {headingRef}. By setting this ref, we have asked theheadingRefto point to ourh1element. This means that we can access the properties of ourh1element from the ref.To see this, if we check the value of
console.log(headingRef)we’ll get{current: HTMLHeadingElement}or{current: h1}and we can assess all the properties or attributes of the element. A similar thing applies to any other HTML element.For instance, we could make the text italic when the component mounts.
useEffect(() => { headingRef.current.style.fontStyle = "italic"; }, []);We can even change the text to something else.
... headingRef.current.innerHTML = "A Changed H1 Element"; ...We can even change the background color of the parent container as well.
... headingRef.current.parentNode.style.backgroundColor = "red"; ...Any kind of DOM manipulation can be done here. Observe that
headingRef.currentcan be read in the same way asdocument.querySelector('.topheading').One interesting use case of the
useRefhook in manipulating the DOM element is to focus the cursor on the input element. Let’s quickly run through it.import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return() } export default inputRefExampleIn the above code, we created
inputRefusing theuseRefhook and then asked it to point to the input element. We then made the cursor focus on the input ref when the component loads and when the button is clicked usinginputRef.current.focus(). This is possible becausefocus()is an attribute of input elements and so the ref will be able to assess the methods.Refs created in a parent component can be assessed at the child component by forwarding it using
React.forwardRef(). Let’s take a look at it.Let’s first create another component
NewInput.jsand add the following codes to it.import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return ; }); export default NewInput;This component accepts
propsandref. We passed the ref to its ref prop andprops.valto its placeholder prop. Regular React components do not take arefattribute. This attribute is available only when we wrap it withReact.forwardRefas shown above.We can then easily call this in the parent component.
... ...Storing The Mutable States
Refs are not just used to manipulate DOM elements, they can also be used to store mutable values without re-rendering the entire component.
The following example will detect the number of times a button is clicked without re-rendering the component.
import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return (); }In the code above, we incremented the
countRefwhen the button is clicked and then logged it to the console. Although the value is incremented as shown in the console, we won’t be able to see any change if we try to assess it directly in our component. It will only update in the component when it re-renders.Note that while
useStateis asynchronous,useRefis synchronous. In other words, the value is available immediately after it is updated.The
useLayoutEffectHookLike the
useEffecthook,useLayoutEffectis called after the component is mounted and rendered. This hook fires after DOM mutation and it does so synchronously. Apart from getting called synchronously after DOM mutation,useLayoutEffectdoes the same thing asuseEffect.useLayoutEffectshould only be used for performing DOM mutation or DOM-related measurement, otherwise, you should use theuseEffecthook. Using theuseEffecthook for DOM mutation functions may cause some performance issues such as flickering butuseLayoutEffecthandles them perfectly as it runs after the mutations have occurred.Let’s take a look at some examples to demonstrate this concept.
- We’ll be getting the width and height of the window on resize.
import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return () } export default ResizeExamplewidth: {windowSize.width}
height: {windowSize.height}
In the above code, we created a state
windowSizewith width and height properties. Then we set the state to the current window’s width and height respectively when the window is resized. We also cleaned up the code when it unmounts. The clean-up process is essential inuseLayoutEffectto clean up the DOM manipulation and improve efficiency.- Let’s blur a text with
useLayoutEffect.
import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return (); }This is the text to blur
We used
useRefanduseLayoutEffecttogether in the above code. We first created a ref,paragraphRefto point to our paragraph. Then we created an on-click event listener to monitor when the paragraph is clicked and then blurred it using the style properties we defined. Finally, we cleaned up the event listener usingremoveEventListener.The
useDispatchAnduseSelectorHooksuseDispatchis a Redux hook for dispatching (triggering) actions in an application. It takes an action object as an argument and invokes the action.useDispatchis the hook’s equivalence tomapDispatchToProps.On the other hand,
useSelectoris a Redux hook for assessing Redux states. It takes a function to select the exact Redux reducer from the store and then returns the corresponding states.Once our Redux store is connected to a React application through the Redux provider, we can invoke the actions with
useDispatchand access the states withuseSelector. Every Redux action and state can be assessed with these two hooks.Note that these states ship with React Redux (a package that makes assessing the Redux store easy in a React application). They are not available in the core Redux library.
These hooks are very simple to use. First, we have to declare the dispatch function and then trigger it.
import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExampleIn the above code, we imported
useDispatchanduseSelectorfromreact-redux. Then, in auseEffecthook, we dispatched the action. We could define the action in another file and then call it here or we could define it directly as shown in theuseEffectcall.Once we have dispatched the actions, our states will be available. We can then retrieve the state using the
useSelectorhook as shown. The states can be used in the same way we would use states from theuseStatehook.Let’s take a look at an example to demonstrate these two hooks.
To demonstrate this concept, we have to create a Redux store, reducer, and actions. To simplify things here, we’ll be using the Redux Toolkit library with our fake database from JSONPlaceholder.
We need to install the following packages to get started. Run the following bash commands.
npm i redux @reduxjs/toolkit react-redux axiosFirst, let’s create the
employeesSlice.jsto handle the reducer and action for our employees’ API.import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: []loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;This is the standard setup for the Redux toolkit. We used the
createAsyncThunkto access theThunkmiddleware to perform async actions. This allowed us to fetch the list of employees from the API. We then created theemployeesSliceand returned, “loading”, “error”, and the employees’ data depending on the action types.Redux toolkit also makes setting up the store easy. Here is the store.
import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;Here, we used
combineReducersto bundle the reducers and theconfigureStorefunction provided by Redux toolkit to set up the store.Let’s proceed to use this in our application.
First, we need to connect Redux to our React application. Ideally, this should be done at the root of our application. I like to do it in the
index.jsfile.import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( , rootElement );Here, I’ve imported the store I created above and also
Providerfromreact-redux.Then, I wrapped the entire application with the
Providerfunction, passing the store to it. This makes the store accessible throughout our application.We can then proceed to use the
useDispatchanduseSelectorhooks to fetch the data.Let’s do this in our
App.jsfile.import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ({loading ? ( "Loading..." ) : error ? (); }{error}) : ( <>List of Employees
{employees.map((employee) => ())} > )}{`${employee.firstName} ${employee.lastName}`}
In the above code, we used the
useDispatchhook to invoke thefetchEmployeesaction created in theemployeesSlice.jsfile. This makes the employees state to be available in our application. Then, we used theuseSelectorhook to get the states. Thereafter, we displayed the results by mapping through theemployees.The
useHistoryHookNavigation is very important in a React application. While you could achieve this in a couple of ways, React Router provides a simple, efficient and popular way to achieve dynamic routing in a React application. Furthermore, React Router provides a couple of hooks for assessing the state of the router and performing navigation on the browser but to use them, you need to first set up your application properly.
To use any React Router hook, we should first wrap our application with
BrowserRouter. We can then nest the routes withSwitchandRoute.But first, we have to install the package by running the following commands.
npm install react-router-domThen, we need to set up our application as follows. I like to do this in my
App.jsfile.import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return (); }We could have as many Routes as possible depending on the number of components we wish to render. Here, we have rendered only the
Employeescomponent. Thepathattribute tells React Router DOM the path of the component and can be assessed with query string or various other methods.The order matters here. The root route should be placed below the child route and so forth. To override this order, you need to include the
exactkeyword on the root route.Now that we have set up the router, we can then use the
useHistoryhook and other React Router hooks in our application.To use the
useHistoryhook, we need to first declare it as follows.import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }If we log history to the console, we’ll see several properties associated with it. These include
blockcreateHrefgogoBackgoForwardlengthlistenlocationpushreplace. While all these properties are useful, you will most likely usehistory.pushandhistory.replacemore often than other properties.Let’s use this property to move from one page to another.
Assuming we want to fetch data about a particular employee when we click on their names. We can use the
useHistoryhook to navigate to the new page where the employee’s information will be displayed.function moveToPage = (id) =>{ history.push(`/employees/${id}`) }We can implement this in our
Employee.jsfile by adding the following.import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return (...); }List of Employees
{employees.map((employee) => ({`${employee.firstName} ${employee.lastName} `}))}In the
pushToPagefunction, we usedhistoryfrom theuseHistoryhook to navigate to the employee’s page and pass the employee id alongside.The
useLocationHookThis hook also ships with React Router DOM. It is a very popular hook used to work with the query string parameter. This hook is similar to the
window.locationin the browser.import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExampleThe
useLocationhook returns thepathnamesearchparameter,hashandstate. The most commonly used parameters include thepathnameandsearchbut you could equally usehashandstatea lot in your application.The location
pathnameproperty will return the path we set in ourRouteset up. Whilesearchwill return the query search parameter if any. For instance, if we pass'http://mywebsite.com/employee/?id=1'to our query, thepathnamewould be/employeeand thesearchwould be?id=1.We can then retrieve the various search parameters using packages like query-string or by coding them.
The
useParamsHookIf we set up our Route with a URL parameter in its path attribute, we can assess those parameters as key/value pairs with the
useParamshook.For instance, let’s assume that we have the following Route.
The Route will be expecting a dynamic id in place of
:id.With the
useParamshook, we can assess the id passed by the user, if any.For instance, assuming the user passes the following in function with
history.push,function goToPage = () => { history.push(`/employee/3`) }We can use the
useParamshook to access this URL parameter as follows.import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return(...) } export default ParamsExampleIf we log
paramsto the console, we’ll get the following object{id: "3"}.The
useRouteMatchHookThis hook provides access to the match object. It returns the closest match to a component if no argument is supplied to it.
The match object returns several parameters including the
path(the same as the path specified in Route), theURLparamsobject, andisExact.For instance, we can use
useRouteMatchto return components based on the route.import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( ) : ( ); }; export default CustomRoute;In the above code, we set a route’s path with
useRouteMatchand then rendered the or component depending on the route selected by the user.For this to work, we still need to add the route to our
App.jsfile.... ...Building A Custom Hook
According to the React documentation, building a custom hook allows us to extract a logic into a reusable function. However, you need to make sure that all the rules that apply to React hooks apply to your custom hook. Check the rules of React hook at the top of this tutorial and ensure that your custom hook complies with each of them.
Custom hooks allow us to write functions once and reuse them whenever they are needed and hence obeying the DRY principle.
For instance, we could create a custom hook to get the scroll position on our page as follows.
import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };Here, we defined a custom hook to determine the scroll position on a page. To achieve this, we first created a state,
scrollPosto store the scroll position. Since this will be modifying the DOM, we need to useuseLayoutEffectinstead ofuseEffect. We added a scroll event listener to capture the x and y scroll positions and then cleaned up the event listener. Finally, we returned to the scroll position.We can use this custom hook anywhere in our application by calling it and using it just as we would use any other state.
import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default AppHere, we imported the custom hook
useScrollPoswe created above. Then we initialized it and then logged the value to our console. If we scroll on the page, the hook will show us the scroll position at every step of the scroll.We can create custom hooks to do just about anything we can imagine in our app. As you can see, we simply need to use the inbuilt React hook to perform some functions. We can also use third-party libraries to create custom hooks but if we do so, we will have to install that library to be able to use the hook.
Conclusion
In this tutorial, we took a good look at some useful React hooks you will be using in most of your applications. We examined what they present and how to use them in your application. We also looked at several code examples to help you understand these hooks and apply them to your application.
I encourage you to try these hooks in your own application to understand them more.
Resources From The React Docs
(ks, vf, yk, il)
Source link

