rework/lightweight #12
168
app/pages/docs/react/use-context/+Page.mdx
Normal file
168
app/pages/docs/react/use-context/+Page.mdx
Normal file
@ -0,0 +1,168 @@
|
||||
---
|
||||
title: Le hook useContext de React
|
||||
description: Découvrez comment utiliser le hook useContext de React pour gérer les contextes dans vos applications.
|
||||
tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)]
|
||||
---
|
||||
|
||||
import Callout from "@/components/Callout";
|
||||
import tabs from "./tabs";
|
||||
|
||||
Les contextes sont un moyen de diffuser des données au travers des composants, sans avoir à les passer explicitement à chaque composant.
|
||||
|
||||
Pour faire simple, imaginons une arborescence de plusieurs composants imbriqués les uns dans les autres :
|
||||
|
||||
<tabs.reactNestedProps />
|
||||
|
||||
Fastidieux, n'est-ce pas ? On transmet à chaque fois les mêmes données, et ce, à chaque niveau de l'arborescence.
|
||||
|
||||
C'est là que les contextes entrent en jeu !
|
||||
On va pouvoir alors déclarer notre contexte _(qui contiendra les données à diffuser)_ et le fournir à un niveau supérieur de l'arborescence.
|
||||
|
||||
## Déclaration d'un contexte
|
||||
|
||||
Avant de penser à notre contexte, on va réfléchir à ce que l'on veut diffuser et les valeurs par défaut.
|
||||
Si on reprend notre exemple avec le thème clair et sombre, on sait que l'on va vouloir diffuser la valeur du thème et une fonction pour le changer.
|
||||
|
||||
On va donc préparer le terrain en créant un fichier `ThemeContext.jsx` _(ou `ThemeContext.tsx` si tu utilises TypeScript)_ :
|
||||
|
||||
<tabs.reactCreateContext />
|
||||
|
||||
## Fournir un contexte
|
||||
|
||||
Maintenant on peut le dire : notre contexte est prêt à être utilisé !
|
||||
Il ne reste plus qu'à le fournir à notre arborescence de composants en lui créant un `Provider`.
|
||||
|
||||
<Callout type="question" title="Un provider ?">
|
||||
Un `Provider` est un composant qui va permettre de **diffuser** les données du contexte à ses enfants.
|
||||
Il est important de noter que le `Provider` doit **englober** les composants qui vont utiliser le contexte.
|
||||
</Callout>
|
||||
|
||||
Un contexte React est un objet qui contient deux propriétés : `Provider` et `Consumer`.
|
||||
|
||||
Le `Provider` est un composant qui va permettre de diffuser les données du contexte à ses enfants.
|
||||
Le `Consumer` est un composant qui va permettre de récupérer les données du contexte.
|
||||
|
||||
<tabs.reactContextProvider />
|
||||
|
||||
Mais on peut aller encore plus loin, en créant un Provider dédié à notre contexte !
|
||||
Cela permettra de simplifier l'arborescence de composants et de rendre le code plus lisible :
|
||||
|
||||
<tabs.reactContextProviderWithValues />
|
||||
|
||||
Et pour terminer, on va maintenant pouvoir directement imbriquer notre `ThemeProvider` dans notre `App` :
|
||||
|
||||
<tabs.reactContextProviderInApp />
|
||||
|
||||
## Utilisation d'un contexte
|
||||
|
||||
C'est bien beau de créer un contexte, mais comment l'utiliser ?
|
||||
Tu te souviens peut-être du `Consumer` que l'on a évoqué plus tôt, non ?
|
||||
|
||||
Et bien, il est temps de le mettre en pratique ! 😁
|
||||
|
||||
Pour commencer, nous allons avoir besoin du hook `useContext` de React.
|
||||
Ce hook va nous permettre de récupérer les données du contexte, et ce, directement dans nos composants.
|
||||
|
||||
<tabs.reactUseContext />
|
||||
|
||||
Pas mal, non ? 😉
|
||||
Fini l'arborescence de composants à rallonge, on peut maintenant récupérer les données du contexte directement dans nos composants !
|
||||
|
||||
## Les défauts des contextes
|
||||
|
||||
Seulement... Un grand pouvoir implique de grandes responsabilités. 🕷️
|
||||
|
||||
Bien que les contextes soient très pratiques, il faut prendre en compte quelques points :
|
||||
|
||||
- On ne peut pas utiliser les contextes pour tout et n'importe quoi. Ils sont plutôt adaptés pour diffuser des données qui sont utilisées par plusieurs composants.
|
||||
- Les contextes peuvent rendre le code plus difficile à comprendre.
|
||||
- L'utilisation de nombreux contextes va faire apparaître ce qu'on appelle le **context hell**.
|
||||
|
||||
### Le context hell
|
||||
|
||||
Dans cet article, nous avons vu comment créer un contexte et l'utiliser.
|
||||
Et par chance, nous n'avons pas encore rencontré le **context hell**.
|
||||
|
||||
Mais maintenant, que se passe-t-il si on a besoin de plusieurs contextes _(plusieurs dizaines par exemple !)_ dans notre application ?
|
||||
On va se retrouver avec une arborescence de composants qui va devenir de plus en plus difficile à comprendre et à maintenir.
|
||||
|
||||
Et c'est ça, le **context hell**.
|
||||
|
||||
<tabs.reactContextHell />
|
||||
|
||||
Maintenant, demande à un développeur d'inverser le provider `UserProvider` avec le provider `NoteProvider`.
|
||||
C'est jouable sans difficulté, mais si tu entends des cris de désespoir, c'est normal. 😅
|
||||
|
||||
Pour éviter de tomber dans le **context hell**, il est important de bien réfléchir à l'utilisation des contextes dans notre application avec ces quelques questions :
|
||||
|
||||
- Est-ce que l'utilisation d'un contexte est vraiment nécessaire pour ce cas d'usage ?
|
||||
- Est-ce que le contexte est utilisé par plusieurs composants ?
|
||||
- Est-ce que le contexte est utilisé par des composants éloignés dans l'arborescence ?
|
||||
|
||||
Mais alors, si tu as besoin d'autant de contextes dans ton application, comment faire ?
|
||||
Et bien, il existe des solutions pour éviter le **context hell** :
|
||||
|
||||
- Utiliser des bibliothèques tierces comme Redux _(solution lourde, mais très puissante)_
|
||||
- Créer un nouveau composant qui va regrouper tous les contextes _(solution plus légère, mais plus difficile à maintenir)_
|
||||
|
||||
N'étant pas un grand fan de Redux, je vais plutôt te présenter la deuxième solution.
|
||||
Mais si tu veux en savoir plus sur Redux, n'hésite pas à consulter la documentation officielle !
|
||||
|
||||
### Résoudre le context hell avec un composant dédié
|
||||
|
||||
Parlons de ce fameux composant qui va regrouper tous les contextes !
|
||||
On ne parle pas ici d'un simple composant Providers qui va imbriquer tous les Provider de nos contextes, mais d'une solution plus élégante.
|
||||
|
||||
Après tout, nous sommes des feignants développeurs, non ? 😏
|
||||
|
||||
Réfléchissons à ce que l'on veut faire :
|
||||
|
||||
- On veut pouvoir regrouper tous les contextes dans un seul composant.
|
||||
- On veut pouvoir ajouter ou supprimer des contextes facilement.
|
||||
- On veut pouvoir facilement les ordonner entre eux.
|
||||
- On veut éviter le **context hell**.
|
||||
|
||||
Et si on créait un composant Providers qui va nous permettre de faire tout ça ?
|
||||
|
||||
<tabs.reactContextProvidersComponent />
|
||||
|
||||
Ici on ne va pas remettre une cascade de Provider comme on a pu le voir plus tôt.
|
||||
On va chercher à créer une fonction qui va nous permettre de les imbriquer les uns dans les autres.
|
||||
|
||||
<tabs.reactNestFunction />
|
||||
|
||||
<Callout type="note" title="React.cloneElement">
|
||||
`React.cloneElement` est une fonction qui va permettre de cloner un élément React en lui passant de nouvelles propriétés.
|
||||
Cela va nous permettre de créer une nouvelle arborescence de composants sans modifier l'arborescence actuelle.
|
||||
|
||||
Le premier argument est l'élément à cloner _(le composant)_, et le deuxième argument est un objet contenant les nouvelles propriétés.
|
||||
Le troisième argument est le contenu de l'élément cloné _(les enfants)_.
|
||||
</Callout>
|
||||
|
||||
Et maintenant, on va pouvoir utiliser notre fonction `nest` pour imbriquer nos Provider en utilisant la méthode `reduceRight` :
|
||||
|
||||
<tabs.reactNestFunctionWithReduceRight />
|
||||
|
||||
<Callout type="note" title="reduceRight">
|
||||
`reduceRight` est une méthode qui va permettre de réduire un tableau _(ou un objet)_ en appliquant une fonction de rappel de droite à gauche.
|
||||
Cela va nous permettre de réduire un tableau de `Provider` en les imbriquant les uns dans les autres sans se soucier de l'ordre _(qui est défini par le tableau)_.
|
||||
|
||||
Dans l'idée, on commence par le **dernier** élément du tableau, et on l'imbrique avec l'élément **précédent** du tableau et ainsi de suite jusqu'au **premier** élément du tableau.
|
||||
Chaque itération va créer un nouvel élément imbriqué dans le précédent, en appelant la fonction `nest` qui est passée en argument.
|
||||
</Callout>
|
||||
|
||||
Et voilà ! Il ne nous reste plus qu'à utiliser notre composant `Providers` pour regrouper tous nos `Provider` :
|
||||
|
||||
<tabs.reactCleanerProviders />
|
||||
|
||||
Évidemment le fichier contiendra toujours beaucoup de lignes, mais au moins, on a évité le **context hell** !
|
||||
Il sera nettement plus facile de modifier l'ordre des Provider ou d'en ajouter de nouveaux.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Ça casse un peu la tête, mais les contextes sont un outil très puissant pour diffuser des données dans nos applications React.
|
||||
|
||||
C'est aussi une excellente solution pour éviter d'utiliser des bibliothèques tierces comme Redux _(qui est très bien, mais qui peut être un peu lourd pour des petites applications)_.
|
||||
On prendra d'ailleurs le temps de parler de Redux et de Zustand dans un prochain article 😉
|
||||
|
||||
Et si tu as besoin de plusieurs contextes dans ton application, n'oublie pas de réfléchir à l'utilisation de notre composant Providers pour éviter le **context hell**.
|
||||
@ -1,537 +0,0 @@
|
||||
---
|
||||
title: Le hook useContext de React
|
||||
description: Découvrez comment utiliser le hook useContext de React pour gérer les contextes dans vos applications.
|
||||
tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)]
|
||||
---
|
||||
|
||||
Les contextes sont un moyen de diffuser des données au travers des composants, sans avoir à les passer explicitement à chaque composant.
|
||||
|
||||
Pour faire simple, imaginons une arborescence de plusieurs composants imbriqués les uns dans les autres :
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
```jsx
|
||||
import { useState } from "react";
|
||||
|
||||
const App = () => {
|
||||
const [theme, setTheme] = useState("light");
|
||||
|
||||
return <A theme={theme} setTheme={theme} />;
|
||||
};
|
||||
|
||||
const A = ({ theme, setTheme }) => {
|
||||
return <B theme={theme} setTheme={setTheme} />;
|
||||
};
|
||||
|
||||
const B = ({ theme, setTheme }) => {
|
||||
return <C theme={theme} setTheme={setTheme} />;
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
```tsx
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
type Theme = "light" | "dark";
|
||||
|
||||
const App = () => {
|
||||
const [theme, setTheme] = useState<Theme>("light");
|
||||
|
||||
return <A theme={theme} setTheme={theme} />;
|
||||
};
|
||||
|
||||
const A = ({ theme, setTheme }: { theme: Theme; setTheme: Dispatch<SetStateAction<Theme>> }) => {
|
||||
return <B theme={theme} setTheme={setTheme} />;
|
||||
};
|
||||
|
||||
const B = ({ theme, setTheme }: { theme: Theme; setTheme: Dispatch<SetStateAction<Theme>> }) => {
|
||||
return <C theme={theme} setTheme={setTheme} />;
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Fastidieux, n'est-ce pas ? On transmet à chaque fois les mêmes données, et ce, à chaque niveau de l'arborescence.
|
||||
|
||||
C'est là que les contextes entrent en jeu !
|
||||
On va pouvoir alors déclarer notre contexte _(qui contiendra les données à diffuser)_ et le fournir à un niveau supérieur de l'arborescence.
|
||||
|
||||
## Déclaration d'un contexte
|
||||
|
||||
Avant de penser à notre contexte, on va réfléchir à ce que l'on veut diffuser et les valeurs par défaut.
|
||||
Si on reprend notre exemple avec le thème clair et sombre, on sait que l'on va vouloir diffuser la valeur du thème et une fonction pour le changer.
|
||||
|
||||
On va donc préparer le terrain en créant un fichier `ThemeContext.jsx` _(ou `ThemeContext.tsx` si tu utilises TypeScript)_ :
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
```jsx
|
||||
import { createContext } from "react";
|
||||
|
||||
// On crée notre contexte, avec une valeur par défaut : un thème clair
|
||||
const ThemeContext = createContext({
|
||||
theme: "light",
|
||||
setTheme: () => {},
|
||||
});
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
```tsx
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
import { createContext } from "react";
|
||||
|
||||
// On crée un type pour les valeurs de thème
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
// On crée un type pour notre contexte
|
||||
type ThemeContextType = {
|
||||
theme: Theme;
|
||||
setTheme: Dispatch<SetStateAction<Theme>>;
|
||||
};
|
||||
|
||||
// On crée notre contexte, avec une valeur par défaut : un thème clair
|
||||
const ThemeContext = createContext<ThemeContextType>({
|
||||
theme: "light",
|
||||
setTheme: () => {},
|
||||
});
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
## Fournir un contexte
|
||||
|
||||
Maintenant on peut le dire : notre contexte est prêt à être utilisé !
|
||||
Il ne reste plus qu'à le fournir à notre arborescence de composants en lui créant un `Provider`.
|
||||
|
||||
{% callout type="question" title="Un provider ?" %}
|
||||
|
||||
Un `Provider` est un composant qui va permettre de **diffuser** les données du contexte à ses enfants.
|
||||
Il est important de noter que le `Provider` doit **englober** les composants qui vont utiliser le contexte.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
Un contexte React est un objet qui contient deux propriétés : `Provider` et `Consumer`.
|
||||
|
||||
Le `Provider` est un composant qui va permettre de diffuser les données du contexte à ses enfants.
|
||||
Le `Consumer` est un composant qui va permettre de récupérer les données du contexte.
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
```jsx
|
||||
import { useState } from "react";
|
||||
|
||||
const App = () => {
|
||||
const [theme, setTheme] = useState("light");
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
<A />
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
```tsx
|
||||
import type { Theme } from "./ThemeContext";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
const App = () => {
|
||||
const [theme, setTheme] = useState<Theme>("light");
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
<A />
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Mais on peut aller encore plus loin, en créant un Provider dédié à notre contexte !
|
||||
Cela permettra de simplifier l'arborescence de composants et de rendre le code plus lisible :
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
```jsx
|
||||
import { createContext, useState } from "react";
|
||||
|
||||
const ThemeContext = createContext({
|
||||
theme: "light",
|
||||
setTheme: () => {},
|
||||
});
|
||||
|
||||
const ThemeProvider = ({ children }) => {
|
||||
const [theme, setTheme] = useState("light");
|
||||
|
||||
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
|
||||
};
|
||||
|
||||
export { ThemeContext, ThemeProvider };
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
```tsx
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { createContext, useState } from "react";
|
||||
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
type ThemeContextType = {
|
||||
theme: Theme;
|
||||
setTheme: Dispatch<SetStateAction<Theme>>;
|
||||
};
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType>({
|
||||
theme: "light",
|
||||
setTheme: () => {},
|
||||
});
|
||||
|
||||
type ThemeProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
||||
const [theme, setTheme] = useState<Theme>("light");
|
||||
|
||||
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
|
||||
};
|
||||
|
||||
export { ThemeContext, ThemeProvider };
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Et pour terminer, on va maintenant pouvoir directement imbriquer notre `ThemeProvider` dans notre `App` :
|
||||
|
||||
```jsx
|
||||
import { ThemeProvider } from "./ThemeContext";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<A />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Utilisation d'un contexte
|
||||
|
||||
C'est bien beau de créer un contexte, mais comment l'utiliser ?
|
||||
Tu te souviens peut-être du `Consumer` que l'on a évoqué plus tôt, non ?
|
||||
|
||||
Et bien, il est temps de le mettre en pratique ! 😁
|
||||
|
||||
Pour commencer, nous allons avoir besoin du hook `useContext` de React.
|
||||
Ce hook va nous permettre de récupérer les données du contexte, et ce, directement dans nos composants.
|
||||
|
||||
```jsx
|
||||
import { ThemeContext } from "./ThemeContext";
|
||||
import { useContext } from "react";
|
||||
|
||||
const C = () => {
|
||||
const { theme, setTheme } = useContext(ThemeContext);
|
||||
|
||||
return <>{/** JSX */}</>;
|
||||
};
|
||||
```
|
||||
|
||||
Pas mal, non ? 😉
|
||||
Fini l'arborescence de composants à rallonge, on peut maintenant récupérer les données du contexte directement dans nos composants !
|
||||
|
||||
## Les défauts des contextes
|
||||
|
||||
Seulement... Un grand pouvoir implique de grandes responsabilités. 🕷️
|
||||
|
||||
Bien que les contextes soient très pratiques, il faut prendre en compte quelques points :
|
||||
|
||||
- On ne peut pas utiliser les contextes pour tout et n'importe quoi. Ils sont plutôt adaptés pour diffuser des données qui sont utilisées par plusieurs composants.
|
||||
- Les contextes peuvent rendre le code plus difficile à comprendre.
|
||||
- L'utilisation de nombreux contextes va faire apparaître ce qu'on appelle le **context hell**.
|
||||
|
||||
### Le context hell
|
||||
|
||||
Dans cet article, nous avons vu comment créer un contexte et l'utiliser.
|
||||
Et par chance, nous n'avons pas encore rencontré le **context hell**.
|
||||
|
||||
Mais maintenant, que se passe-t-il si on a besoin de plusieurs contextes _(plusieurs dizaines par exemple !)_ dans notre application ?
|
||||
On va se retrouver avec une arborescence de composants qui va devenir de plus en plus difficile à comprendre et à maintenir.
|
||||
|
||||
Et c'est ça, le **context hell**.
|
||||
|
||||
```jsx
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<UserProvider>
|
||||
<ThemeProvider>
|
||||
<LanguageProvider>
|
||||
<PostProvider>
|
||||
<SettingsProvider>
|
||||
<SocketProvider>
|
||||
<FriendProvider>
|
||||
<NotificationProvider>
|
||||
<ChatProvider>
|
||||
<MusicProvider>
|
||||
<VideoProvider>
|
||||
<GameProvider>
|
||||
<WeatherProvider>
|
||||
<NewsProvider>
|
||||
<CalendarProvider>
|
||||
<TaskProvider>
|
||||
<NoteProvider>
|
||||
<App />
|
||||
</NoteProvider>
|
||||
</TaskProvider>
|
||||
</CalendarProvider>
|
||||
</NewsProvider>
|
||||
</WeatherProvider>
|
||||
</GameProvider>
|
||||
</VideoProvider>
|
||||
</MusicProvider>
|
||||
</ChatProvider>
|
||||
</NotificationProvider>
|
||||
</FriendProvider>
|
||||
</SocketProvider>
|
||||
</SettingsProvider>
|
||||
</PostProvider>
|
||||
</LanguageProvider>
|
||||
</ThemeProvider>
|
||||
</UserProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
```
|
||||
|
||||
Maintenant, demande à un développeur d'inverser le provider `UserProvider` avec le provider `NoteProvider`.
|
||||
C'est jouable sans difficulté, mais si tu entends des cris de désespoir, c'est normal. 😅
|
||||
|
||||
Pour éviter de tomber dans le **context hell**, il est important de bien réfléchir à l'utilisation des contextes dans notre application avec ces quelques questions :
|
||||
|
||||
- Est-ce que l'utilisation d'un contexte est vraiment nécessaire pour ce cas d'usage ?
|
||||
- Est-ce que le contexte est utilisé par plusieurs composants ?
|
||||
- Est-ce que le contexte est utilisé par des composants éloignés dans l'arborescence ?
|
||||
|
||||
Mais alors, si tu as besoin d'autant de contextes dans ton application, comment faire ?
|
||||
Et bien, il existe des solutions pour éviter le **context hell** :
|
||||
|
||||
- Utiliser des bibliothèques tierces comme Redux _(solution lourde, mais très puissante)_
|
||||
- Créer un nouveau composant qui va regrouper tous les contextes _(solution plus légère, mais plus difficile à maintenir)_
|
||||
|
||||
N'étant pas un grand fan de Redux, je vais plutôt te présenter la deuxième solution.
|
||||
Mais si tu veux en savoir plus sur Redux, n'hésite pas à consulter la documentation officielle !
|
||||
|
||||
### Résoudre le context hell avec un composant dédié
|
||||
|
||||
Parlons de ce fameux composant qui va regrouper tous les contextes !
|
||||
On ne parle pas ici d'un simple composant Providers qui va imbriquer tous les Provider de nos contextes, mais d'une solution plus élégante.
|
||||
|
||||
Après tout, nous sommes des feignants développeurs, non ? 😏
|
||||
|
||||
Réfléchissons à ce que l'on veut faire :
|
||||
|
||||
- On veut pouvoir regrouper tous les contextes dans un seul composant.
|
||||
- On veut pouvoir ajouter ou supprimer des contextes facilement.
|
||||
- On veut pouvoir facilement les ordonner entre eux.
|
||||
- On veut éviter le **context hell**.
|
||||
|
||||
Et si on créait un composant Providers qui va nous permettre de faire tout ça ?
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
```jsx
|
||||
const Providers = ({ providers, children }) => {
|
||||
return (
|
||||
<>
|
||||
{/** Ouverture des providers */}
|
||||
{children}
|
||||
{/** Fermeture des providers */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
```tsx
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type ProvidersProps = {
|
||||
providers: ReactNode[];
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const Providers = ({ providers, children }: ProvidersProps) => {
|
||||
return (
|
||||
<>
|
||||
{/** Ouverture des providers */}
|
||||
{children}
|
||||
{/** Fermeture des providers */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
Ici on ne va pas remettre une cascade de Provider comme on a pu le voir plus tôt.
|
||||
On va chercher à créer une fonction qui va nous permettre de les imbriquer les uns dans les autres.
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
```jsx
|
||||
const nest = (children, component) => {
|
||||
return React.cloneElement(component, {}, children);
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
```tsx
|
||||
const nest = (children: ReactNode, component: ReactNode) => {
|
||||
return React.cloneElement(component, {}, children);
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% callout type="note" title="React.cloneElement" %}
|
||||
|
||||
`React.cloneElement` est une fonction qui va permettre de cloner un élément React en lui passant de nouvelles propriétés.
|
||||
Cela va nous permettre de créer une nouvelle arborescence de composants sans modifier l'arborescence actuelle.
|
||||
|
||||
Le premier argument est l'élément à cloner _(le composant)_, et le deuxième argument est un objet contenant les nouvelles propriétés.
|
||||
Le troisième argument est le contenu de l'élément cloné _(les enfants)_.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
Et maintenant, on va pouvoir utiliser notre fonction `nest` pour imbriquer nos Provider en utilisant la méthode `reduceRight` :
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
```jsx
|
||||
const nest = (children, component) => {
|
||||
return React.cloneElement(component, {}, children);
|
||||
};
|
||||
|
||||
const Providers = ({ providers, children }) => {
|
||||
return providers.reduceRight(nest, children);
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
```tsx
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type ProvidersProps = {
|
||||
providers: ReactNode[];
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const nest = (children: ReactNode, component: ReactNode) => {
|
||||
return React.cloneElement(component, {}, children);
|
||||
};
|
||||
|
||||
const Providers = ({ providers, children }: ProvidersProps) => {
|
||||
return providers.reduceRight(nest, children);
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% callout type="note" title="reduceRight" %}
|
||||
|
||||
reduceRight est une méthode qui va permettre de réduire un tableau _(ou un objet)_ en appliquant une fonction de rappel de droite à gauche.
|
||||
Cela va nous permettre de réduire un tableau de `Provider` en les imbriquant les uns dans les autres sans se soucier de l'ordre _(qui est défini par le tableau)_.
|
||||
|
||||
Dans l'idée, on commence par le **dernier** élément du tableau, et on l'imbrique avec l'élément **précédent** du tableau et ainsi de suite jusqu'au **premier** élément du tableau.
|
||||
Chaque itération va créer un nouvel élément imbriqué dans le précédent, en appelant la fonction `nest` qui est passée en argument.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
Et voilà ! Il ne nous reste plus qu'à utiliser notre composant `Providers` pour regrouper tous nos `Provider` :
|
||||
|
||||
```jsx
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<Providers
|
||||
providers={[
|
||||
<UserProvider />,
|
||||
<ThemeProvider />,
|
||||
<LanguageProvider />,
|
||||
<PostProvider />,
|
||||
<SettingsProvider />,
|
||||
<SocketProvider />,
|
||||
<FriendProvider />,
|
||||
// ...
|
||||
]}
|
||||
>
|
||||
<App />
|
||||
</Providers>
|
||||
</StrictMode>,
|
||||
);
|
||||
```
|
||||
|
||||
Évidemment le fichier contiendra toujours beaucoup de lignes, mais au moins, on a évité le **context hell** !
|
||||
Il sera nettement plus facile de modifier l'ordre des Provider ou d'en ajouter de nouveaux.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Ça casse un peu la tête, mais les contextes sont un outil très puissant pour diffuser des données dans nos applications React.
|
||||
|
||||
C'est aussi une excellente solution pour éviter d'utiliser des bibliothèques tierces comme Redux _(qui est très bien, mais qui peut être un peu lourd pour des petites applications)_.
|
||||
On prendra d'ailleurs le temps de parler de Redux et de Zustand dans un prochain article 😉
|
||||
|
||||
Et si tu as besoin de plusieurs contextes dans ton application, n'oublie pas de réfléchir à l'utilisation de notre composant Providers pour éviter le **context hell**.
|
||||
397
app/pages/docs/react/use-context/tabs.tsx
Normal file
397
app/pages/docs/react/use-context/tabs.tsx
Normal file
@ -0,0 +1,397 @@
|
||||
import { Snippet } from "@/components/Snippet";
|
||||
import { name } from "eslint-plugin-prettier/recommended";
|
||||
|
||||
const reactNestedPropsSnippets = [
|
||||
{
|
||||
name: "JSX",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `import { useState } from "react";
|
||||
|
||||
const App = () => {
|
||||
const [theme, setTheme] = useState("light");
|
||||
|
||||
return <A theme={theme} setTheme={theme} />;
|
||||
};
|
||||
|
||||
const A = ({ theme, setTheme }) => {
|
||||
return <B theme={theme} setTheme={setTheme} />;
|
||||
};
|
||||
|
||||
const B = ({ theme, setTheme }) => {
|
||||
return <C theme={theme} setTheme={setTheme} />;
|
||||
};`,
|
||||
},
|
||||
{
|
||||
name: "TSX",
|
||||
codeLanguage: "tsx",
|
||||
withLineNumbers: true,
|
||||
code: `import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
type Theme = "light" | "dark";
|
||||
|
||||
const App = () => {
|
||||
const [theme, setTheme] = useState<Theme>("light");
|
||||
|
||||
return <A theme={theme} setTheme={theme} />;
|
||||
};
|
||||
|
||||
const A = ({ theme, setTheme }: { theme: Theme; setTheme: Dispatch<SetStateAction<Theme>> }) => {
|
||||
return <B theme={theme} setTheme={setTheme} />;
|
||||
};
|
||||
|
||||
const B = ({ theme, setTheme }: { theme: Theme; setTheme: Dispatch<SetStateAction<Theme>> }) => {
|
||||
return <C theme={theme} setTheme={setTheme} />;
|
||||
};`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactCreateContextSnippets = [
|
||||
{
|
||||
name: "JSX",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `import { createContext } from "react";
|
||||
|
||||
// On crée notre contexte, avec une valeur par défaut : un thème clair
|
||||
const ThemeContext = createContext({
|
||||
theme: "light",
|
||||
setTheme: () => {},
|
||||
});`,
|
||||
},
|
||||
{
|
||||
name: "TSX",
|
||||
codeLanguage: "tsx",
|
||||
withLineNumbers: true,
|
||||
code: `import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
import { createContext } from "react";
|
||||
|
||||
// On crée un type pour les valeurs de thème
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
// On crée un type pour notre contexte
|
||||
type ThemeContextType = {
|
||||
theme: Theme;
|
||||
setTheme: Dispatch<SetStateAction<Theme>>;
|
||||
};
|
||||
|
||||
// On crée notre contexte, avec une valeur par défaut : un thème clair
|
||||
const ThemeContext = createContext<ThemeContextType>({
|
||||
theme: "light",
|
||||
setTheme: () => {},
|
||||
});`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactContextProviderSnippets = [
|
||||
{
|
||||
name: "JSX",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `import { useState } from "react";
|
||||
|
||||
const App = () => {
|
||||
const [theme, setTheme] = useState("light");
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
<A />
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};`,
|
||||
},
|
||||
{
|
||||
name: "TSX",
|
||||
codeLanguage: "tsx",
|
||||
withLineNumbers: true,
|
||||
code: `import type { Theme } from "./ThemeContext";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
const App = () => {
|
||||
const [theme, setTheme] = useState<Theme>("light");
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
<A />
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactContextProviderWithValuesSnippets = [
|
||||
{
|
||||
name: "JSX",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `import { createContext, useState } from "react";
|
||||
|
||||
const ThemeContext = createContext({
|
||||
theme: "light",
|
||||
setTheme: () => {},
|
||||
});
|
||||
|
||||
const ThemeProvider = ({ children }) => {
|
||||
const [theme, setTheme] = useState("light");
|
||||
|
||||
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
|
||||
};
|
||||
|
||||
export { ThemeContext, ThemeProvider };`,
|
||||
},
|
||||
{
|
||||
name: "TSX",
|
||||
codeLanguage: "tsx",
|
||||
withLineNumbers: true,
|
||||
code: `import type { ReactNode } from "react";
|
||||
|
||||
import { createContext, useState } from "react";
|
||||
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
type ThemeContextType = {
|
||||
theme: Theme;
|
||||
setTheme: Dispatch<SetStateAction<Theme>>;
|
||||
};
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType>({
|
||||
theme: "light",
|
||||
setTheme: () => {},
|
||||
});
|
||||
|
||||
type ThemeProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
||||
const [theme, setTheme] = useState<Theme>("light");
|
||||
|
||||
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
|
||||
};
|
||||
|
||||
export { ThemeContext, ThemeProvider };`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactContextProviderInAppSnippets = [
|
||||
{
|
||||
name: "App.jsx",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `import { ThemeProvider } from "./ThemeContext";
|
||||
import A from "./A";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<A />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactUseContextSnippets = [
|
||||
{
|
||||
name: "Utilisation du hook `useContext`",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `import { ThemeContext } from "./ThemeContext";
|
||||
import { useContext } from "react";
|
||||
|
||||
const C = () => {
|
||||
const { theme, setTheme } = useContext(ThemeContext);
|
||||
|
||||
return <>{/** JSX */}</>;
|
||||
};`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactContextHellSnippets = [
|
||||
{
|
||||
name: "Exemple de contexte imbriqué",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `root.render(
|
||||
<StrictMode>
|
||||
<UserProvider>
|
||||
<ThemeProvider>
|
||||
<LanguageProvider>
|
||||
<PostProvider>
|
||||
<SettingsProvider>
|
||||
<SocketProvider>
|
||||
<FriendProvider>
|
||||
<NotificationProvider>
|
||||
<ChatProvider>
|
||||
<MusicProvider>
|
||||
<VideoProvider>
|
||||
<GameProvider>
|
||||
<WeatherProvider>
|
||||
<NewsProvider>
|
||||
<CalendarProvider>
|
||||
<TaskProvider>
|
||||
<NoteProvider>
|
||||
<App />
|
||||
</NoteProvider>
|
||||
</TaskProvider>
|
||||
</CalendarProvider>
|
||||
</NewsProvider>
|
||||
</WeatherProvider>
|
||||
</GameProvider>
|
||||
</VideoProvider>
|
||||
</MusicProvider>
|
||||
</ChatProvider>
|
||||
</NotificationProvider>
|
||||
</FriendProvider>
|
||||
</SocketProvider>
|
||||
</SettingsProvider>
|
||||
</PostProvider>
|
||||
</LanguageProvider>
|
||||
</ThemeProvider>
|
||||
</UserProvider>
|
||||
</StrictMode>,
|
||||
);`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactContextProvidersComponentSnippets = [
|
||||
{
|
||||
name: "JSX",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `const Providers = ({ providers, children }) => {
|
||||
return (
|
||||
<>
|
||||
{/** Ouverture des providers */}
|
||||
{children}
|
||||
{/** Fermeture des providers */}
|
||||
</>
|
||||
);
|
||||
};`,
|
||||
},
|
||||
{
|
||||
name: "TSX",
|
||||
codeLanguage: "tsx",
|
||||
withLineNumbers: true,
|
||||
code: `import type { ReactNode } from "react";
|
||||
|
||||
type ProvidersProps = {
|
||||
providers: ReactNode[];
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const Providers = ({ providers, children }: ProvidersProps) => {
|
||||
return (
|
||||
<>
|
||||
{/** Ouverture des providers */}
|
||||
{children}
|
||||
{/** Fermeture des providers */}
|
||||
</>
|
||||
);
|
||||
};`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactNestFunctionSnippets = [
|
||||
{
|
||||
name: "JSX",
|
||||
codeLanguage: "jsx",
|
||||
code: `const nest = (children, component) => {
|
||||
return React.cloneElement(component, {}, children);
|
||||
};`,
|
||||
},
|
||||
{
|
||||
name: "TSX",
|
||||
codeLanguage: "tsx",
|
||||
code: `const nest = (children: ReactNode, component: ReactNode) => {
|
||||
return React.cloneElement(component, {}, children);
|
||||
};`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactNestFunctionWithReduceRightSnippets = [
|
||||
{
|
||||
name: "JSX",
|
||||
codeLanguage: "jsx",
|
||||
code: `const nest = (children, component) => {
|
||||
return React.cloneElement(component, {}, children);
|
||||
};
|
||||
|
||||
const Providers = ({ providers, children }) => {
|
||||
return providers.reduceRight(nest, children);
|
||||
};`,
|
||||
},
|
||||
{
|
||||
name: "TSX",
|
||||
codeLanguage: "tsx",
|
||||
code: `import type { ReactNode } from "react";
|
||||
|
||||
type ProvidersProps = {
|
||||
providers: ReactNode[];
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const nest = (children: ReactNode, component: ReactNode) => {
|
||||
return React.cloneElement(component, {}, children);
|
||||
};
|
||||
|
||||
const Providers = ({ providers, children }: ProvidersProps) => {
|
||||
return providers.reduceRight(nest, children);
|
||||
};`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactCleanerProvidersSnippets = [
|
||||
{
|
||||
name: "Import lisible et maintenable pour les providers",
|
||||
codeLanguage: "jsx",
|
||||
code: `root.render(
|
||||
<StrictMode>
|
||||
<Providers
|
||||
providers={[
|
||||
<UserProvider />,
|
||||
<ThemeProvider />,
|
||||
<LanguageProvider />,
|
||||
<PostProvider />,
|
||||
<SettingsProvider />,
|
||||
<SocketProvider />,
|
||||
<FriendProvider />,
|
||||
// ...
|
||||
]}
|
||||
>
|
||||
<App />
|
||||
</Providers>
|
||||
</StrictMode>,
|
||||
);`,
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
reactNestedProps: () => <Snippet snippets={reactNestedPropsSnippets} />,
|
||||
reactCreateContext: () => <Snippet snippets={reactCreateContextSnippets} />,
|
||||
reactContextProvider: () => (
|
||||
<Snippet snippets={reactContextProviderSnippets} />
|
||||
),
|
||||
reactContextProviderWithValues: () => (
|
||||
<Snippet snippets={reactContextProviderWithValuesSnippets} />
|
||||
),
|
||||
reactContextProviderInApp: () => (
|
||||
<Snippet snippets={reactContextProviderInAppSnippets} />
|
||||
),
|
||||
reactUseContext: () => <Snippet snippets={reactUseContextSnippets} />,
|
||||
reactContextHell: () => <Snippet snippets={reactContextHellSnippets} />,
|
||||
reactContextProvidersComponent: () => (
|
||||
<Snippet snippets={reactContextProvidersComponentSnippets} />
|
||||
),
|
||||
reactNestFunction: () => <Snippet snippets={reactNestFunctionSnippets} />,
|
||||
reactNestFunctionWithReduceRight: () => (
|
||||
<Snippet snippets={reactNestFunctionWithReduceRightSnippets} />
|
||||
),
|
||||
reactCleanerProviders: () => (
|
||||
<Snippet snippets={reactCleanerProvidersSnippets} />
|
||||
),
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user