Passer des données à un composant React
Un composant React est une entité de code qui, à partir de données, va générer un rendu graphique en HTML. En d’autres termes, et pour faire simple, les composants React sont des fonctions qui prennent en entrée des valeurs et retournent en sortie du code HTML.
Dans cet article, nous allons nous arrêter sur les différentes données utilisées par les composants React pour générer du code HTML.
Les props (ou propriétés)
Les composants s’utilisent comme des balises HTML (mais n’en sont pas). Les propriétés sont donc la version React des attributs HTML.
<MyComponent
name="Component One"
placeholder="Loading component One..."
/>
MyComponent
a les propriétés suivantes :
name
placeholder
Dans l’implémentation du composant, nous pouvons les récupérer de la façon suivante :
function MyComponent(props) {
const { name, placeholder } = props
// ...
}
Les props (ou propriétés) du composant permettent de transférer des données externes au composant depuis son contexte parent. C’est-à-dire depuis l’endroit où il est utilisé.
React va surveiller ces données et rendre à nouveau les composants si celles-ci sont mises à jour. À chaque fois que les valeurs de props
changent, React ré-exécute la fonction MyComponent
avec les nouvelles valeurs des données et récupère le HTML généré par celle-ci pour pouvoir mettre à jour le DOM en conséquence.
Remarquons que les propriétés étant des données externes au composant, il est logique de les récupérer via les arguments de la fonction.
Les states (ou variables d’états)
Mettre à jour les composants à partir de données externes correspond à énormément de cas d’utilisation. Cependant, il arrive souvent qu’un composant ait besoin de garder en mémoire une donnée qui lui serait utile au moment du prochain rendu.
Par exemple un bouton qui affiche le nombre de fois où il a été cliqué :
function CountButton() {
let count = 0
return (
<button onClick={() => count += 1}>
Click me! (clicked {count} times)
</button>
)
}
La variable count
n’est pas une propriété, car elle est propre au composant CountButton
. Elle n’aurait pas de sens à l’extérieur de ce composant.
Mais, comme les plus perspicaces d’entre vous l’auront remarqué, nous ne pouvons pas non plus déclarer de simple variable dans notre composant. Le code ci-dessus ne fonctionne donc pas.
Les raisons sont les suivantes :
- Nous avons dit que React utilisait la fonction
CountButton
pour rendre le composant du même nom. À chaque fois que les propriétés changent, React rappelle la fonction qui va être exécutée comme une fonction JavaScript normale.count
étant limité au scope de la fonction, au début de la fonction, il n’existe pas et après l’exécution de la fonction, il n’existe plus. Le bouton afficherait donc toujours0
clic. - Nous souhaitons que le bouton affiche la valeur de
count
lorsqu’elle change. React doit donc rendre à nouveau le composant pour afficher dans le DOM son nouvel état. Comment React peut-il savoir que la variable a changé et qu’il faut rendre à nouveau le composant ? Il ne le peut pas avec les variables JavaScript.
Le comportement que nous décrivons ressemble beaucoup aux variables d’instance des objets en POO (Programmation Orienté Objet). Malheureusement, dans cet article, nous n’utilisons pas d’objet, mais des fonctions pour créer nos composants.
Il y a un outil que React fournit et qui permet tout ce dont nous avons besoin : les hooks. Un hook en particulier nous intéresse ici, c’est useState
. Nous n’expliquerons pas les hooks dans les détails. Nous verrons juste comment créer une variable d’état et comment useState
résout les problèmes listés précédemment.
function CountButton() {
let [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Click me! (clicked {count} times)
</button>
)
}
Pas grand-chose ne change avec ce hook. Nous déléguons juste la création de count
à la fonction useState
en lui passant la première valeur.
useState
va sauvegarder la valeur de count
ailleurs que dans la fonction du composant pour pouvoir le retenir malgré les cycles de rendu. Il retourne aussi une fonction de modification du state. La fonction de modification notifie React à chaque fois que nous l’appelons, il sait ainsi qu’il doit rendre à nouveau le composant.
Faisons le processus manuellement pour mieux comprendre. La première fois que React doit rendre le composant, il exécute useState
. useState
cherche un state existant pour count
. Il n’en trouve pas et en crée un avec comme valeur celle qui est passée en argument de useState
(0
pour nous). count
vaut donc zéro. React continue à rendre le composant et affiche zéro dans le bouton.
Lorsque l’utilisateur clique sur le bouton setCount
est appelé avec la valeur 1
. setCount
modifie la valeur du state et prévient React que quelque chose a changé. React rend le composant, il exécute useState
. useState
cherche un state existant pour count
. Il en trouve un et retourne sa valeur actuelle (1
pour nous). count
vaut donc un. React continue à rendre le composant et affiche 1 dans le bouton.
Ainsi de suite.
Les contextes
Nous avons parlé de contextes dans la partie consacrée aux propriétés des composants. Pour rappel, une propriété sert à rendre accessible une valeur disponible immédiatement dans le contexte actuel à un composant enfant. La valeur devient donc disponible dans le contexte du composant enfant qui peut donc en faire ce qu’il veut et même le passer en propriétés de ses composants enfants. Etc., etc.
Deux problèmes se posent alors :
- Les valeurs qui sont nécessaires au bon fonctionnement d’énormément de composants.
- Les valeurs qui sont disponibles à un certain niveau de l’arbre des composants, mais utilisé uniquement plusieurs niveaux plus bas dans l’arbre des composants. Nous sommes alors obligés de les passer en propriété de tous les composants parents avant que la valeur ne soit disponible pour le seul composant qui en a besoin.
Regardez, par exemple, cet arbre de composants :
function ComponentOne(props) {
return (
<>
<ComponentTwo count={props.count} />
<ComponentThree count={props.count} />
</>
)
}
function ComponentTwo(props) {
return <ComponentFour count={props.count} />
}
function ComponentThree(props) {
return <ComponentFive count={props.count} />
}
function ComponentFour(props) {
doSomething(props.count)
return <ComponentSix count={props.count} />
}
function ComponentFive(props) {
doSomething(props.count)
return <div />
}
function ComponentSix(props) {
doSomething(props.count)
return <div />
}
C’est un arbre simple, mais déjà dans cet exemple count
est utilisé dans trois composants différents à différents niveaux de l’arbre, alors que dans au moins trois autres composants de niveau intermédiaire, il n’est pas du tout utilisé. Ces composants ne font que transmettre la donnée.
Dans une plus grosse application, ce comportement peut vite rendre le code plus fastidieux à écrire et à maintenir. Pour résoudre ces problèmes, React propose les contextes.
Ils sont exactement ce que leur nom indique d’eux : des contextes un peu plus globaux. Leur spécificité est que nous pouvons accéder aux valeurs contenues dans ces contextes depuis n’importe où dans l’arbre des composants. Les contextes peuvent aussi permettre la modification des valeurs qu'ils contiennent.
Si nous reprenons l’exemple ci-dessous en y ajoutant les contextes, cela donnerait :
const CountContext = createContext(0)
function ComponentOne(props) {
return (
<CountContext.Provider value={props.count}>
<ComponentTwo />
<ComponentThree />
</CountContext.Provider>
)
}
function ComponentTwo() {
return <ComponentFour />
}
function ComponentThree() {
return <ComponentFive />
}
function ComponentFour() {
const count = useContext(CountContext)
doSomething(count)
return <ComponentSix />
}
function ComponentFive() {
const count = useContext(CountContext)
doSomething(count)
return <div />
}
function ComponentSix() {
const count = useContext(CountContext)
doSomething(count)
return <div />
}
Nous avons créé un Context Provider au niveau où la valeur est disponible. À partir de là tous les composants enfants ont accès au contexte, et donc aux valeurs qu’il contient. Seuls les composants qui en ont besoin contiennent du code concernant ces valeurs.
La valeur que nous passons à la fonction createContext
est une valeur par défaut. Ce qui permet d’utiliser les composants ayant besoin de la valeur du contexte à des endroits du code où le contexte n’est pas disponible. Par exemple, si nous n’avons pas utilisé le provider.
J’ai précisé plus haut que les Context React étaient des contextes un peu plus globaux. Ce que je voulais dire par là est que nous ne sommes pas obligés d’en faire un mégacontexte global à la redux pour contenir toutes les valeurs de notre application (même si cela est possible).
Les contextes de React ont été conçus pour être utilisés localement là où c’est nécessaire. Ils résident à un niveau précis de l’arbre des composants, rien ne nous force à créer un contexte au plus haut niveau de l’arbre. En cela, ils ne sont pas globaux.
Nous pouvons aussi utiliser plusieurs contextes différents qui vont contenir des données concernant une fonctionnalité spécifique de notre application, rien ne nous oblige à n’utiliser qu’un seul contexte fourre-tout contenant toutes les données de l’application. Par exemple, nous pouvons avoir un contexte pour la fonctionnalité de traduction, un autre pour l’affichage de message toast, un autre pour l’affichage des pop-ups, un autre pour le theming de nos composants, etc.
Chaque contexte pourra donc prendre sa place à un niveau de l’arbre des composants où ils seront utiles. Nous pouvons même utiliser plusieurs instances du même contexte afin de ne pas centraliser les données lorsque c’est nécessaire. Par exemple pour preview l’effet d’un changement de langue sur l’interface utilisateur avant d’effectuer le vrai changement.
Voilà nous avons vu 3 façons simples de transmettre des informations à nos composants React :
- les props
- les states
- les contexts
Il est bien sûr possible de mixer ses différentes méthodes ensemble pour créer des composants plus intelligents qui permettent, par exemple, aux composants parents de modifier la valeur initiale d’une variable d’état ou alors qui savent modifier directement la valeur contenue dans un contexte afin de mettre à jour d’autres composants voisins simplement, etc.