Partager des valeurs entre les composants en utilisant le contexte
Le problème de Prop Drilling
Imaginez que l’on souhaite afficher une notification qui renferme un message et un bouton d’action. Si on souhaite profiter au maximum des composants afin de pouvoir les réutiliser ailleurs, nous allons séparer le bouton d’action dans un composant à part. Donc, on aura trois fichiers de base pour notre projet:
- App.js : qui implémente le composant principale <App /> de notre application.
- Notification.js : qui décrit le composant <Notification /> et qui sera importé dans le composant <App />.
- Bouton.js : qui fait office du composant <Bouton /> et qui sera à son tour importé dans le composant <Notification />.
On suppose que les fichiers Notification.js et Bouton.js sont placés dans le dossier components utilisé précédemment.
Imaginons que nous voulons rendre dynamique l'étiquette du bouton, c'est à dire que l'on spécifiera sa valeur dans le composant
<App /> et que l'on réutilisera ensuite dans le composant
<Bouton />. Nous allons donc faire appel aux
props comme ceci:
Code du composant
<App />:
import Notification from './components/Notification'
function App() {
return (
<>
<Notification label="Continuer"/>
</>
);
}
export default App;
Code du composant
<Notification />:
import Bouton from "./Bouton"
function Notification(props){
return (
<>
<h2>Notification</h2>
<div>Cliquez sur le bouton pour continuer</div>
<hr />
<Bouton label={props.label} />
</>
)
}
export default Notification
Code du composant
<Bouton />:
function Bouton(props){
return(
<>
<button>{props.label}</button>
</>
)
}
export default Bouton
Vous avez constaté que l'étiquette du bouton (message "Continuer") a été passé comme un prop nommé
label lors de l'invocation du composant
<Notification /> au sein du composant
<App />. Ce même prop a été retransmis comme prop lors de l'appel du composant
<Bouton /> au sein du composant
<Notification />.
Vous avez compris que le composant
<Notification /> n'a pas vraiment besoin de cette étiquette, mais on doit le lui transmettre quand même car il figure au milieu de la chaîne entre
<App /> et
<Bouton />. Donc, si ça se trouve que notre chaîne de composants est plus longue que ça (un composant qui appelle un autre), alors on se retrouvera avec nos props qui se baladent un peu partout. Ce phénomène est ce que l'on appelle
Prop Drilling.
Partager les valeurs entre un Provider et un Consumer en utilisant le mécanisme du contexte
Pour contrer le problème de Prop Drilling, un mécanisme nommé
Context a été intégré à React. Son principe est simple, on mettra en place deux entités nommées
Provider (fournisseur) et
Consumer (Client ou consommateur). Il s'agit en fait de deux composants qui sont créés automatiquement avec le mécanisme Context. D'après leurs noms, le
Provider va créer et initialiser les valeurs à partager entre les composants, et le
Consumer va avoir accès à ces valeurs là depuis n'importe quel composant de la chaîne. Autrement dit, on n'a plus besoin de trainer nos valeurs (props) dans tous les composants comme on l'a fait avant. Le composant qui en a besoin peut y accéder directement.
Le terme Consumer fait référence à une ancienne méthode utilisée en React. Les versions actuelles utilisent des méthodes bien plus simples et efficaces comme on le verra juste après.
La fonction createContext et le hook useContext
La fonction
createContext, comme son nom l'indique, permet de créer un contexte, c'est à dire, les données globales qui seront partagées à travers l'arborescence des composants imbriqués et qui seront accessibles à n'importe quel niveau de cette arborescence là. Cette fonction est généralement utilisée dans le composant de plus haut niveau (
<App /> dans notre cas).
La fonction
createContext crée un provider qui servira à envelopper les composants de l'applications qui sont susceptibles d'utiliser les données partagées. Il s'agit là d'une sorte de wrapper qui spécifie la valeur à communiquer.
Le code du composant
<App /> ressemblera à ceci:
import { createContext } from 'react';
import Notification from './components/Notification'
export const label = createContext(null)
function App() {
return (
<>
<label.Provider value="Continuer">
<Notification />
</label.Provider>
</>
);
}
export default App;
Premièrement, on importe le module
createContext de la bibliothèque
react. Ensuite, on crée le contexte à l'aide la fonction
createContext(). On peut spécifier une valeur initiale du contexte. Dans ce cas, j'ai simplement mis
null. Le contexte créé est placé dans un objet que j'ai appelé
label.
Une chose importante à noter, c'est qu'il faut exporter l'objet contexte qui vient d'être créé. De cette façon, on pourra l'importer depuis n'importe quel autre composant.
Il ne faut pas exporter le contexte par défaut car une seule exportation par défaut est permise par composant.
Par la suite, on spécifie le wrapper
Provider qu'on préfixe par le nom du contexte (
<label.Provider>). Il doit envelopper tous les composants enfants dont les descendants sont susceptibles d'utiliser le contexte partagé. Comme valeur, on met ce qu'on veut. Dans mon cas, j'ai juste spécifié la chaîne de caractères "Continuer" qui fera office de l'étiquette du bouton.
Maintenant, on se place dans le composant qui aura besoin d'utiliser la donnée partagée par le contexte. Dans notre cas, il s'agit du composant
<Bouton /> dont le code ressemblera à ceci:
import { useContext } from "react"
import { label } from '../App'
function Bouton(){
const labelValue = useContext(label)
return(
<>
<button>{labelValue}</button>
</>
)
}
export default Bouton
On importe l'objet
label qui représente le contexte partagé. N'oubliez pas de le placer entre accolades car son exportation n'était pas pas défaut.
On invoque le hook
useContext qui accepte comme argument l'objet
label. Assurez-vous d'avoir importé le module
useContext de la bibliothèque
react avant.
La variable
labelValue générée à laide du hook
useContext contient la donnée partagée par le
Provider, c'est à dire la chaîne de caractères "Continuer" qu'on affiche tout simplement en guise d'étiquette du bouton en utilisant l'interpolation JSX.