Aller au contenu

d

Plongez dans le débogage d'applications React

Une note sur la version React

La version 18 de React est sortie fin mars 2022. Le code du matériel devrait fonctionner tel qu'il est avec la nouvelle version de React. Cependant, certaines bibliothèques peuvent ne pas encore être compatibles avec React 18. Au moment de la rédaction (4 avril), le client Apollo utilisé dans la partie 8 ne fonctionne pas encore avec la version la plus récente de React.

Si vous vous retrouvez dans une situation où votre application tombe en panne en raison de problèmes de compatibilité de bibliothèque, rétrogradez vers l'ancien React en modifiant le fichier package.json comme suit :

{
  "dependencies": {
    "react": "^17.0.2",    "react-dom": "^17.0.2",    "react-scripts": "5.0.0",
    "web-vitals": "^2.1.4"
  },
  // ...
}

Une fois la modification effectuée, réinstallez les dépendances en exécutant

npm install

Notez que le fichier index.js doit également être légèrement modifié. Pour React 17, cela ressemble à

import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(<App />, document.getElementById('root'))

mais pour React 18, la forme correcte est

import React from 'react'
import ReactDOM from 'react-dom/client'

import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

État complexe

Dans notre exemple précédent, l'état de l'application était simple car il était composé d'un seul entier. Et si notre application nécessite un état plus complexe ?

Dans la plupart des cas, le moyen le plus simple et plus adéquat d'y parvenir est d'utiliser la fonction useState plusieurs fois pour créer des "morceaux" d'état séparés.

Dans le code suivant, nous créons deux éléments d'état nommés left et right qui obtiennent tous deux la valeur initiale de 0 :

const App = () => {
  const [left, setLeft] = useState(0)
  const [right, setRight] = useState(0)

  return (
    <div>
      {left}
      <button onClick={() => setLeft(left + 1)}>
        left
      </button>
      <button onClick={() => setRight(right + 1)}>
        right
      </button>
      {right}
    </div>
  )
}

Le composant a accès aux fonctions setLeft et setRight qu'il peut utiliser pour mettre à jour les deux états.

L'état du composant ou une partie de son état peut être de n'importe quel type. Nous pourrions implémenter la même fonctionnalité en enregistrant le nombre de clics des boutons gauche et droit dans un seul objet :

{
  left: 0,
  right: 0
}

Dans ce cas, l'application ressemblerait à ceci :

const App = () => {
  const [clicks, setClicks] = useState({
    left: 0, right: 0
  })

  const handleLeftClick = () => {
    const newClicks = { 
      left: clicks.left + 1, 
      right: clicks.right 
    }
    setClicks(newClicks)
  }

  const handleRightClick = () => {
    const newClicks = { 
      left: clicks.left, 
      right: clicks.right + 1 
    }
    setClicks(newClicks)
  }

  return (
    <div>
      {clicks.left}
      <button onClick={handleLeftClick}>left</button>
      <button onClick={handleRightClick}>right</button>
      {clicks.right}
    </div>
  )
}

Désormais, le composant n'a qu'un seul état et les gestionnaires d'événements doivent s'occuper de modifier l'état de l'ensemble de l'application.

Le gestionnaire d'événements semble un peu brouillon. Lorsque le bouton gauche est cliqué, la fonction suivante est appelée :

const handleLeftClick = () => {
  const newClicks = { 
    left: clicks.left + 1, 
    right: clicks.right 
  }
  setClicks(newClicks)
}

L'objet suivant est défini comme nouvel état de l'application :

{
  left: clicks.left + 1,
  right: clicks.right
}

La nouvelle valeur de la propriété left est maintenant la même que la valeur de left + 1 de l'état précédent, et la valeur de la propriété right est la même que la valeur de la propriété right de l'état précédent.

Nous pouvons définir le nouvel état de l'objet un peu plus précisément en utilisant la syntaxe de propagation de l'objet object spread, syntaxe qui a été ajoutée à la spécification du langage à l'été 2018 :

const handleLeftClick = () => {
  const newClicks = { 
    ...clicks, 
    left: clicks.left + 1 
  }
  setClicks(newClicks)
}

const handleRightClick = () => {
  const newClicks = { 
    ...clicks, 
    right: clicks.right + 1 
  }
  setClicks(newClicks)
}

La syntaxe peut sembler un peu étrange au premier abord. En pratique, { ...clicks } crée un nouvel objet qui a des copies de toutes les propriétés de l'objet clicks. Lorsque nous spécifions une propriété particulière - par ex. right in { ...clicks, right: 1 }, la valeur de la propriété right dans le nouvel objet sera 1.

Dans l'exemple ci-dessus, ceci :

{ ...clicks, right: clicks.right + 1 }

crée une copie de l'objet clicks où la valeur de la propriété right est augmentée de un.

L'affectation de l'objet à une variable dans les gestionnaires d'événements n'est pas nécessaire et nous pouvons simplifier les fonctions sous la forme suivante :

const handleLeftClick = () =>
  setClicks({ ...clicks, left: clicks.left + 1 })

const handleRightClick = () =>
  setClicks({ ...clicks, right: clicks.right + 1 })

Certains lecteurs pourraient se demander pourquoi nous n'avons pas simplement mis à jour l'état directement, comme ceci :

const handleLeftClick = () => {
  clicks.left++
  setClicks(clicks)
}

L'application semble fonctionner. Cependant, il est interdit dans React de muter directement l'état, car cela peut entraîner des effets secondaires inattendus. Le changement d'état doit toujours être effectué en définissant l'état sur un nouvel objet. Si les propriétés de l'objet d'état précédent ne sont pas modifiées, elles doivent simplement être copiées, ce qui se fait en copiant ces propriétés dans un nouvel objet et en le définissant comme nouvel état.

Stocker tout l'état dans un seul objet d'état est un mauvais choix pour cette application particulière ; il n'y a aucun avantage apparent et l'application qui en résulte est beaucoup plus complexe. Dans ce cas, stocker les compteurs de clics dans des éléments d'état séparés est un choix bien plus approprié.

Il existe des situations où il peut être avantageux de stocker une partie de l'état de l'application dans une structure de données plus complexe. La documentation officielle de React contient des conseils utiles sur le sujet.

Gestion des tableaux

Ajoutons un élément d'état à notre application contenant un tableau allClicks qui se souvient de chaque clic qui s'est produit dans l'application.

const App = () => {
  const [left, setLeft] = useState(0)
  const [right, setRight] = useState(0)
  const [allClicks, setAll] = useState([])
  const handleLeftClick = () => {    setAll(allClicks.concat('L'))    setLeft(left + 1)  }
  const handleRightClick = () => {    setAll(allClicks.concat('R'))    setRight(right + 1)  }
  return (
    <div>
      {left}
      <button onClick={handleLeftClick}>left</button>
      <button onClick={handleRightClick}>right</button>
      {right}
      <p>{allClicks.join(' ')}</p>    </div>
  )
}

Chaque clic est stocké dans un élément d'état séparé appelé allClicks qui est initialisé sous la forme d'un tableau vide :

const [allClicks, setAll] = useState([])

Lorsque le bouton gauche est cliqué, nous ajoutons la lettre L au tableau allClicks :

const handleLeftClick = () => {
  setAll(allClicks.concat('L'))
  setLeft(left + 1)
}

L'élément d'état stocké dans allClicks est désormais défini comme un tableau contenant tous les éléments du tableau d'état précédent plus la lettre L. L'ajout du nouvel élément au tableau est accompli avec la méthode concat, qui ne mute pas le tableau existant mais renvoie plutôt une nouvelle copie du tableau avec l'élément ajouté.

Comme mentionné précédemment, il est également possible en JavaScript d'ajouter des éléments à un tableau avec la méthode push . Si nous ajoutons l'élément en le poussant vers le tableau allClicks puis en mettant à jour l'état, l'application semblerait toujours fonctionner :

const handleLeftClick = () => {
  allClicks.push('L')
  setAll(allClicks)
  setLeft(left + 1)
}

Cependant, __ ne faites pas cela. Comme mentionné précédemment, l'état des composants React comme allClicks ne doit pas être muté directement. Même si l'état de mutation semble fonctionner dans certains cas, cela peut entraîner des problèmes très difficiles à déboguer.

Regardons de plus près comment le clic est rendu sur la page :

const App = () => {
  // ...

  return (
    <div>
      {left}
      <button onClick={handleLeftClick}>left</button>
      <button onClick={handleRightClick}>right</button>
      {right}
      <p>{allClicks.join(' ')}</p>    </div>
  )
}

Nous appelons la méthode join sur le tableau allClicks qui joint tous les éléments en une seule chaîne, séparés par la chaîne passée en paramètre de la fonction, qui dans notre cas est un espace vide.

Rendu conditionnel

Modifions notre application pour que le rendu de l'historique des clics soit géré par un nouveau composant History :

const History = (props) => {  if (props.allClicks.length === 0) {    return (      <div>        the app is used by pressing the buttons      </div>    )  }  return (    <div>      button press history: {props.allClicks.join(' ')}    </div>  )}
const App = () => {
  // ...

  return (
    <div>
      {left}
      <button onClick={handleLeftClick}>left</button>
      <button onClick={handleRightClick}>right</button>
      {right}
      <History allClicks={allClicks} />    </div>
  )
}

Maintenant, le comportement du composant dépend du fait que des boutons aient été cliqués ou non. Si ce n'est pas le cas, ce qui signifie que le tableau allClicks est vide, le composant restitue un élément div avec quelques instructions à la place :

<div>the app is used by pressing the buttons</div>

Et dans tous les autres cas, le composant restitue l'historique des clics :

<div>
  button press history: {props.allClicks.join(' ')}
</div>

Le composant History rend des éléments React complètement différents en fonction de l'état de l'application. C'est ce qu'on appelle le rendu conditionnel.

React propose également de nombreuses autres façons de faire le rendu conditionnel. Nous y reviendrons plus en détail dans la partie 2.

Apportons une dernière modification à notre application en la refactorisant pour utiliser le composant Button que nous avons défini précédemment :

const History = (props) => {
  if (props.allClicks.length === 0) {
    return (
      <div>
        the app is used by pressing the buttons
      </div>
    )
  }

  return (
    <div>
      button press history: {props.allClicks.join(' ')}
    </div>
  )
}

const Button = ({ handleClick, text }) => (  <button onClick={handleClick}>    {text}  </button>)
const App = () => {
  const [left, setLeft] = useState(0)
  const [right, setRight] = useState(0)
  const [allClicks, setAll] = useState([])

  const handleLeftClick = () => {
    setAll(allClicks.concat('L'))
    setLeft(left + 1)
  }

  const handleRightClick = () => {
    setAll(allClicks.concat('R'))
    setRight(right + 1)
  }

  return (
    <div>
      {left}
      <Button handleClick={handleLeftClick} text='left' />      <Button handleClick={handleRightClick} text='right' />      {right}
      <History allClicks={allClicks} />
    </div>
  )
}

Ancienne version de React

Dans ce cours, nous utilisons le state hook pour ajouter un état à nos composants React, qui fait partie des nouvelles versions de React et est disponible à partir de la version 16.8.0 et versions ultérieures. Avant l'ajout des hooks, il n'y avait aucun moyen d'ajouter un état aux composants fonctionnels. Les composants qui nécessitaient un état devaient être définis en tant que composants classes, à l'aide de la syntaxe de classe JavaScript.

Dans ce cours, nous avons pris la décision radicale d'utiliser exclusivement les hooks dès le premier jour, pour nous assurer que nous apprenons le style actuel et futur de React. Même si les composants fonctionnels sont l'avenir de React, il est toujours important d'apprendre la syntaxe de la classe, car il existe des milliards de lignes de code React que vous pourriez finir par maintenir un jour. Il en va de même pour la documentation et les exemples de React que vous pouvez trouver sur Internet.

Nous en apprendrons plus sur les composants classes de React plus tard dans le cours.

Débogage des applications React

Une grande partie du temps d'un développeur typique est consacrée au débogage et à la lecture du code existant. De temps en temps, nous écrivons une ligne ou deux de nouveau code, mais une grande partie de notre temps est consacrée à essayer de comprendre pourquoi quelque chose est cassé ou comment quelque chose fonctionne. Les bonnes pratiques et les outils de débogage sont extrêmement importants pour cette raison.

Heureusement pour nous, React est une bibliothèque extrêmement conviviale pour les développeurs en matière de débogage.

Avant de poursuivre, rappelons-nous l'une des règles les plus importantes du développement Web.

La première règle du développement Web

Gardez la console développeur du navigateur ouverte à tout moment.

L'onglet Console en particulier doit toujours être ouvert, sauf s'il existe une raison spécifique d'afficher un autre onglet.

Gardez votre code et la page Web ouverts ensemble en même temps, tout le temps.

Si et quand votre code ne compile pas et que votre navigateur s'allume comme un sapin de Noël :

fullstack content

n'écrivez pas plus de code mais plutôt trouvez et corrigez le problème immédiatement. Il n'y a pas encore eu de moment dans l'histoire du codage où le code qui ne compile pas commencerait miraculeusement à fonctionner après avoir écrit de grandes quantités de code supplémentaire. Je doute fortement qu'un tel événement se produise au cours de ce cours non plus.

Le débogage à l'ancienne, basé sur l'impression, est toujours une bonne idée. Si le composant

const Button = ({ onClick, text }) => (
  <button onClick={onClick}>
    {text}
  </button>
)

ne fonctionne pas comme prévu, il est utile de commencer à imprimer ses variables sur la console. Pour le faire efficacement, nous devons transformer notre fonction dans la forme la moins compacte et recevoir l'intégralité de l'objet props sans le déstructurer immédiatement :

const Button = (props) => { 
  console.log(props)  const { onClick, text } = props
  return (
    <button onClick={onClick}>
      {text}
    </button>
  )
}

Cela révélera immédiatement si, par exemple, l'un des attributs a été mal orthographié lors de l'utilisation du composant.

NB Lorsque vous utilisez console.log pour le débogage, ne combinez pas objects à la manière de Java en utilisant l'opérateur plus. Au lieu d'écrire :

console.log('props value is ' + props)

Séparez les éléments que vous souhaitez consigner dans la console par une virgule :

console.log('props value is', props)

Si vous utilisez la manière Java de concaténer une chaîne avec un objet, vous vous retrouverez avec un message de journal plutôt peu informatif :

props value is [Object object]

Alors que les éléments séparés par une virgule seront tous disponibles dans la console du navigateur pour une inspection plus approfondie.

Se connecter à la console n'est en aucun cas le seul moyen de déboguer nos applications. Vous pouvez suspendre l'exécution de votre code d'application dans le débogueur de la console développeur Chrome, en écrivant la commande debugger n'importe où dans votre code.

L'exécution s'arrêtera une fois qu'elle arrivera à un point où la commande debugger sera exécutée :

fullstack content

En allant dans l'onglet Console, il est facile d'inspecter l'état actuel des variables :

fullstack content

Une fois la cause du bogue découverte, vous pouvez supprimer la commande debugger et actualiser la page.

Le débogueur nous permet également d'exécuter notre code ligne par ligne avec les contrôles situés à droite de l'onglet Sources.

Vous pouvez également accéder au débogueur sans la commande debugger en ajoutant des points d'arrêt dans l'onglet Sources. L'inspection des valeurs des variables du composant peut être effectuée dans la section Scope :

fullstack content

Il est fortement recommandé d'ajouter l'extension React developer tools à Chrome. Il ajoute un nouvel onglet Components aux outils de développement. Le nouvel onglet des outils de développement peut être utilisé pour inspecter les différents éléments React de l'application, ainsi que leur état et leurs props:

fullstack content

L'état du composant App est défini comme suit :

const [left, setLeft] = useState(0)
const [right, setRight] = useState(0)
const [allClicks, setAll] = useState([])

Dev tools affichent l'état des hooks dans l'ordre de leur définition :

fullstack content

Le premier State contient la valeur de l'état left, le suivant contient la valeur de l'état right et le dernier contient la valeur de l'état état de tous les clics.

Règles des Hooks

Il y a quelques limitations et règles que nous devons suivre pour nous assurer que notre application utilise correctement les fonctions d'état basées sur les hooks.

La fonction useState (ainsi que la fonction useEffect introduite plus tard dans le cours) ne doit pas être appelée depuis l'intérieur d'une boucle, d'une expression conditionnelle ou de tout endroit qui n'est pas une fonction définissant un composant. Cela doit être fait pour s'assurer que les hooks sont toujours appelés dans le même ordre, et si ce n'est pas le cas, l'application se comportera de manière erratique.

Pour récapituler, les hooks ne peuvent être appelés que depuis l'intérieur d'un corps de fonction qui définit un composant React :

const App = () => {
  // ceci est ok
  const [age, setAge] = useState(0)
  const [name, setName] = useState('Juha Tauriainen')

  if ( age > 10 ) {
    // ceci ne marche pas!
    const [foobar, setFoobar] = useState(null)
  }

  for ( let i = 0; i < age; i++ ) {
    // toujours pas ok !
    const [rightWay, setRightWay] = useState(false)
  }

  const notGood = () => {
    // et ceci est presqu'un péché !
    const [x, setX] = useState(-1000)
  }

  return (
    //...
  )
}

Gestion des événements revisitée

La gestion des événements s'est avérée être un sujet difficile dans les versions précédentes de ce cours.

C'est pourquoi nous reviendrons sur le sujet.

Supposons que nous développions cette application simple avec le composant suivant App :

const App = () => {
  const [value, setValue] = useState(10)

  return (
    <div>
      {value}
      <button>reset to zero</button>
    </div>
  )
}

Nous voulons que le clic sur le bouton réinitialise l'état stocké dans la variable value.

Afin de faire réagir le bouton à un événement de clic, nous devons lui ajouter un event handler.

Les gestionnaires d'événements doivent toujours être une fonction ou une référence à une fonction. Le bouton ne fonctionnera pas si le gestionnaire d'événements est défini sur une variable d'un autre type.

Si nous devions définir notre event handler sous forme de chaîne :

<button onClick="crap...">button</button>

React nous en avertirait dans la console :

index.js:2178 Warning: Expected `onClick` listener to be a function, instead got a value of `string` type.
    in button (at index.js:20)
    in div (at index.js:18)
    in App (at index.js:27)

La tentative suivante ne fonctionnerait pas non plus :

<button onClick={value + 1}>button</button>

Nous avons tenté de définir le gestionnaire d'événements sur value + 1 qui renvoie simplement le résultat de l'opération. React nous en avertira gentiment dans la console :

index.js:2178 Warning: Expected `onClick` listener to be a function, instead got a value of `number` type.

Cette tentative ne fonctionnerait pas non plus :

<button onClick={value = 0}>button</button>

Le gestionnaire d'événements n'est pas une fonction mais une affectation de variable, et React émettra à nouveau un avertissement sur la console. Cette tentative est également imparfaite dans le sens où nous ne devons jamais muter l'état directement dans React.

Qu'en est-il des éléments suivants :

<button onClick={console.log('clicked the button')}>
  button
</button>

Le message est affiché sur la console une fois lorsque le composant est rendu, mais rien ne se passe lorsque nous cliquons sur le bouton. Pourquoi cela ne fonctionne-t-il pas même lorsque notre gestionnaire d'événements contient une fonction console.log ?

Le problème ici est que notre gestionnaire d'événements est défini comme un appel de fonction, ce qui signifie que le gestionnaire d'événements se voit en fait attribuer la valeur renvoyée par la fonction, qui dans le cas de console.log est undefined .

L'appel de la fonction console.log est exécuté lorsque le composant est rendu et pour cette raison, il est imprimé une fois sur la console.

La tentative suivante est également erronée :

<button onClick={setValue(0)}>button</button>

Nous avons de nouveau essayé de définir un appel de fonction comme gestionnaire d'événements. Cela ne fonctionne pas. Cette tentative particulière provoque également un autre problème. Lorsque le composant est rendu, la fonction setValue(0) est exécutée, ce qui entraîne à son tour le rendu du composant. Le re-rendu appelle à son tour setValue(0), ce qui entraîne une récursivité infinie.

L'exécution d'un appel de fonction particulier lorsque le bouton est cliqué peut être accompli comme ceci :

<button onClick={() => console.log('clicked the button')}>
  button
</button>

Maintenant, le gestionnaire d'événements est une fonction définie avec la syntaxe de la fonction fléchée () => console.log('clicked the button'). Lorsque le composant est rendu, aucune fonction n'est appelée et seule la référence à la fonction fléchée est définie sur le gestionnaire d'événements. L'appel de la fonction n'a lieu qu'une fois le bouton cliqué.

Nous pouvons implémenter la réinitialisation de l'état dans notre application avec cette même technique :

<button onClick={() => setValue(0)}>button</button>

Le gestionnaire d'événements est maintenant la fonction () => setValue(0).

Définir les gestionnaires d'événements directement dans l'attribut du bouton n'est pas nécessairement la meilleure idée possible.

Vous verrez souvent des gestionnaires d'événements définis dans un endroit séparé. Dans la version suivante de notre application, nous définissons une fonction qui est ensuite affectée à la variable handleClick dans le corps de la fonction composant :

const App = () => {
  const [value, setValue] = useState(10)

  const handleClick = () =>
    console.log('clicked the button')

  return (
    <div>
      {value}
      <button onClick={handleClick}>button</button>
    </div>
  )
}

La variable handleClick est maintenant affectée à une référence à la fonction. La référence est transmise au bouton en tant qu'attribut onClick :

<button onClick={handleClick}>button</button>

Naturellement, notre fonction de gestion d'événements peut être composée de plusieurs commandes. Dans ces cas, nous utilisons la syntaxe des accolades plus longues pour les fonctions fléchées :

const App = () => {
  const [value, setValue] = useState(10)

  const handleClick = () => {    console.log('clicked the button')    setValue(0)  }
  return (
    <div>
      {value}
      <button onClick={handleClick}>button</button>
    </div>
  )
}

Fonction qui renvoie une fonction

Une autre façon de définir un gestionnaire d'événements consiste à utiliser fonction qui renvoie une fonction.

Vous n'aurez probablement pas besoin d'utiliser des fonctions qui renvoient des fonctions dans aucun des exercices de ce cours. Si le sujet semble particulièrement déroutant, vous pouvez ignorer cette section pour le moment et y revenir plus tard.

Apportons les modifications suivantes à notre code :

const App = () => {
  const [value, setValue] = useState(10)

  const hello = () => {    const handler = () => console.log('hello world')    return handler  }
  return (
    <div>
      {value}
      <button onClick={hello()}>button</button>
    </div>
  )
}

Le code fonctionne correctement même s'il semble compliqué.

Le gestionnaire d'événements est maintenant défini sur un appel de fonction :

<button onClick={hello()}>button</button>

Plus tôt, nous avons déclaré qu'un gestionnaire d'événements ne peut pas être un appel à une fonction, et qu'il doit être une fonction ou une référence à une fonction. Pourquoi alors un appel de fonction fonctionne-t-il dans ce cas ?

Lorsque le composant est rendu, la fonction suivante est exécutée :

const hello = () => {
  const handler = () => console.log('hello world')

  return handler
}

La valeur de retour de la fonction est une autre fonction affectée à la variable handler.

Lorsque React affiche la ligne :

<button onClick={hello()}>button</button>

Il attribue la valeur de retour de hello() à l'attribut onClick. Essentiellement, la ligne se transforme en :

<button onClick={() => console.log('hello world')}>
  button
</button>

Puisque la fonction hello renvoie une fonction, le gestionnaire d'événements est maintenant une fonction.

Quel est l'intérêt de ce concept ?

Modifions un tout petit peu le code :

const App = () => {
  const [value, setValue] = useState(10)

  const hello = (who) => {    const handler = () => {      console.log('hello', who)    }    return handler  }
  return (
    <div>
      {value}
      <button onClick={hello('world')}>button</button>      <button onClick={hello('react')}>button</button>      <button onClick={hello('function')}>button</button>    </div>
  )
}

Maintenant, l'application a trois boutons avec des gestionnaires d'événements définis par la fonction hello qui accepte un paramètre.

Le premier bouton est défini comme

<button onClick={hello('world')}>button</button>

Le gestionnaire d'événements est créé en exécutant l'appel de fonction hello('world'). L'appel de fonction renvoie la fonction :

() => {
  console.log('hello', 'world')
}

Le deuxième bouton est défini comme :

<button onClick={hello('react')}>button</button>

L'appel de fonction hello('react') qui crée le event handler renvoie :

() => {
  console.log('hello', 'react')
}

Les deux boutons disposent de leurs propres gestionnaires d'événements individualisés.

Les fonctions renvoyant des fonctions peuvent être utilisées pour définir des fonctionnalités génériques qui peuvent être personnalisées avec des paramètres. La fonction hello qui crée les gestionnaires d'événements peut être considérée comme une usine qui produit des event handlers personnalisés destinés à accueillir les utilisateurs.

Notre définition actuelle est légèrement verbeuse :

const hello = (who) => {
  const handler = () => {
    console.log('hello', who)
  }

  return handler
}

Éliminons les variables d'assistance et renvoyons directement la fonction créée :

const hello = (who) => {
  return () => {
    console.log('hello', who)
  }
}

Puisque notre fonction hello est composée d'une seule commande de retour, nous pouvons omettre les accolades et utiliser la syntaxe plus compacte pour les fonctions fléchées :

const hello = (who) =>
  () => {
    console.log('hello', who)
  }

Enfin, écrivons toutes les flèches sur la même ligne :

const hello = (who) => () => {
  console.log('hello', who)
}

Nous pouvons utiliser la même astuce pour définir des gestionnaires d'événements qui définissent l'état du composant à une valeur donnée. Apportons les modifications suivantes à notre code :

const App = () => {
  const [value, setValue] = useState(10)
  
  const setToValue = (newValue) => () => {    console.log('value now', newValue)  // affiche la nouvelle valeur sur la console    setValue(newValue)  }  
  return (
    <div>
      {value}
      <button onClick={setToValue(1000)}>thousand</button>      <button onClick={setToValue(0)}>reset</button>      <button onClick={setToValue(value + 1)}>increment</button>    </div>
  )
}

Lorsque le composant est rendu, le bouton thousand est créé :

<button onClick={setToValue(1000)}>thousand</button>

Le gestionnaire d'événements est défini sur la valeur de retour de setToValue(1000) qui est la fonction suivante :

() => {
  console.log('value now', 1000)
  setValue(1000)
}

Le bouton d'incrémentation est déclaré comme suit :

<button onClick={setToValue(value + 1)}>increment</button>

Le gestionnaire d'événements est créé par l'appel de fonction setToValue(value + 1) qui reçoit en paramètre la valeur courante de la variable d'état value augmentée de un. Si la valeur de value était 10, alors le gestionnaire d'événements créé serait la fonction :

() => {
  console.log('value now', 11)
  setValue(11)
}

L'utilisation de fonctions qui renvoient des fonctions n'est pas nécessaire pour obtenir cette fonctionnalité. Renvoyons la fonction setToValue qui est responsable de la mise à jour de l'état, dans une fonction normale :

const App = () => {
  const [value, setValue] = useState(10)

  const setToValue = (newValue) => {
    console.log('value now', newValue)
    setValue(newValue)
  }

  return (
    <div>
      {value}
      <button onClick={() => setToValue(1000)}>
        thousand
      </button>
      <button onClick={() => setToValue(0)}>
        reset
      </button>
      <button onClick={() => setToValue(value + 1)}>
        increment
      </button>
    </div>
  )
}

Nous pouvons maintenant définir le gestionnaire d'événements comme une fonction qui appelle la fonction setToValue avec un paramètre approprié. Le gestionnaire d'événements pour réinitialiser l'état de l'application serait :

<button onClick={() => setToValue(0)}>reset</button>

Choisir entre les deux façons présentées pour définir vos gestionnaires d'événements est surtout une question de goût.

Passer vos events handlers aux composants enfants

Extrayons le bouton dans son propre composant :

const Button = (props) => (
  <button onClick={props.handleClick}>
    {props.text}
  </button>
)

Le composant obtient la fonction de gestionnaire d'événements de la prop handleClick et le texte du bouton de la prop text.

L'utilisation du composant Button est simple, même si nous devons nous assurer que nous utilisons les noms d'attribut corrects lors de la transmission des props au composant.

fullstack content

Ne pas définir de composants dans les composants

Commençons à afficher la valeur de l'application dans son propre composant Display.

Nous allons changer l'application en définissant un nouveau composant à l'intérieur du composant App.

// C'est le bon endroit pour définir un composant
const Button = (props) => (
  <button onClick={props.handleClick}>
    {props.text}
  </button>
)

const App = () => {
  const [value, setValue] = useState(10)

  const setToValue = newValue => {
    console.log('value now', newValue)
    setValue(newValue)
  }

  // Ne pas définir de composants à l'intérieur d'un autre composant
  const Display = props => <div>{props.value}</div>
  return (
    <div>
      <Display value={value} />
      <Button handleClick={() => setToValue(1000)} text="thousand" />
      <Button handleClick={() => setToValue(0)} text="reset" />
      <Button handleClick={() => setToValue(value + 1)} text="increment" />
    </div>
  )
}

L'application semble toujours fonctionner, mais n'implémentez pas de composants comme celui-ci ! Ne définissez jamais de composants à l'intérieur d'autres composants. La méthode n'offre aucun avantage et entraîne de nombreux problèmes désagréables. Les plus gros problèmes sont dus au fait que React traite un composant défini à l'intérieur d'un autre composant comme un nouveau composant dans chaque rendu. Cela rend impossible pour React d'optimiser le composant.

Déplaçons plutôt la fonction de composant Display à sa place correcte, qui est en dehors de la fonction de composant App :

const Display = props => <div>{props.value}</div>

const Button = (props) => (
  <button onClick={props.handleClick}>
    {props.text}
  </button>
)

const App = () => {
  const [value, setValue] = useState(10)

  const setToValue = newValue => {
    console.log('value now', newValue)
    setValue(newValue)
  }

  return (
    <div>
      <Display value={value} />
      <Button handleClick={() => setToValue(1000)} text="thousand" />
      <Button handleClick={() => setToValue(0)} text="reset" />
      <Button handleClick={() => setToValue(value + 1)} text="increment" />
    </div>
  )
}

Lecture utile

Internet regorge de contenu lié à React. Cependant, nous utilisons le nouveau style de React pour lequel une grande majorité du matériel trouvé en ligne est obsolète.

Les liens suivants peuvent vous être utiles :

  • La documentation officielle de React vaut la peine d'être consultée à un moment donné, même si la plupart d'entre elles ne deviendront pertinentes que plus tard dans le cours. De plus, tout ce qui concerne les composants basés sur des classes ne nous concerne pas ;
  • Certains cours sur Egghead.io comme Start learning React sont de haute qualité, et récemment mis à jour Le guide du débutant pour réagir est également relativement bon ; les deux cours introduisent des concepts qui seront également introduits plus tard dans ce cours. NB Le premier utilise des composants classes mais le second utilise les nouveaux composants fonctionnels.