d) L'état plus avancé
Comment gérer un formulaire en React ?
Introduction
Il y a beaucoup d'applications web où nous allons souhaiter gérer un formulaire.
En React, pour afficher ce qui est visible dans un formulaire, nous allons devoir jouer avec l'état de l'application.
Pour ce tutoriel, veuillez créer une copie du tutoriel start-state
, si nécessaire voici le code du tutoriel start-state, et l'appeler medium-state
. Changez le nom du projet dans package.json
.
Il existe deux façons de gérer des formulaires en React :
- les formulaires non contrôlés par React : les valeurs des inputs sont gérées par le DOM (Document Object Model) ; c'est la manière qui correspond à la manière de faire en JS pur, quand on fait de la programmation "old-school" ;
- les formulaires contrôlés par React : les valeurs des inputs sont gérées par React, via un état.
Formulaire non contrôlé par React
Dans le composant Main
(/src/components/Main/index.tsx
), à la suite de PizzaMenu
, nous allons ajouter un formulaire :
tsxconst Main = () => {const handleSubmit = (e: SyntheticEvent) => {e.preventDefault();const form = e.target as HTMLFormElement;console.log("submit:", form.pizza.value, form.description.value);};return (<main><p>My HomePage</p><p>Because we love JS, you can also click on the header to stop / start themusic ; )</p><audio id="audioPlayer" controls autoPlay><source src={sound} type="audio/mpeg" />Your browser does not support the audio element.</audio><PizzaMenu /><div><br /><form onSubmit={handleSubmit}><label htmlFor="pizza">Pizza</label><input type="text" id="pizza" name="pizza" /><label htmlFor="description">Description</label><input type="text" id="description" name="description" /><button type="submit">Ajouter</button></form></div>
Ce code va être expliqué en découvrant ce qu'est l'objet "event" et comment l'utiliser.
L' objet "event"
L'objet event
a été nommé e
ci-dessus, mais nous aurions pu lui donner le nom que l'on souhaitait.
👍 Pour éviter la confusion, il est recommandé de l'appeler e
(ou éventuellement event
).
L'objet event
est automatiquement passé à la callback d'un gestionnaire d'événements.
Il est très utile, pour deux raisons principalement :
- Stopper l'action par défaut associée à un événement.
- Lorsqu'on attache une même callback à une multitude d'éléments, pour retrouver la cible de l'événement.
e.target
est l'élément HTML qui lance la propagation de l'événement dans l'arbre des composants.
Parfois on préfère utiliser e.currentTarget
qui est l'élément HTML sur lequel est attaché le gestionnaire d'événements.
Dans le code ci-dessus, on utilise l'objet event
pour stopper l'action par défaut d'un formulaire qui est d'envoyer les données au serveur (indiqué par la propriété action
du formulaire) et de recharger la page.
Veuillez :
- exécuter l'application et vérifiez que tout fonctionne ;
- commenter
e.preventDefault()
:
tsconst handleSubmit = (e: SyntheticEvent) => {// e.preventDefault();const form = e.target as HTMLFormElement;console.log("submit:", form.pizza.value, form.description.value);};
Qu'est-ce qui se passe ?
Il y a un rechargement de page qui est interdit dans le type d'application que nous développons. Nous reviendrons plus tard sur pourquoi il n'est pas acceptable de recharger la page...
Formulaire contrôlé par React
Dans le formulaire développé jusque là, il n'est pas contrôlé par React. Nous avons accès à la valeur des inputs, néanmoins, ça n'est pas une pratique recommandée.
👍 Il est recommandé d'utiliser des composants contrôlés par React. Les valeurs des inputs doivent être contrôlées par React à travers un état, et les changements seront gérés via les gestionnaires d'événements (onChange
pour les inputs).
Comme nous avons deux inputs ici, nous allons créer deux variables d'états et les mettre à jour dans la callback associée aux gestionnaires d'événements onChange
:
tsx1const Main = () => {2const [pizza, setPizza] = useState("");3const [description, setDescription] = useState("");45const handleSubmit = (e: SyntheticEvent) => {6e.preventDefault();7console.log("submit:", pizza, description);8};910const handlePizzaChange = (e: SyntheticEvent) => {11const pizzaInput = e.target as HTMLInputElement;12console.log("change in pizzaInput:", pizzaInput.value);13setPizza(pizzaInput.value);14};1516const handleDescriptionChange = (e: SyntheticEvent) => {17const descriptionInput = e.target as HTMLInputElement;18console.log("change in descriptionInput:", descriptionInput.value);19setDescription(descriptionInput.value);20};2122return (23<main>24<p>My HomePage</p>25<p>26Because we love JS, you can also click on the header to stop / start the27music ; )28</p>29<audio id="audioPlayer" controls autoPlay>30<source src={sound} type="audio/mpeg" />31Your browser does not support the audio element.32</audio>33<PizzaMenu />34<div>35<br />36<form onSubmit={handleSubmit}>37<label htmlFor="pizza">Pizza</label>38<input39value={pizza}40type="text"41id="pizza"42name="pizza"43onChange={handlePizzaChange}44required45/>46<label htmlFor="description">Description</label>47<input48value={description}49type="text"50id="description"51name="description"52onChange={handleDescriptionChange}53required54/>55<button type="submit">Ajouter</button>56</form>57</div>
Nous voyons maintenant que :
- chaque valeur d'une input est contrôlée par une variable d'état qui est mise à jour à chaque changement opéré (dans l'input) ;
- que grâce à l'objet "event", nous avons accès à la valeur de chaque input via
e.target.value
. Néanmoins, comme TS est typé, nous devons d'abord faire une assertion de type poure.target
en indiquant que c'est unHTMLInputElement
afin d'avoir accès àvalue
.
Veuillez exécuter l'application, ouvrir la console, et observer ce qui se passe :
- quand vous écrivez dans les inputs,
- lorsque vous cliquez sur le bouton
Ajouter
.
Ca y est, nous avons appris comment maîtriser les formulaires en Flutter.
Il nous reste maintenant à voir comment utiliser les données du formulaire au sein d'une collection de données qui va permettre de mettre à jour nos écrans.
Gérer une collection comme état de l'application
Introduction
Très souvent, c'est une collection de données qui sera utilisée comme état de l'application.
Par exemple, dans notre tutoriel, nous souhaiterions qu'une collection de pizzas permette :
- d'afficher toutes les pizzas du menu ;
- d'ajouter automatiquement une nouvelle pizzas au menu après soumission des données du formulaire.
💭 Quand nous devons mettre en place une variable d'état, ici un array de Pizza
, il faut toujours se poser la question : "Mais où est-ce que je dois gérer cet état ?".
Actuellement, la collection de Pizza
est gérée dans le composant PizzaMenu
, qui est un "sibling" (un frère ou une sœur) du formulaire. Ainsi, si nous devons y accéder dans ces différents éléments, il faut faire monter l'état vers leur ancêtre commun le plus proche, leur parent. Ici, c'est le composant Main
.
Nous allons donc mettre à jour PizzaMenu
pour qu'il reçoive dans ses props la collection de pizza. Voici PizzaMenu
mis à jour :
tsx1import { Pizza } from "../../types";2import "./PizzaMenu.css";34interface PizzaMenuProps {5pizzas: Pizza[];6}78const PizzaMenu = ({ pizzas }: PizzaMenuProps) => {9return (10<table className="pizza-menu">11<thead>12<tr>13<th>Pizza</th>14<th>Description</th>15</tr>16</thead>17<tbody>18{pizzas.map((pizza) => (19<tr key={pizza.id}>20<td>{pizza.title}</td>21<td>{pizza.content}</td>22</tr>23))}24</tbody>25</table>26);27};2829export default PizzaMenu;
Nous avons créé l'interface Pizza
dans un nouveau fichier /src/types.ts
:
tsinterface Pizza {id: number;title: string;content: string;}export type { Pizza };
Pour simplifier le développement, nous vous conseillons d'enlever l'autoPlay
pour l'audio... Ca fera moins de bruit ; )
Voici la mise à jour du composant Main
afin de passer une variable d'état initialisée par les pizzas par défaut du menu (et enlever l'autoPlay
de l'audio):
tsx1const defaultPizzas = [2{3id: 1,4title: "4 fromages",5content: "Gruyère, Sérac, Appenzel, Gorgonzola, Tomates",6},7{8id: 2,9title: "Vegan",10content: "Tomates, Courgettes, Oignons, Aubergines, Poivrons",11},12{13id: 3,14title: "Vegetarian",15content: "Mozarella, Tomates, Oignons, Poivrons, Champignons, Olives",16},17{18id: 4,19title: "Alpage",20content: "Gruyère, Mozarella, Lardons, Tomates",21},22{23id: 5,24title: "Diable",25content: "Tomates, Mozarella, Chorizo piquant, Jalapenos",26},27];2829const Main = () => {30const [pizza, setPizza] = useState("");31const [description, setDescription] = useState("");32const [pizzas] = useState(defaultPizzas);3334const handleSubmit = (e: SyntheticEvent) => {35e.preventDefault();36console.log("submit:", pizza, description);37};3839const handlePizzaChange = (e: SyntheticEvent) => {40const pizzaInput = e.target as HTMLInputElement;41console.log("change in pizzaInput:", pizzaInput.value);42setPizza(pizzaInput.value);43};4445const handleDescriptionChange = (e: SyntheticEvent) => {46const descriptionInput = e.target as HTMLInputElement;47console.log("change in descriptionInput:", descriptionInput.value);48setDescription(descriptionInput.value);49};5051return (52<main>53<p>My HomePage</p>54<p>55Because we love JS, you can also click on the header to stop / start the56music ; )57</p>58<audio id="audioPlayer" controls >59<source src={sound} type="audio/mpeg" />60Your browser does not support the audio element.61</audio>62<PizzaMenu pizzas={pizzas} />63// Reste du code
L'application s'affiche comme auparavant.
Nous allons maintenant faire en sorte que lors du submit, on mette à jour la variable d'état pizzas
.
Mise à jour de l'état en React
En React, nous ne devons jamais mettre à jour l'état directement. Lors du submit, nous pourrions être tenté de faire quelque chose du genre :
tsconst newPizza = {id: nextPizzaId(pizzas),title: pizza,content: description,};pizzas.push(newPizza);setPizzas(pizzas);
👎 Si vous faites cela, ça pourrait marcher, et vous pourriez vous en sortir malgré tout.
Néanmoins, vous risquez d'avoir des soucis de debugging (vous ne pouvez pas suivre les changements d'états), d'optimisation, et même de comportement erratique...
👍 Retenez qu'en React, l'état est immuable. Si vous souhaitez le changer, vous devez chaque fois passer un nouvel objet à votre fonction mettant à jour l'état.
Par exemple, pour mettre à jour un array, vous avez deux options. Soit vous utilisez la fonction concat
qui crée un nouvel array, ajoute l'élément, et renvoie le nouvel array :
tssetPizzas(pizzas.concat(newPizza));
Soit vous utilisez le spread operator pour créer un nouvel array contenant tous les éléments de pizzas, en y ajoutant le dernier élément :
tssetPizzas([...pizzas, newPizza]);
Voici le code final du Main
dans lequel nous avons ajouté une fonction toute à la fin permettant de générer un identifiant :
tsx1import { SyntheticEvent, useState } from "react";2import sound from "../../assets/sounds/Infecticide-11-Pizza-Spinoza.mp3";3import DrinkCard from "./DrinkCard";4import DrinkMenu from "./DrinkMenu";5import "./Main.css";6import PizzaMenu from "./PizzaMenu";7import { Pizza } from "../../types";8910const defaultPizzas = [11{12id: 1,13title: "4 fromages",14content: "Gruyère, Sérac, Appenzel, Gorgonzola, Tomates",15},16{17id: 2,18title: "Vegan",19content: "Tomates, Courgettes, Oignons, Aubergines, Poivrons",20},21{22id: 3,23title: "Vegetarian",24content: "Mozarella, Tomates, Oignons, Poivrons, Champignons, Olives",25},26{27id: 4,28title: "Alpage",29content: "Gruyère, Mozarella, Lardons, Tomates",30},31{32id: 5,33title: "Diable",34content: "Tomates, Mozarella, Chorizo piquant, Jalapenos",35},36] ;3738const Main = () => {39const [pizza, setPizza] = useState("");40const [description, setDescription] = useState("");41const [pizzas, setPizzas] = useState(defaultPizzas);4243const handleSubmit = (e: SyntheticEvent) => {44e.preventDefault();45console.log("submit:", pizza, description);46const newPizza = {47id: nextPizzaId(pizzas),48title: pizza,49content: description,50};5152setPizzas([...pizzas, newPizza]);53};5455const handlePizzaChange = (e: SyntheticEvent) => {56const pizzaInput = e.target as HTMLInputElement;57console.log("change in pizzaInput:", pizzaInput.value);58setPizza(pizzaInput.value);59};6061const handleDescriptionChange = (e: SyntheticEvent) => {62const descriptionInput = e.target as HTMLInputElement;63console.log("change in descriptionInput:", descriptionInput.value);64setDescription(descriptionInput.value);65};6667return (68<main>69<p>My HomePage</p>70<p>71Because we love JS, you can also click on the header to stop / start the72music ; )73</p>74<audio id="audioPlayer" controls >75<source src={sound} type="audio/mpeg" />76Your browser does not support the audio element.77</audio>78<PizzaMenu pizzas={pizzas} />7980<div>81<br />82<form onSubmit={handleSubmit}>83<label htmlFor="pizza">Pizza</label>84<input85value={pizza}86type="text"87id="pizza"88name="pizza"89onChange={handlePizzaChange}90required91/>92<label htmlFor="description">Description</label>93<input94value={description}95type="text"96id="description"97name="description"98onChange={handleDescriptionChange}99required100/>101<button type="submit">Ajouter</button>102</form>103</div>104105<DrinkMenu title="Notre Menu de Boissons">106<DrinkCard107title="Coca-Cola"108image="https://media.istockphoto.com/id/1289738725/fr/photo/bouteille-en-plastique-de-coke-avec-la-conception-et-le-chapeau-rouges-d%C3%A9tiquette.jpg?s=1024x1024&w=is&k=20&c=HBWfROrGDTIgD6fuvTlUq6SrwWqIC35-gceDSJ8TTP8="109>110<p>Volume: 33cl</p>111<p>Prix: 2,50 €</p>112</DrinkCard>113<DrinkCard114title="Pepsi"115image="https://media.istockphoto.com/id/185268840/fr/photo/bouteille-de-cola-sur-un-fond-blanc.jpg?s=1024x1024&w=is&k=20&c=xdsxwb4bLjzuQbkT_XvVLyBZyW36GD97T1PCW0MZ4vg="116>117<p>Volume: 33cl</p>118<p>Prix: 2,50 €</p>119</DrinkCard>120<DrinkCard121title="Eau Minérale"122image="https://media.istockphoto.com/id/1397515626/fr/photo/verre-deau-gazeuse-%C3%A0-boire-isol%C3%A9.jpg?s=1024x1024&w=is&k=20&c=iEjq6OL86Li4eDG5YGO59d1O3Ga1iMVc_Kj5oeIfAqk="123>124<p>Volume: 50cl</p>125<p>Prix: 1,50 €</p>126</DrinkCard>127</DrinkMenu>128</main>129);130};131132const nextPizzaId = (pizzas: Pizza[]) => {133return pizzas.reduce((maxId, pizza) => Math.max(maxId, pizza.id), 0) + 1;134};135136export default Main;
Notons que la fonction reduce
est très intéressante :
- c'est de la programmation fonctionnelle,
reduce
reçoit une fonction en paramètre (on appelle ça une callback) - elle permet d'itérer sur tous les éléments d'une collection, en appelant la callback sur chaque élément de la collection ; chaque élément est reçu dans le deuxième argument de la callback appelé
pizza
ici ; - à chaque appel de la callback, le résultat de l'itération précédente est récupéré dans le 1er argument de la callback appelé
maxId
ici ; - à la 1ère itération, on considère la valeur
0
comme valeur précédente ; c'est d'ailleurs la valeur0
qui serait renvoyée s'il n'y a pas d'éléments dans la collection.
Exercice 2.7 : Gestion d'une collection pour l'état
Veuillez créer un nouveau projet en utilisant les technos Vite + React + TS + SWC nommé /exercises/2.7
dans votre git repo.
Veuillez créer une nouvelle application qui vous permette, dans la même page :
- d'afficher 5 de vos films préférés.
- d'ajouter un film via un formulaire.
Un film devra avoir :
- un titre
- un director
- une durée en minutes
Un film pourra avoir :
- un lien vers une image
- une description
- un budget (en million)
Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.7
Gestion d'un état partagé par plusieurs composants
Quand un état est partagé par plusieurs composants, sa gestion peut se compliquer.
Pour notre tutoriel, afin de bien structurer notre code, nous allons créer un nouveau composant AddPizza
dans /src/components/Main/AddPizza.tsx
:
tsximport { useState, SyntheticEvent } from "react";import { NewPizza } from "../../types";interface AddPizzaProps {addPizza: (pizza: NewPizza) => void;}const AddPizza = ({ addPizza }: AddPizzaProps) => {const [pizza, setPizza] = useState("");const [description, setDescription] = useState("");const handleSubmit = (e: SyntheticEvent) => {e.preventDefault();console.log("submitting pizza:", pizza, description);addPizza({ title: pizza, content: description });};const handlePizzaChange = (e: SyntheticEvent) => {const pizzaInput = e.target as HTMLInputElement;console.log("change in pizzaInput:", pizzaInput.value);setPizza(pizzaInput.value);};const handleDescriptionChange = (e: SyntheticEvent) => {const descriptionInput = e.target as HTMLInputElement;console.log("change in descriptionInput:", descriptionInput.value);setDescription(descriptionInput.value);};return (<div><form onSubmit={handleSubmit}><label htmlFor="pizza">Pizza</label><inputvalue={pizza}type="text"id="pizza"name="pizza"onChange={handlePizzaChange}required/><label htmlFor="description">Description</label><inputvalue={description}type="text"id="description"name="description"onChange={handleDescriptionChange}required/><button type="submit">Ajouter</button></form></div>);};export default AddPizza;
Nous avons créé un nouveau type NewPizza
qui est quasi identique au type Pizza
, à la différence qu'il ne contient pas d'id :
ts1interface Pizza {2id: number;3title: string;4content: string;5}67type NewPizza = Omit<Pizza, "id">;89export type { Pizza, NewPizza };
Le composant AddPizza
reçoit de son parent la callback qui permet de mettre à jour l'état géré par le parent !
💭 Nous pouvons maintenant bien assimiler comment un composant "enfant" peut renvoyer de l'information à son parent. C'est via la callback que l'enfant reçoit, lorsqu'il l'appelle, qu'il passera en paramètre ses données. Ici, l'enfant passe comme info au parent une nouvelle pizza : addPizza({ title: pizza, content: description });
Il est à noter que comme l'enfant n'a pas accès à toutes les pizzas et leur identifiants, c'est le parent qui devra générer un identifiant.
Ainsi, le composant Main
est simplifié en faisant appel à AddPizza
:
tsx1import { useState } from "react";2import sound from "../../assets/sounds/Infecticide-11-Pizza-Spinoza.mp3";3import DrinkCard from "./DrinkCard";4import DrinkMenu from "./DrinkMenu";5import "./Main.css";6import PizzaMenu from "./PizzaMenu";7import { NewPizza, Pizza } from "../../types";8import AddPizza from "./AddPizza";91011const defaultPizzas = [12{13id: 1,14title: "4 fromages",15content: "Gruyère, Sérac, Appenzel, Gorgonzola, Tomates",16},17{18id: 2,19title: "Vegan",20content: "Tomates, Courgettes, Oignons, Aubergines, Poivrons",21},22{23id: 3,24title: "Vegetarian",25content: "Mozarella, Tomates, Oignons, Poivrons, Champignons, Olives",26},27{28id: 4,29title: "Alpage",30content: "Gruyère, Mozarella, Lardons, Tomates",31},32{33id: 5,34title: "Diable",35content: "Tomates, Mozarella, Chorizo piquant, Jalapenos",36},37] ;3839const Main = () => {4041const [pizzas, setPizzas] = useState(defaultPizzas);424344const addPizza = (newPizza:NewPizza) => {45const pizzaAdded = { ...newPizza, id: nextPizzaId(pizzas) };46setPizzas([...pizzas, pizzaAdded]);47};484950return (51<main>52<p>My HomePage</p>53<p>54Because we love JS, you can also click on the header to stop / start the55music ; )56</p>57<audio id="audioPlayer" controls >58<source src={sound} type="audio/mpeg" />59Your browser does not support the audio element.60</audio>61<PizzaMenu pizzas={pizzas} />6263<div>64<br />65<AddPizza addPizza={addPizza} />66</div>6768<DrinkMenu title="Notre Menu de Boissons">69<DrinkCard70title="Coca-Cola"71image="https://media.istockphoto.com/id/1289738725/fr/photo/bouteille-en-plastique-de-coke-avec-la-conception-et-le-chapeau-rouges-d%C3%A9tiquette.jpg?s=1024x1024&w=is&k=20&c=HBWfROrGDTIgD6fuvTlUq6SrwWqIC35-gceDSJ8TTP8="72>73<p>Volume: 33cl</p>74<p>Prix: 2,50 €</p>75</DrinkCard>76<DrinkCard77title="Pepsi"78image="https://media.istockphoto.com/id/185268840/fr/photo/bouteille-de-cola-sur-un-fond-blanc.jpg?s=1024x1024&w=is&k=20&c=xdsxwb4bLjzuQbkT_XvVLyBZyW36GD97T1PCW0MZ4vg="79>80<p>Volume: 33cl</p>81<p>Prix: 2,50 €</p>82</DrinkCard>83<DrinkCard84title="Eau Minérale"85image="https://media.istockphoto.com/id/1397515626/fr/photo/verre-deau-gazeuse-%C3%A0-boire-isol%C3%A9.jpg?s=1024x1024&w=is&k=20&c=iEjq6OL86Li4eDG5YGO59d1O3Ga1iMVc_Kj5oeIfAqk="86>87<p>Volume: 50cl</p>88<p>Prix: 1,50 €</p>89</DrinkCard>90</DrinkMenu>91</main>92);93};9495const nextPizzaId = (pizzas: Pizza[]) => {96return pizzas.reduce((maxId, pizza) => Math.max(maxId, pizza.id), 0) + 1;97};9899export default Main;
💭 Vous devriez à présent avoir les réponses à ces questions :
- Comment passer de l'info d'un parent vers ses enfants ?
- Comment passer de l'info d'un enfant vers un ancêtre ?
- Comment passer de l'info d'un sibling (frère ou sœur) vers un autre sibling ?
Si nécessaire, vous pouvez trouver le code associé à ce tutoriel ici : medium-state.
Debugging d'une application React
Introduction
💭 Qui est votre meilleur ami ?
Il est possible qu'à ce stade-ci, vous ignorez une des bonnes réponses, car pour les développeurs, le debugger est leur meilleur ami !
Le debugger est toujours là pour vous, prêt à vous faire voyager pas à pas dans votre code, à vous donner des pistes dans les moments difficiles, sans imposer de solutions, il vous offre une liberté totale ! Et il acceptera toujours votre code tel qu'il est, sous réserve bien sûr que celui-ci compile. C'est exactement ce que l'on attend d'un ami 😁.
Debugging sous Chrome
Pour accéder aux outils de debugging sous Chrome :
- Soit clic droit sur votre page Web,
Inspect
. - Soit
F12
. - Clic sur le tab
Sources
. - Dans
localhost:5173
(ou un autre port en fonction du port associé à votre app,5173
est le port par défaut d'une application exécutée parVite
), danssrc
, vous trouverez vos fichiers.tsx
. Il y a chaque fois deux versions d'un même fichier ; celle qui vous intéresse est celle dont le nom de fichier est écrit en italique : c'est un map de vos sources contenant le code TSX / TS.
Pour déboguer un fichier .tsx
ou .ts
:
-
Ajout de breakpoint : clic sur le numéro de la ligne à gauche du code.
-
Utilisation des flèches pour exécuter et naviguer dans le code :
Step over
: pour aller à la ligne de code suivante.Step into
: pour entrer dans une fonction.Resume
: pour aller au prochain breakpoint, est souvent utilisé quand on veut avancer plus vite dans du code contenant de nombreuses lignes.
Debugging dans VS code
Veuillez ouvrir votre application dans VS Code en tant que Workspace. Pour ce faire :
File
,Open Folder...
et sélectionnez le répertoire de votre application Vite + React + TS.La toute première fois, vous devez créer une configuration pour votre debugger :
- Cliquez sur `Run and Debug.
- Cliquez sur
create a launch.json file
. - Sélectionnez
Web App (Chrome)
.
Vous obtenez une configuration dans
.vscode/launch.json
. Veuillez changer le port de l'url vers le port par défaut deVite
(5173
):
json{// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"type": "chrome","request": "launch","name": "Launch Chrome against localhost","url": "http://localhost:5173","webRoot": "${workspaceFolder}"}]}
D'abord lancez votre application à l'aide de la commande habituelle : npm run dev
.
Maintenant que votre application tourne, vous allez lui attacher le debugger. Il suffit de cliquer sur F5
pour lancer le debugger.
🍬 Challenge optionnel 2.8 : gestion d'un état partagé
Si vous le souhaitez, et que vous vous sentez avancé dans ce cours, vous pourriez découvrir des concepts par vous-même à travers ce challenge. Vous pouvez créer un nouveau projet en copiant le code du tutoriel nommé medium-state
en tant que répertoire /exercises/2.8
dans votre git repo.
Vous avez remarqué que dans le composant Main
, il est écrit : "Because we love JS, you can also click on the header to stop / start the music ; )"
Vous pourriez choisir cette mission : à l'aide de JS/TS, veuillez faire en sorte que l'on puisse cliquer sur le Header
et que cela démarre ou stop la musique de l'élément <audio>
présent dans le main.
🤝 Tips
- Utilisation du Hook
useRef
pour obtenir une référence directe et persistante à l'élément<audio>
, qui peut être mutée, ce qui permet d'interagir avec cet élément DOM de manière impérative, par exemple pour appeler la méthodeplay
oupause
. L'avantage de cette méthode c'est qu'elle ne provoque pas de re-render du composant quand il est mis à jour (à l'inverse de si l'on faisait de la programmation old-school avecdocument.getElementById
pour récupérer une référence à<audio>
). - Utilisation du Hook
useEffect
pour réaliser une action à chaque fois que l'on a une action à réaliser parce qu'il y aurait eu un clic dans le Header. Dans ce cas-ci, il faudrait bien comprendre leuseEffect
pour l'associer au changement d'une variable d'état (qui représente s'il y a besoin d'une action à faire à cause d'un clic dans le header).
Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.8+
.