Aller au contenu

b

Formulaires

Continuons à développer notre application en permettant aux utilisateurs d'ajouter de nouvelles notes. Vous pouvez trouver le code de l'application actuelle ici.

Afin que notre page soit mise à jour lorsque de nouvelles notes sont ajoutées, il est préférable de stocker les notes dans l'état du composant App. Importons la fonction useState et utilisons-la pour définir un élément d'état qui est initialisé avec le tableau de notes initial transmis dans les accessoires.

import { useState } from 'react'import Note from './components/Note'

const App = (props) => {  const [notes, setNotes] = useState(props.notes)
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <Note key={note.id} note={note} />
        )}
      </ul>
    </div>
  )
}

export default App 

Le composant utilise la fonction useState pour initialiser l'élément d'état stocké dans notes avec le tableau de notes passé dans les accessoires :

const App = (props) => { 
  const [notes, setNotes] = useState(props.notes) 

  // ...
}

Si nous voulions commencer avec une liste vide de notes, nous définirions la valeur initiale comme un tableau vide, et puisque les accessoires ne seraient pas utilisés, nous pourrions omettre le paramètre props de la définition de la fonction :

const App = () => { 
  const [notes, setNotes] = useState([]) 

  // ...
}  

Restons avec la valeur initiale transmise dans les props pour le moment.

Ensuite, ajoutons un formulaire HTML au composant qui sera utilisé pour ajouter de nouvelles notes.

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)

  const addNote = (event) => {    event.preventDefault()    console.log('button clicked', event.target)  }
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <Note key={note.id} note={note} />
        )}
      </ul>
      <form onSubmit={addNote}>        <input />        <button type="submit">save</button>      </form>       </div>
  )
}

Nous avons ajouté la fonction addNote en tant que event handler à l'élément du formulaire qui sera appelé lors de la soumission du formulaire, le click sur le bouton Soumettre.

Nous utilisons la méthode décrite dans la partie 1 pour définir notre gestionnaire d'événements :

const addNote = (event) => {
  event.preventDefault()
  console.log('button clicked', event.target)
}

Le paramètre event est l'évènement qui déclenche l'appel de la fonction de gestion d'événements :

Le gestionnaire d'événements appelle immédiatement la méthode event.preventDefault(), qui empêche l'action par défaut de soumettre un formulaire. L'action par défaut entraînerait entre autres la page à recharger.

La cible de l'événement stocké dans event.target est consignée dans la console :

fullstack content

La cible dans ce cas est le formulaire que nous avons défini dans notre composant.

Comment accède-t-on aux données contenues dans l'élément input du formulaire ?

Composant contrôlé

Il y a plusieurs façons d'accomplir ceci; la première méthode que nous allons examiner consiste à utiliser ce que l'on appelle des composants contrôlés.

Ajoutons un nouvel élément d'état appelé newNote pour stocker l'entrée soumise par l'utilisateur et définissons-le comme la valeur de l'élément input attribut:

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)
  const [newNote, setNewNote] = useState(    'a new note...'  ) 
  const addNote = (event) => {
    event.preventDefault()
    console.log('button clicked', event.target)
  }

  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <Note key={note.id} note={note} />
        )}
      </ul>
      <form onSubmit={addNote}>
        <input value={newNote} />        <button type="submit">save</button>
      </form>   
    </div>
  )
}

Le texte d'espace réservé stocké comme valeur initiale de l'état newNote apparaît dans l'élément input, mais le texte d'entrée ne peut pas être modifié. La console affiche un avertissement qui nous donne une idée de ce qui ne va pas :

fullstack content

Étant donné que nous avons attribué une partie de l'état du composant App en tant qu'attribut value de l'élément d'entrée, le composant App contrôle maintenant le comportement de l'élément d'entrée.

Afin de permettre l'édition de l'élément d'entrée, nous devons enregistrer un gestionnaire d'événements qui synchronise les modifications apportées à l'entrée avec l'état du composant :

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)
  const [newNote, setNewNote] = useState(
    'a new note...'
  ) 

  // ...

  const handleNoteChange = (event) => {    console.log(event.target.value)    setNewNote(event.target.value)  }
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <Note key={note.id} note={note} />
        )}
      </ul>
      <form onSubmit={addNote}>
        <input
          value={newNote}
          onChange={handleNoteChange}        />
        <button type="submit">save</button>
      </form>   
    </div>
  )
}

Nous avons maintenant enregistré un gestionnaire d'événements pour l'attribut onChange de l'élément input du formulaire :

<input
  value={newNote}
  onChange={handleNoteChange}
/>

Le gestionnaire d'événements est appelé chaque fois qu'un changement se produit dans l'élément d'entrée. La fonction de gestionnaire d'événements reçoit l'objet événement comme paramètre:

const handleNoteChange = (event) => {
  console.log(event.target.value)
  setNewNote(event.target.value)
}

La propriété target de l'objet événement correspond maintenant à l'élément input contrôlé, et event.target.value fait référence à la valeur d'entrée de cet élément .

Notez que nous n'avons pas eu besoin d'appeler la méthode event.preventDefault() comme nous l'avons fait dans le gestionnaire d'événements onSubmit. En effet, aucune action par défaut ne se produit lors d'un changement d'entrée, contrairement à une soumission de formulaire.

Vous pouvez suivre dans la console pour voir comment le gestionnaire d'événements est appelé :

fullstack content

Vous avez pensé à installer React devtools, n'est-ce pas ? Bien. Vous pouvez voir directement comment l'état change depuis l'onglet React Devtools :

fullstack content

Maintenant, l'état newNote du composant App reflète la valeur actuelle de l'entrée, ce qui signifie que nous pouvons compléter la fonction addNote pour créer de nouvelles notes :

const addNote = (event) => {
  event.preventDefault()
  const noteObject = {
    content: newNote,
    date: new Date().toISOString(),
    important: Math.random() < 0.5,
    id: notes.length + 1,
  }

  setNotes(notes.concat(noteObject))
  setNewNote('')
}

Tout d'abord, nous créons un nouvel objet pour la note appelée noteObject qui recevra son contenu de l'état newNote du composant. L'identifiant unique id est généré en fonction du nombre total de notes. Cette méthode fonctionne pour notre application puisque les notes ne sont jamais supprimées. Avec l'aide de la fonction Math.random(), notre note a 50 % de chances d'être marquée comme importante.

La nouvelle note est ajoutée à la liste des notes à l'aide de la méthode concat, introduite dans la partie 1:

setNotes(notes.concat(noteObject))

La méthode ne modifie pas le tableau notes d'origine, mais crée plutôt une nouvelle copie du tableau avec le nouvel élément ajouté à la fin. Ceci est important car nous ne devons jamais muter l'état directement dans React !

Le gestionnaire d'événements réinitialise également la valeur de l'élément d'entrée contrôlé en appelant la fonction setNewNote de l'état newNote :

setNewNote('')

Vous pouvez trouver le code de notre application actuelle dans son intégralité sur la branche part2-2 de ce référentiel GitHub.

Filtrage des éléments affichés

Ajoutons quelques nouvelles fonctionnalités à notre application qui nous permettrons de visualiser uniquement les notes importantes.

Ajoutons un élément d'état au composant App qui garde une trace des notes à afficher :

const App = (props) => {
  const [notes, setNotes] = useState(props.notes) 
  const [newNote, setNewNote] = useState('')
  const [showAll, setShowAll] = useState(true)  
  // ...
}

Modifions le composant afin qu'il stocke une liste de toutes les notes à afficher dans la variable notesToShow. Les éléments de la liste dépendent de l'état du composant :

import { useState } from 'react'
import Note from './components/Note'

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)
  const [newNote, setNewNote] = useState('') 
  const [showAll, setShowAll] = useState(true)

  // ...

  const notesToShow = showAll    ? notes    : notes.filter(note => note.important === true)
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notesToShow.map(note =>          <Note key={note.id} note={note} />
        )}
      </ul>
      // ...
    </div>
  )
}

La définition de la variable notesToShow est plutôt compacte :

const notesToShow = showAll
  ? notes
  : notes.filter(note => note.important === true)

La définition utilise l'opérateur conditionnel également présent dans de nombreux autres langages de programmation.

L'opérateur fonctionne comme suit. Si nous avons:

const result = condition ? val1 : val2

la variable résultat sera définie sur la valeur de val1 si la condition est vraie. Si condition est fausse, la variable result sera définie sur la valeur de val2.

Si la valeur de showAll est false, la variable notesToShow sera affectée à une liste qui ne contient que des notes dont la propriété important est définie sur true . Le filtrage est effectué à l'aide de la méthode array filter :

notes.filter(note => note.important === true)

L'opérateur de comparaison est en fait redondant, puisque la valeur de note.important est soit true soit false, ce qui signifie qu'on peut simplement écrire :

notes.filter(note => note.important)

La raison pour laquelle nous avons d'abord montré l'opérateur de comparaison était de souligner un détail important : en JavaScript, val1 == val2 ne fonctionne pas comme prévu dans toutes les situations et il est plus sûr d'utiliser val1 === val2 exclusivement dans les comparaisons. Vous pouvez en savoir plus sur le sujet ici.

Vous pouvez tester la fonctionnalité de filtrage en modifiant la valeur initiale de l'état showAll.

Ensuite, ajoutons une fonctionnalité qui permet aux utilisateurs de basculer l'état showAll de l'application à partir de l'interface utilisateur.

Les changements pertinents sont indiqués ci-dessous :

import { useState } from 'react' 
import Note from './components/Note'

const App = (props) => {
  const [notes, setNotes] = useState(props.notes) 
  const [newNote, setNewNote] = useState('')
  const [showAll, setShowAll] = useState(true)

  // ...

  return (
    <div>
      <h1>Notes</h1>
      <div>        <button onClick={() => setShowAll(!showAll)}>          show {showAll ? 'important' : 'all' }        </button>      </div>      <ul>
        {notesToShow.map(note =>
          <Note key={note.id} note={note} />
        )}
      </ul>
      // ...    
    </div>
  )
}

Les notes affichées (toutes versus importantes) sont contrôlées par un bouton. Le gestionnaire d'événements pour le bouton est si simple qu'il a été défini directement dans l'attribut de l'élément bouton. Le gestionnaire d'événements fait passer la valeur de showAll de true à false et vice versa :

() => setShowAll(!showAll)

Le texte du bouton dépend de la valeur de l'état showAll :

show {showAll ? 'important' : 'all'}

Vous pouvez trouver le code de l'application actuelle dans son intégralité sur la branche part2-3 de ce référentiel GitHub.