Est-ce que je vous ai déjà parlé du render (rendu) d'une application React ?
Dès qu'une modification intervient dans une prop ou le state, le composant concerné et ses enfants sont re-render.
Mais comment faire si on veut effectuer une action qui ne fait pas partie du return ? Qui intervient après que React a mis à jour le DOM ? Par exemple, si vous voulez déclencher une alerte à chaque fois que votre panier est mis à jour ? Ou bien même pour sauvegarder ce panier à chaque mise à jour ?
Eh bien, ces types d'actions s'appellent des effets de bord, et pour cela, nous avons useEffect
. 😎 Ils nous permettent d'effectuer une action à un moment donné du cycle de vie de nos composants.
Découvrez useEffect
Essayons dès maintenant. Disons que je veux créer une alerte lorsque j'ajoute une plante à mon panier, et que cette alerte affiche le montant total du panier.
Pour ça, une petite ligne de code suffit dans Cart.js
:
alert(`J'aurai ${total}€ à payer 💸`)
On la met donc directement dans notre composant, avant le return
. Allez-y, essayez !
😨 Quand je clique, ça bloque mon code et ma valeur ne s'affiche qu'une fois que j'ai cliqué sur "OK" ! Qu’est-ce qui se passe ?
Pas de panique ! Faites-moi confiance : nous n'allons pas en rester là.
À la place, on va utiliser useEffect
.
Importez-le comme nous l’avons fait avec useState
dans Cart.js
:
import { useState, useEffect } from 'react'
et utilisez ce snippet à la place (toujours dans Cart.js
) :
useEffect(() => {
alert(`J'aurai ${total}€ à payer 💸`)
})
Ce qui nous donne pour Cart.js
:
function Cart({ cart, updateCart }) {
const [isOpen, setIsOpen] = useState(true)
const total = cart.reduce(
(acc, plantType) => acc + plantType.amount * plantType.price,
0
)
useEffect(() => {
alert(`J'aurai ${total}€ à payer 💸`)
})
return isOpen ? (
<div className='lmj-cart'>
<button
className='lmj-cart-toggle-button'
onClick={() => setIsOpen(false)}
>
Fermer
</button>
{cart.length > 0 ? (
<div>
<h2>Panier</h2>
<ul>
{cart.map(({ name, price, amount }, index) => (
<div key={`${name}-${index}`}>
{name} {price}€ x {amount}
</div>
))}
</ul>
<h3>Total :{total}€</h3>
<button onClick={() => updateCart([])}>Vider le panier</button>
</div>
) : (
<div>Votre panier est vide</div>
)}
</div>
) : (
<div className='lmj-cart-closed'>
<button
className='lmj-cart-toggle-button'
onClick={() => setIsOpen(true)}
>
Ouvrir le Panier
</button>
</div>
)
}
Et voilààà ! Tout se passe comme espéré, pour la simple et bonne raison que useEffect
nous permet d'effectuer notre effet une fois le rendu du composant terminé. Et comme useEffect
est directement dans notre composant, nous avons directement accès à notre state, à nos variables, nos props, magique n'est-ce pas ?
Mais attendez, qu'est-ce qui se passe si je ferme mon panier ?
Nooon ! 😭 Mon alerte se déclenche aussi !
Eh bien c'est normal : je vous ai dit que useEffect
se déclenche après le rendu. Eh bien il se déclenche après CHAQUE rendu du composant. Sauf si vous...
Précisez quand déclencher un effet avec le tableau de dépendances
Pour décider précisément quand on veut déclencher un effet, on peut utiliser le tableau de dépendances. Il correspond au deuxième paramètre passé à useEffect
.
Cette fonction correspond à l'effet à exécuter. Ici, il s'agit de :
() => {
alert(`J'aurai ${total}€ à payer 💸`)
}
Le deuxième paramètre de useEffect
accepte un tableau noté entre crochets : il s'agit du tableau de dépendances.
Dans notre cas, si je veux que l'alerte ne s'affiche que lorsque le total de mon panier change, il me suffit de faire :
useEffect(() => {
alert(`J'aurai ${total}€ à payer 💸`)
}, [total])
Vous pouvez mettre n'importe quelle variable ici. Si vous voulez afficher l'alerte quand le total change OU quand une nouvelle catégorie est sélectionnée, vous pourriez tout à fait :
récupérer la catégorie sélectionnée (en faisant remonter
activeCategory
etsetActiveCategory
et en les passant en props) ;puis mettre
[total, activeCategory]
dans votre tableau de dépendances.
Essayez pour voir !
L'alerte s'affiche bien quand la catégorie change ou bien quand le total change.
Et… est-ce que l'effet est lancé au tout premier render de mon composant ?
Essayez de rafraîchir la page pour voir ! L'alerte s'affiche. Donc la réponse est oui.
D'ailleurs, cette question me fait rebondir sur une autre question :
Comment faire pour exécuter un effet uniquement après le premier render de mon composant ? Par exemple, si je veux récupérer des données sur une API ?
Eh bien, dans ce cas, il faut renseigner un tableau de dépendances vide :
useEffect(() => {
alert('Bienvenue dans La maison jungle')
}, [])
Modifiez le titre de votre onglet
Bon, moi je commence à en avoir un peu marre de toutes ces alertes. J'ai plutôt envie d'utiliser useEffect
pour mettre à jour le titre de l'onglet de mon navigateur.
Vous voyez de quoi je parle ?
On va donc utiliser document.title
toujours dans Cart.js
, comme ici :
useEffect(() => {
document.title = `LMJ: ${total}€ d'achats`
}, [total])
Et voilàààà ! Le titre de notre onglet change en fonction du total de notre panier ! 🎉
![titre de l'onglet modifié par useEffect](https://user.oc-static.com/upload/2023/03/27/16799375045377_onglet-chrome.png)
Maîtrisez les règles de useEffect
Intégrez les différentes étapes de useEffect
Repassons sur ce qu’on vient de voir de useEffect et d’autres utilisations dans cette vidéo !
Intégrez quelques règles
Comme je vous l'ai expliqué au chapitre précédent, useEffect
est un hook, une fonction qui permet de « se brancher » sur la fonctionnalité des effets de React. Mais quelques règles particulières s'appliquent au hook useEffect
:
Appelez toujours
useEffect
à la racine de votre composant. Vous ne pouvez pas l'appeler à l’intérieur de boucles, de code conditionnel ou de fonctions imbriquées. Ainsi, vous vous assurez d'éviter des erreurs involontaires.Comme pour
useState
,useEffect
est uniquement accessible dans un composant fonction React. Donc ce n'est pas possible de l'utiliser dans un composant classe, ou dans une simple fonction JavaScript.
Par ailleurs, je vous conseille de séparer les différentes actions effectuées dans différents useEffect. Cela est plutôt une bonne pratique qu’une règle.
Exercez-vous
Nous allons maintenant pouvoir mettre en pratique ce que vous avez appris. Comme toujours, vous trouverez la base de code sur la branche P3C3-Begin.
Ici, vous allez permettre à l'utilisateur de garder son panier, même lorsqu'il rafraîchit la page. Pour cela, nous allons utiliser localStorage. Afin d'assurer la meilleure expérience utilisateur possible, vous devez :
sauvegarder le panier à chaque modification avec
localStorage.setItem()
. Attention, le cart étant maintenant un objet, il vous faudra passer parJSON.stringify
(vous avez la documentation ici) puisJSON.parse
(et à nouveau la doc ici) ;vous assurer que le panier n'est pas sauvegardé plus de fois que nécessaire ;
récupérer le panier au premier chargement de la page avec
localStorage.getItem
.
Une fois que vous aurez fini, vous pourrez comparer votre code avec le mien sur P3C3-Solution.
En résumé
useEffect
permet d'effectuer des effets : cela permet à notre composant d'exécuter des actions après l'affichage, en choisissant à quel moment cette action doit être exécutée.Le hook
useEffect
est appelé après chaque rendu de votre composant. Il est possible de préciser quelle modification de donnée déclenche les effets exécutés dans useEffect, avec le tableau de dépendances.Un tableau de dépendances vide permet d'exécuter un effet uniquement au premier rendu de votre composant.
Félicitations à vous ! Vous avez presque terminé ce cours - il ne vous reste qu'un quiz 🎉 Puis, dans la conclusion, nous ferons un petit récapitulatif de ce que vous avez appris, et je vous donnerai quelques pistes pour poursuivre votre apprentissage. Alors à tout de suite ! 😊