La gestion de l’état dans vos applications React n’est pas aussi simple que l’utilisation de useState
ou useReducer
.
Non seulement il existe de nombreux types d’états différents, mais il y a souvent des dizaines de façons de gérer chaque type. Laquelle choisir ?
Dans ce guide, nous allons découvrir les différents types d’état dans vos applications React dont vous n’avez peut-être pas conscience, ainsi que la façon de les gérer de la manière la plus efficace.
Les quatre types d’états React à gérer
Lorsque nous parlons d’état dans nos applications, il est important d’être clair sur les types d’état qui comptent réellement.
Il existe quatre principaux types d’état que vous devez gérer correctement dans vos applications React :
- État local
- État global
- État du serveur
- État de l’URL
Nous allons couvrir chacun d’entre eux en détail :
État local (UI) – L’état local est constitué de données que nous gérons dans l’un ou l’autre des composants.
L’état local est le plus souvent géré dans React à l’aide du hook useState
.
Par exemple, l’état local serait nécessaire pour afficher ou masquer un composant modal ou pour suivre les valeurs d’un composant de formulaire, comme la soumission du formulaire, lorsque le formulaire est désactivé et les valeurs des entrées d’un formulaire.
État global (UI) – L’état global est constitué des données que nous gérons sur plusieurs composants.
L’état global est nécessaire lorsque nous voulons obtenir et mettre à jour des données n’importe où dans notre application, ou du moins dans plusieurs composants.
Un exemple courant d’état global est l’état de l’utilisateur authentifié. Si un utilisateur est connecté à notre application, il est nécessaire d’obtenir et de modifier ses données dans toute l’application.
Parfois, un état que nous pensons devoir être local peut devenir global.
État du serveur – Données provenant d’un serveur externe qui doivent être intégrées à l’état de notre interface utilisateur.
L’état du serveur est un concept simple, mais il peut être difficile à gérer parallèlement à tous les états locaux et globaux de l’interface utilisateur.
Plusieurs éléments d’état doivent être gérés chaque fois que vous récupérez ou mettez à jour des données provenant d’un serveur externe, notamment l’état de chargement et d’erreur.
Heureusement, il existe des outils tels que SWR et React Query qui facilitent la gestion de l’état du serveur.
État de l’URL – Données qui existent sur nos URL, y compris le nom du chemin et les paramètres de la requête.
L’état de l’URL est souvent absent en tant que catégorie d’état, mais c’est une catégorie importante.
Dans de nombreux cas, beaucoup de parties importantes de notre application dépendent de l’accès à l’état de l’URL. Essayez d’imaginer la construction d’un blog sans pouvoir récupérer un article à partir de son slug ou de son id qui se trouve dans l’URL !
Il existe sans aucun doute d’autres éléments d’état que nous pourrions identifier, mais ce sont les principales catégories sur lesquelles il convient de se concentrer pour la plupart des applications que vous construisez.
Comment gérer l’état local dans React
L’état local est peut-être le type d’état le plus facile à gérer dans React, étant donné qu’il existe de nombreux outils intégrés à la bibliothèque React de base pour le gérer.
useState
est le premier outil que vous devriez utiliser pour gérer l’état de vos composants.
Il peut accepter toute valeur de données valide, y compris les valeurs primitives et les valeurs d’objet. De plus, sa fonction setter peut être transmise à d’autres composants en tant que fonction de rappel (sans nécessiter d’optimisations comme useCallback
).
import { useState } de "react" ;
function Layout() {
const [isSidebarOpen, setSidebarOpen] = useState(false) ;
return (
<>
setSidebarOpen(false)} />
{/* ... */}
) ;
}
useReducer
est une autre option qui peut être utilisée pour l’état local ou global. Elle est similaire en de nombreux points à useState
, mais au lieu d’un état initial, elle accepte un réducteur.
L’avantage de useReducer
est qu’il fournit un moyen intégré d’effectuer un certain nombre d’opérations d’état différentes avec l’aide de la fonction reducer, ce qui le rend globalement plus dynamique que useState
.
Vous pouvez voir l’avantage de useReducer
par rapport à useState
dans cet exemple de suivi des votes. Tout ce que nous avons à faire pour mettre à jour l’état est de passer à la fonction de rappel dispatch
une chaîne (qui est ensuite passée au reducer) au lieu du nouvel état lui-même.
const initialState = { votes : 0 } ;
function reducer(state, action) {
switch (action.type) {
cas 'upvote' :
return {votes : state.votes + 1} ;
cas 'downvote' :
retourne {votes : state.votes - 1} ;
par défaut :
lancer une nouvelle erreur() ;
}
}
function VoteCounter() {
const [state, dispatch] = useReducer(reducer, initialState) ;
retour (
<>
Votes actuels : {Etat.votes}
) ;
}
Comment gérer l’état global dans React
Lorsque vous tentez de gérer l’état de plusieurs composants, les choses se compliquent un peu.
Vous atteindrez un point dans votre application où des modèles comme « lever l’état vers le haut » et passer des callbacks vers le bas pour mettre à jour votre état à partir des composants conduisent à beaucoup, beaucoup de props.
Que faites-vous si vous voulez mettre à jour l’état d’un composant depuis n’importe quel endroit de votre application ? Vous le transformez en état global.
Pour le gérer, cependant, vous devez opter pour une solution tierce. De nombreux développeurs ont tendance à utiliser les fonctionnalités intégrées de React, comme l’API Contexte, pour gérer leur état.
Soyons clairs : l’API Contexte n’est pas une solution de gestion d’état. Elle permet d’éviter des problèmes comme le forage de props (création d’un tas de props dans des composants qui n’en ont pas besoin), mais elle n’est utile que pour lire l’état, pas pour le mettre à jour.
La raison de ne pas utiliser Context pour la gestion globale de l’état réside dans la façon dont il fonctionne. Le comportement par défaut de Context est de rendre à nouveau tous les composants enfants si la valeur qui lui est fournie en tant que prop change.
Par exemple, la combinaison de useReducer
et de useContext
est une mauvaise pratique :
function App() {
const [state, dispatch] = useReducer(reducer, initialState) ;
return (
)
}
Dans de nombreux cas, vous ne souhaitez pas que tous les enfants soient mis à jour en réponse à une mise à jour de l’état global, car tous les enfants ne consomment pas forcément cet état global ou ne s’y fient pas. Vous souhaitez uniquement procéder à un nouveau rendu si leurs props ou leur état changent.
Pour gérer votre état global, faites appel à des bibliothèques tierces éprouvées et testées, comme Zustand, Jotai et Recoil
.
Redux est également excellent, mais assurez-vous de commencer par utiliser Redux Toolkit.
L’avantage d’une bibliothèque comme Zustand est qu’elle est petite, qu’elle fait de l’ensemble de votre état global un hook personnalisé, et que pour lire ou mettre à jour l’état, il suffit d’appeler ce hook dans vos composants.
Pour utiliser Zustand, exécutez npm install zustand
. Après cela, créez un fichier ou un dossier dédié au magasin et créez votre magasin :
import create de 'zustand
const useStore = create(set => ({
votes : 0,
upvote : () => set(state => ({ vote : state.votes + 1 })),
vote négatif : () => set(state => ({ vote : state.votes - 1 })),
}))
function VoteCounter() {
const { votes, upvote, downvote } = useStore() ;
retourne (
<>
Votes actuels : {votes}
) ;
}
Une grande raison pour laquelle je recommande d’utiliser Zustand plutôt qu’une bibliothèque comme Redux est qu’il vous donne toutes les fonctionnalités dont vous avez besoin sans le boilerplate et la surcharge conceptuelle des actions, des réducteurs, etc.
De plus, vous n’avez pas besoin d’envelopper vos composants dans un Context Provider. Il suffit de l’installer et d’y aller !
Comment gérer l’état du serveur dans React
L’état du serveur peut être faussement difficile à gérer.
Au début, il semble que vous ayez juste besoin de récupérer des données et de les afficher dans la page. Mais ensuite, vous devez afficher un spinner de chargement pendant que vous attendez les données. Puis vous devez gérer les erreurs et les afficher à l’utilisateur lorsqu’elles surviennent.
Que se passe-t-il en cas d’erreur de réseau ? Dois-je vraiment frapper mon serveur chaque fois que mon utilisateur visite la page d’accueil si les données n’ont pas changé ? Dois-je ajouter useState
et useEffect
dans chaque composant qui doit aller chercher mes données ?
Pour remédier à cela, il existe deux grandes bibliothèques qui font de la récupération de données dans React un jeu d’enfant : SWR et React Query.
Non seulement elles nous offrent un crochet pratique pour obtenir et modifier des données à partir d’une API, mais elles gardent la trace de tous les états nécessaires et mettent les données en cache pour nous.
Voici un exemple de récupération du profil d’un utilisateur à partir d’une API sur le client. Nous appelons useSWR
et spécifions le point de terminaison à partir duquel demander les données, qui sont transmises à notre fonction fetcher
et useSWR
nous donne à la fois les données
et l’état d’erreur
.
importation de useSWR de 'swr'
const fetcher = url => fetch(url).then(r => r.json())
function User() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return
if (!data) return
return
}
SWR facilite la gestion des demandes infructueuses et rend nos composants beaucoup plus agréables à regarder.
En outre, si vous effectuez la même opération à plusieurs reprises, vous pouvez utiliser useSWR
dans votre propre hook personnalisé pour le réutiliser dans toute votre application.
function useUser (id) {
const { data, error } = useSWR(`/api/user/${id}`, fetcher)
return {
user : données,
isLoading : !error && !data,
isError : error
}
}
function Avatar ({ id }) {
const { user, isLoading, isError } = useUser(id)
if (isLoading) return
if (isError) return
return
}
Enfin, vous pouvez fournir des options globales à useSWR
, y compris votre fonction de récupération (
afin de ne pas avoir à la passer à chaque fois) ainsi qu’un nombre de fois pour récupérer à nouveau les données après une erreur.
importez useSWR, { SWRConfig } de 'swr'
function Admin () {
// pas besoin de passer la fonction fetcher
const { data : cours } = useSWR('/api/courses')
const { data : orders } = useSWR('/api/orders')
const { data : users } = useSWR('/api/users')
// ...
}
function App () {
return (
fetch(resource, init).then(res => res.json())
}}
>
)
}
Ceci n’est qu’un avant-goût des avantages de la bibliothèque SWR, et React Query vous offre autant d’avantages, si ce n’est plus.
Assurez-vous d’utiliser l’une ou l’autre pour gérer l’état de votre serveur. Cela vous facilitera grandement la vie.
Comment gérer l’état de l’URL dans React
Pour terminer un sujet difficile sur une note positive, l’état des URL est déjà largement géré pour vous si vous utilisez un framework comme Next.js ou la version actuelle de React Router.
L’état de l’URL est assez facile à gérer, généralement grâce à des hooks personnalisés qui nous donnent toutes les informations dont nous avons besoin sur notre emplacement, notre historique et notre nom de chemin.
Si vous utilisez React Router, vous pouvez obtenir toutes les informations dont vous avez besoin en utilisant useHistory
ou useLocation
.
import { useHistory, useLocation } from 'react-router-dom' ;
function BlogPost() {
const history = useHistory() ;
console.log("vous êtes ici : ", history.location) ;
const location = useLocation() ;
console.log('votre nom de chemin est : , location.pathname) ;
// ...
}
En outre, si vous avez des paramètres de route que vous devez utiliser, par exemple pour récupérer des données, vous pouvez utiliser le crochet useParams
.
import { useParams } de 'react-router-dom' ;
function ChatRoom() {
const { roomId } = useParams() ;
const { chatRoom, isLoading, isError } = useChatRoom(roomId) ;
// ...
}
Si vous utilisez Next.js, vous pouvez accéder à presque tout directement en appelant useRouter
.
function Commandes() {
const router = useRouter() ;
console.log('l'url complète est : ', router.asPath) ;
console.log('votre route actuelle est : ', router.pathname) ;
console.log('vos paramètres de requête sont : ', router.query) ;
function handleSubmit(item) {
setQuery("") ;
// pousser vers la nouvelle route
router.push(item.href) ;
closeDropdown() ;
}
// ...
}