Si vous avez suivi le premier cours, vous avez découvert les hooks avec useState
et useEffect
. Ensemble, nous avons appris à les utiliser pour faire des calls API. Et dans le chapitre précédent, vous avez découvert le Contexte et comment y accéder simplement avec useContext
: vous commencez à avoir quelques cartes en main pour utiliser les hooks dans vos applications React. 🎉
Et si je vous disais que vous pouvez créer vos propres hooks ?
Créez vos propres hooks pour simplifier votre code
Je ne sais pas ce que ça évoque pour vous, mais moi la première fois qu'on m'a dit que je pouvais créer mes propres hooks, ça m’a un peu fait paniquer. Je m’imaginais devoir travailler sur la codebase de React, et devoir créer un de ces fichiers immenses pour pouvoir faire tourner un hook “custom”.
Détrompez-vous, la création d’un hook “custom” est toute simple : il s’agit juste d’une fonction qui commence par “use”, qui extrait de la logique réutilisable et qui peut utiliser d’autres hooks.
Mettons en application ce que nous avons vu dès maintenant.
Créez un hook pour vos calls API
Dans le premier chapitre, vous avez appelé l’API du projet Shiny pour récupérer les profils des freelances, ainsi que les questions de notre questionnaire.
Si vous regardez votre code d’un peu plus près, vous aussi vous voyez une certaine répétition ? C’est normal. On va donc essayer de mutualiser tout ça avec un hook “personnalisé” !
Pour cela, on crée dans /utils
un nouveau dossier qu’on appelle /hooks
et dans lequel on crée un ficher index.jsx
.
On va créer notre hook qu'on base cette fois-ci sur la syntaxe async
/ await
:
import { useState, useEffect } from 'react'
export function useFetch(url) {
const [data, setData] = useState({})
const [isLoading, setLoading] = useState(true)
useEffect(() => {
if (!url) return
async function fetchData() {
const response = await fetch(url)
const data = await response.json()
setData(data)
setLoading(false)
}
setLoading(true)
fetchData()
}, [url])
return { isLoading, data }
}
Le code est plutôt explicite, n’est-ce pas ? Pour notre nouveau hook, useFetch
, on lui passe en paramètre l’URL de l’API qu’on veut appeler. Il possède un state interne qui lui permet de stocker la data, et de savoir si la data est en train de charger avec isLoading
.
Dans useEffect
, le hook fait un return
vide si le paramètre de l’URL est vide, et commence par mettre isLoading
à true
. Il déclare la fonction asynchrone fetchData
qui permet de :
appeller
fetch
;parser ce qui est retourné avec
data.json()
;et changer l’état de
isLoading
.
url
fait partie du tableau de dépendances du useEffect
, ce qui permettra de redéclencher le call en cas de changement d’URL passée en paramètre. Puis, on appelle notre fonction fetchData
.
Pour utiliser notre nouveau hook, modifions /Survey/index.jsx
. On commence par importer notre hook avec :
import { useFetch } from '../utils/hooks'
Puis on récupère notre data
avec :
const { data, isLoading } = useFetch(`http://localhost:8000/survey`)
const { surveyData } = data
Je vous invite aussi à supprimer tout le contenu du useEffect
qui permettait d’effectuer le fetch
. J’adore supprimer du code inutile : c’est super satisfaisant, non ? 😍
On n’oublie pas de remplacer isDataLoading
par isLoading
qui sera plus générique ici, et de remplacer surveyData
par data
.
Est-ce que ça marche toujours ?
... Oh, on a une erreur qui empêche le code de tourner ! 😭
Pas de panique, c’est normal ! Pour faire la navigation, on a accédé au contenu de notre objet de questions en faisant data[questionNumber]
. Sauf qu’à l’initialisation, data
est un objet vide... Ce qui veut dire qu'avec :
const { surveyData } = data
surveyData
est undefined. JavaScript provoque donc une erreur pour data[questionNumber]
. Une des manières d’éviter l’erreur est donc de vérifier que surveyData
est défini avant de l'utiliser dans le composant :
<QuestionContent>
{surveyData && surveyData[questionNumber]}
</QuestionContent>
Et voilà, notre page fonctionne à nouveau comme avant, en utilisant notre hook useFetch
, ce qui a permis de supprimer pas mal de code répétitif ! 🎉
Ajoutez une gestion d’erreur
Mais que se passe-t-il quand l’API nous renvoie une erreur ? Notre application ne se comportera pas comme prévu, et l’utilisateur n’en sera même pas informé. Je vous propose d’intégrer une gestion d’erreur dans notre hook useFetch
afin d'afficher à l'écran qu'il y a eu un problème.
Pour cela, dans utils/hooks/index.jsx
, on peut créer un state pour error
avec :
const [error, setError] = useState(false)
Puis, on ajoute un try
et un catch
à notre cher hook useFetch
:
export function useFetch(url) {
const [data, setData] = useState({})
const [isLoading, setLoading] = useState(true)
const [error, setError] = useState(false)
useEffect(() => {
if (!url) return
setLoading(true)
async function fetchData() {
try {
const response = await fetch(url)
const data = await response.json()
setData(data)
} catch (err) {
console.log(gerr)
setError(true)
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
return { isLoading, data, error }
}
Ce qui nous permet de passer error
à true
lorsqu'un problème est rencontré.
De la même manière, dans Survey.jsx
, on récupère maintenant error
:
const { data, isLoading, error } = useFetch(`http://localhost:8000/survey`)
Et on peut tout simplement ajouter au-dessus du return
:
if (error) {
return <span>Il y a un problème</span>
}
Et voilà, vous avez même géré les erreurs d’appel API ! 🎉
Nous allons en profiter pour utiliser useFetch
pour envoyer les réponses de l'utilisateur, et donc récupérer les résultats de notre API depuis la page /results
.
Je vous montre tout ça dans le screencast juste en dessous. 👇
C'est pas mal ce useFetch
, je vais pouvoir l'implémenter dans toutes les bases de code en production ?
Le hook useFetch
que nous avons codé ici est très pratique pour éviter de nous répéter… et pour pratiquer la création de hooks personnalisés. Mais dans les faits, ce code est un peu trop basique pour être utilisé en production. À la place, vous pouvez utiliser un outil bien plus robuste tel que TanStack Query
pour React , qui vous permet d'utiliser des hooks pour faire vos requêtes et les mettre en cache dans vos applications.
Maîtrisez les hooks
Intégrez les règles des hooks
Je le rappelle ici : les hooks ont leurs propres règles d’utilisation.
Les hooks sont uniquement accessibles dans un composant fonction React. Donc ce n'est pas possible d’en utiliser dans un composant classe ou bien dans une simple fonction JavaScript.
Appelez les hooks au niveau racine de vos composants.
Attention au nommage de vos hooks personnalisés : même s’il ne s’agit pas vraiment d’une règle obligatoire, mais d’une convention, vos hooks personnalisés doivent commencer par
use
pour que l’on sache en un coup d’œil qu’il s’agit d’un hook.
Pour l’instant, voici quelques hooks que vous pourriez être susceptible de croiser dans différentes codebases :
useRef
Je vous laisse regarder par vous-même sur la documentation si ça vous intéresse, mais même s’il existe plusieurs utilisations de useRef
, ce hook est avant tout utilisé pour interagir avec des éléments du DOM.
useReducer
useReducer permet de mieux gérer votre state lorsqu’il comporte de nombreuses propriétés qui doivent être modifiées régulièrement.
useMemo et useCallback
Ces deux hooks nous permettent d’éviter de refaire des calculs coûteux pour nos performances. Vous pouvez préciser des valeurs pour lesquelles il faudra refaire les calculs uniquement si l’un des paramètres change, grâce à useMemo
et useCallback
.
Et il en existe encore d’autres… À l’heure où ce cours a été écrit, nous en sommes à la version 17 de React. Mais nous ne sommes pas à l’abri que d’autres hooks soient créés entretemps.
Exercez-vous
Maintenant que nous avons créé un hook personnalisé pour faire des calls API useFetch
, vous allez pouvoir l’utiliser pour les pages /Freelances
ainsi que /Results
.
Vous allez également devoir créer un hook personnalisé useTheme
qui récupère le Contexte du theme
et vous permet de récupérer le thème actuel ( dark
ou light
).
À partir de là, vous pouvez intégrer les changements de style pour le thème, et intégrer la page /results
en fonction des maquettes si vous voulez vous challenger (ou récupérer le CSS sur la branche solution si vous préférez passer moins de temps sur le CSS).
Comme d’habitude, vous trouverez le début de l’exercice sur la branche P2C3-begin
et la solution sur P2C3-solution
.
En résumé
Un hook personnalisé est une fonction qui commence par “use”, qui extrait de la logique réutilisable et qui peut utiliser d’autres hooks.
Les règles des hooks s’appliquent aux hooks personnalisés, en plus de la convention de nommer son hook personnalisé avec
use...
.Il existe d’autres hooks tels que
useRef
,useReducer
,useMemo
, etc.
Bon, votre application commence vraiment à ressembler à quelque chose ! J’espère que cet aperçu un peu plus poussé des hooks vous a bien plu. Mais comment vous assurer de ne pas tout casser si vous faites des modifications dans 6 mois… ou un an, alors que vous ne vous souvenez plus comment votre code est implémenté ? Eh bien… par les tests. C'est ce que nous allons voir dans la prochaine partie. Mais avant cela, vous allez pouvoir tester vos connaissances dans un quiz. Alors bonne chance à vous, et on se retrouve dans quelques instants ! 💪