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 connect
nous 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 avoiruseState
useEffect
etc. 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
react
pour ê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
count
est définie sur0
et 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 hookuseState
et les états de composants basés sur les classes. Il est à noter que le hookuseState
renvoie un tableau, également connu sous le nom de variables d'état et dans l'exemple ci-dessus, nous avons déstructuré le tableau enstate
et la fonctionupdater
.Rendu des composants
La définition des états avec le hook
useState
provoque 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
useState
Notre état
count
peut être défini aux nouvelles valeurs d'état en passant simplement la nouvelle valeur à la fonction de mise à joursetCount
comme 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
count
chaque 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(
{count}
Dans le code ci-dessus, nous avons d'abord importé le hook
useState
dereact
puis initialisé l'étatcount
avec une valeur par défaut de 0. Nous avons créé un gestionnaireonClick
pour incrémenter la valeur decount
de 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
React
compare les états avec l'opérationObject.is
puis 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(
Dans le code ci-dessus, nous avons créé deux états
arr
etobj
et les avons initialisés à des valeurs de tableau et d'objet respectivement. Nous avons ensuite créé des gestionnairesonClick
appeléshandleArrClick
ethandleObjClick
pour définir respectivement les états du tableau et de l'objet. LorsquehandleArrClick
se déclenche, nous appelonssetArr
et 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ésetObj
diffusé les valeurs d'objet existantes à l'aide de l'opérateur de diffusion ES6 et mis à jour les valeurs dename
etage
.Async Nature Of
useState
Comme nous l'avons déjà vu, nous définissons les états avec
useState
en 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 gestionnaireonClick
pour incrémenter l'étatcount
chaque fois que le bouton est cliqué.
Notez que bien que l'étatcount
ait augmenté, comme indiqué dans la baliseh2
l'é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é
newCountValue
créé pour stocker la valeur de comptage mise à jour, puis défini l'étatcount
avec la valeur mise à jour. Ensuite, nous avons enregistré la valeur de comptage mise à jour dans la console.Le
useEffect
HookuseEffect
est un autre hook React important utilisé dans la plupart des projets. Il fait la même chose que les méthodes de cycle de viecomponentDidMount
componentWillUnmount
etcomponentDidUpdate
du composant basé sur les classes.useEffect
nous 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
useEffect
s'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é
count
dans 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
useEffect
a 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
useEffect
une seule fois, sur le support. Cela affichera la valeur1
dans la baliseh1
car 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
useEffect
s'exécute chaque fois quecount
change comme suit.import { useState, useEffect } de "react" ; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(compte); }, [count]); revenir (
Le
useEffect
ci-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
compte
change.
Au montage, l'expression
console.log
s'exécutera et consigneracount
à 0. Une fois lecount
mis à jour, le deuxième la condition est remplie, donc leuseEffect
s'exécute à nouveau, cela continuera chaque fois que le bouton est cliqué.Une fois que nous avons fourni le deuxième argument à
useEffect
il est prévu que nous passons tout les dépendances à celui-ci. Si vous avez installéESLINT
une 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
useEffect
nous 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
useEffect
ci-dessus enregistreramount
lorsque 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
useEffect
il 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
useEffect
L'une des utilisations les plus courantes les cas du crochet
useEffect
ré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
JSONPlaceholder
pour récupérer des données avec leJSONPlaceholder
hook 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
useEffect
pour obtenir un utilisateur basé sur leid
. CeuseEffect
récupèrera les données chaque fois que l'identifiant changera. Pour garantir cela, nous avons passé l'id
dans la liste des dépendances.Ensuite, nous avons créé des fonctions pour mettre à jour la valeur de notre
id
chaque fois que les boutons sont cliqués. Une fois la valeur deid
modifiée,useEffect
s'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
useReducer
Le crochet
useReducer
est 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 hookuseState
est 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,
init
est une fonction et elle est utilisée chaque fois que nous voulons créer l'état initial paresseusement.Regardons comment implémentez le hook
useReducer
en 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
Redux
le 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 à jourstate
et de l'écouter.Ensuite, nous avons utilisé les instructions
switch
pour vérifier le type d'action transmis par l'utilisateur. Si le type d'action estADD_TODO
on veut passer une nouvelle to-do et si c'estREMOVE_TODO
on veut filtrer les to-dos et supprimer celle qui correspond auid
transmis par l'utilisateur. S'il s'agit deCOMPLETE_TODO
nous souhaitons mapper les tâches à effectuer et basculer celle avec l'id
transmis par l'utilisateur.Voici le
App.js
dans 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_TODO
en 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 hookuseState
et 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 hookuseState
mais comme vous pouvez le voir dans l'exemple ci-dessus, le hookuseReducer
nous a aidé à garder les choses plus propres.useReducer
est 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,useReducer
permet 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
useReducer
pour 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
init
comme 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
initFunc
remplacera leinitialState
que nous avons fourni et l'état initial sera calculé paresseusement.Le crochet
useContext
L'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.createContext
et ce contexte peut ensuite être transmis au hook.Pour démontrer l'utilisation du hook
useContext
cré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
16
puis 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.Provider
et passésize
à sa valeur prop. Ici,size
est un état créé avec le hookuseState
. Cela nous permet de changer la valeur prop chaque fois que l'étatsize
change. En enveloppant le composant entier avec leProvider
nous 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); renvoie
Contenu de la première page
; } ; export default PageOne;Ici, nous avons accédé au contexte à l'aide du hook
useContext
de 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
useContext
EtuseReducer
When used with the
useReducer
hook,useContext
allows 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
todoContext
in thetodoContext.js
file.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.js
file 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.js
file with theTodoContext.Provider
then we passed the return values of ourtodoReducer
to it. This makes the reducer’s state anddispatch
function 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.js
file.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
useContext
hook. We can then map through the state and display the to-do items. We still extracted this in aTodo
component. 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
Todo
component.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 (
{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
completeTodo
andremoveTodo
function as already discussed in theuseReducer
section. With thetodo
prop passed fromtodoList.js
we 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.js
file 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.light
as the initial value. Let’s define the colors with this property in thecolors.js
file.const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;
In the code above, we created a
colors
object containing light and dark properties. Each property hasbackgroundColor
andcolor
object.Next, we create the
themeReducer
to 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
themeReducer
takes the state and the action. It then uses theswitch
statement to determine the current action. If it’s of typeLIGHT
we simply assignColors.light
props and if it’s of typeDARK
we displayColors.dark
props. We could have easily done this with theuseState
hook but we chooseuseReducer
to drive the point home.Having set up the
themeReducer
we can then integrate it in ourApp.js
file.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
ThemeContext
themeReducer
ThemeToggler
andColors
. We created a reducer using theuseReducer
hook, passing thethemeReducer
and an initial value ofColors.light
to it. This returned thethemeState
andthemeDispatch
to us.We then nested our component with the provider function from the
ThemeContext
passing thethemeState
and thedispatch
functions to it. We also added theme styles to it by spreading out thethemeStates
. This works because thecolors
object already defined properties similar to what the JSX styles will accept.However, the actual theme toggling happens in the
ThemeToggler
component. 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 (
In this component, we used the
useContext
hook to retrieve the values we passed to theThemeContext.Provider
from ourApp.js
file. As shown above, these values include theThemeState
dispatch 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 stateshowLight
to determine the current theme. This allows us to easily change the button text depending on the current theme.The
useMemo
HookThe
useMemo
hook 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,useMemo
will 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
useMemo
hook.- When the dependency values, a and b remain the same.
TheuseMemo
hook 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
axios
and theasync/await
method in the firstuseEffect
and 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
useMemo
hook 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
useMemo
hook.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
useMemo
may be necessary. This is also a fairly complex calculation and so we may need to memorize it withuseMemo
as shown above.After calculating the values, we simply displayed the result.
Note the following about the
useMemo
hook.useMemo
should 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
useMemo
hook may even compound the performance issues. - Sometimes, too much memoization can also cause performance issues.
The
useCallback
HookuseCallback
serves the same purpose asuseMemo
but it returns a memoized callback instead of a memoized value. In other words,useCallback
is the same as passinguseMemo
without 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
memoResult
andcallbackResult
will give the same value of12
. Here,useCallback
will return a memoized value. However, we could also make it return a memoized callback by passing it as a function.The
useCallback
below 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
useEffect
hook.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 (
In the above code, we defined a callback function using the
useCallback
hook. We then called the callback in auseEffect
hook when the component mounts and also when a button is clicked.Both the
useEffect
and the button click yield the same result.Note that the concepts, do’s, and don’ts that apply to the
useMemo
hook also apply to theuseCallback
hook. We can recreate theuseMemo
example withuseCallback
.The
useRef
HookuseRef
returns an object that can persist in an application. The hook has only one property,current
and we can easily pass an argument to it.It serves the same purpose a
createRef
used in class-based components. We can create a reference with this hook as follows:const newRef = useRef('')
Here we created a new ref called
newRef
and 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(
This is a h1 element
In the example above, we defined
headingRef
using theuseRef
hook passing an empty string. We then set the ref in theh1
tag by passingref = {headingRef}
. By setting this ref, we have asked theheadingRef
to point to ourh1
element. This means that we can access the properties of ourh1
element 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.current
can be read in the same way asdocument.querySelector('.topheading')
.One interesting use case of the
useRef
hook 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(
In the above code, we created
inputRef
using theuseRef
hook 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.js
and add the following codes to it.import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return ; }); export default NewInput;
This component accepts
props
andref
. We passed the ref to its ref prop andprops.val
to its placeholder prop. Regular React components do not take aref
attribute. This attribute is available only when we wrap it withReact.forwardRef
as 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
countRef
when 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
useState
is asynchronous,useRef
is synchronous. In other words, the value is available immediately after it is updated.The
useLayoutEffect
HookLike the
useEffect
hook,useLayoutEffect
is 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,useLayoutEffect
does the same thing asuseEffect
.useLayoutEffect
should only be used for performing DOM mutation or DOM-related measurement, otherwise, you should use theuseEffect
hook. Using theuseEffect
hook for DOM mutation functions may cause some performance issues such as flickering butuseLayoutEffect
handles 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 (
width: {windowSize.width}
height: {windowSize.height}
In the above code, we created a state
windowSize
with 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 inuseLayoutEffect
to 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
useRef
anduseLayoutEffect
together in the above code. We first created a ref,paragraphRef
to 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
useDispatch
AnduseSelector
HooksuseDispatch
is a Redux hook for dispatching (triggering) actions in an application. It takes an action object as an argument and invokes the action.useDispatch
is the hook’s equivalence tomapDispatchToProps
.On the other hand,
useSelector
is 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
useDispatch
and 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 ReduxHooksExample
In the above code, we imported
useDispatch
anduseSelector
fromreact-redux
. Then, in auseEffect
hook, 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 theuseEffect
call.Once we have dispatched the actions, our states will be available. We can then retrieve the state using the
useSelector
hook as shown. The states can be used in the same way we would use states from theuseState
hook.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 axios
First, let’s create the
employeesSlice.js
to 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
createAsyncThunk
to access theThunk
middleware to perform async actions. This allowed us to fetch the list of employees from the API. We then created theemployeesSlice
and 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
combineReducers
to bundle the reducers and theconfigureStore
function 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.js
file.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
Provider
fromreact-redux
.Then, I wrapped the entire application with the
Provider
function, passing the store to it. This makes the store accessible throughout our application.We can then proceed to use the
useDispatch
anduseSelector
hooks to fetch the data.Let’s do this in our
App.js
file.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
useDispatch
hook to invoke thefetchEmployees
action created in theemployeesSlice.js
file. This makes the employees state to be available in our application. Then, we used theuseSelector
hook to get the states. Thereafter, we displayed the results by mapping through theemployees
.The
useHistory
HookNavigation 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 withSwitch
andRoute
.But first, we have to install the package by running the following commands.
npm install react-router-dom
Then, we need to set up our application as follows. I like to do this in my
App.js
file.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
Employees
component. Thepath
attribute 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
exact
keyword on the root route.Now that we have set up the router, we can then use the
useHistory
hook and other React Router hooks in our application.To use the
useHistory
hook, 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
block
createHref
go
goBack
goForward
length
listen
location
push
replace
. While all these properties are useful, you will most likely usehistory.push
andhistory.replace
more 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
useHistory
hook 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.js
file 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
pushToPage
function, we usedhistory
from theuseHistory
hook to navigate to the employee’s page and pass the employee id alongside.The
useLocation
HookThis 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.location
in the browser.import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample
The
useLocation
hook returns thepathname
search
parameter,hash
andstate
. The most commonly used parameters include thepathname
andsearch
but you could equally usehash
andstate
a lot in your application.The location
pathname
property will return the path we set in ourRoute
set up. Whilesearch
will return the query search parameter if any. For instance, if we pass'http://mywebsite.com/employee/?id=1'
to our query, thepathname
would be/employee
and thesearch
would be?id=1
.We can then retrieve the various search parameters using packages like query-string or by coding them.
The
useParams
HookIf we set up our Route with a URL parameter in its path attribute, we can assess those parameters as key/value pairs with the
useParams
hook.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
useParams
hook, 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
useParams
hook 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
params
to the console, we’ll get the following object{id: "3"}
.The
useRouteMatch
HookThis 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), theURL
params
object, andisExact
.For instance, we can use
useRouteMatch
to 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
useRouteMatch
and 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.js
file.... ...
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,
scrollPos
to store the scroll position. Since this will be modifying the DOM, we need to useuseLayoutEffect
instead 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 App
Here, we imported the custom hook
useScrollPos
we 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