useState()
Props vs State
- Props fait référence aux propriétés transmises à un composant pour qu’il fonctionne correctement, de la même manière qu’une fonction reçoit des paramètres. Un composant recevant des props n’est pas autorisé à les modifier (i.e. il sont immuables).
function addTwoNumbers(a, b) {
// NE FAITES PAS ÇA
a = 42
return a + b
}
console.log(addTwoNumbers(1, 2))function Navbar(props) {
// NE FAITES PAS ÇA
props.logoIcon = "some-other-icon.png"
}
<Navbar logoIcon="spatula.png" />- State fait référence aux valeurs gérées par le composant, comme les variables déclarées à l’intérieur d’une fonction. Chaque fois que vous avez des valeurs changeantes qui doivent être sauvegardées/affichées, vous utiliserez probablement un state.
Gestion de variables avec state
Prenons un exemple pour comprendre l’utilité des states en React :
export default function App() {
let state = "Oui"
function handleClick() {
state = "Bien sûr"
}
return (
<main>
<h1 className="title">Est-il important de connaître les states?</h1>
<button onClick={handleClick} className="value">{state}</button>
</main>
)
}* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background-color: #262626;
color: #D9D9D9;
padding: 20px;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
main {
display: flex;
flex-direction: column;
align-items: center;
}
main > .title {
text-align: center;
}
main > .value {
background-color: white;
border: none;
border-radius: 50%;
height: 100px;
width: 100px;
display: flex;
justify-content: center;
align-items: center;
color: #262626;
font-size: 2rem;
font-weight: bold;
cursor: pointer;
}Lorsqu’on appuie sur le bouton, une fonction handleClick() sensée modifier le texte du bouton est appelée.
Copiez ce code et essayez de cliquer sur le bouton. Que remarquez-vous ?
Rien ne change sur la page ! Le simple fait de modifier une variable locale ne va pas inciter React à réexécuter notre composant. React ne va pas réagir au changement d’état et afficher la valeur mise à jour. Pour que cela puisse fonctionner, nous devons utiliser une fonction fournie par React : React.useState() :
import { useState } from "react"
export default function App() {
const state = useState("Oui")
return (
<main>
<h1 className="title">Est-il important de connaître les states?</h1>
<button onClick={handleClick} className="value">{state[0]}</button>
</main>
)
}Cette fonction prend en paramètre la valeur initiale de la variable et retourne un tableau avec deux éléments :
- Le premier élément est la valeur de la variable ;
- Le deuxième est une fonction qui nous permettra de modifier la valeur de cette variable.
Destructurer le retour de useState
Afin de mieux nommer les éléments du tableau que retourne useState() (et rendre notre code plus lisible), nous pouvons destructurer sa valeur de retour :
import React from "react"
export default function App() {
const [isImportant, setIsImportant] = React.useState("Oui")
return (
<main>
<h1 className="title">Est-il important de connaître les states?</h1>
<button className="value">{isImportant}</button>
</main>
)
}Note
Par convention, on donne à la variable un nom qui décrit la valeur stockée, et on nomme la fonction setNomDeLaVariable
Le code du bouton peut donc être modifié de la façon suivante :
import { useState } from "react"
export default function App() {
const [isImportant, setIsImportant] = useState("Oui")
function handleClick() {
setIsImportant("Bien sûr")
}
return (
<main>
<h1 className="title">Est-il important de connaître les states?</h1>
<button onClick={handleClick} className="value">{isImportant}</button>
</main>
)
}Exercice
Pour le code ci-dessous :
- Créez un state pour suivre la valeur de la variable
count(valeur initiale à 0) - Créez une fonction
addqui sera appelée lorsqu’on appuie sur le bouton+ - Réfléchissez à un moyen d’ajouter 1 à
countà chaque fois qu’on appuie sur le bouton+ - Faites la même chose pour le bouton
-
import { useState } from "react"
export default function App() {
return (
<main className="container">
<h1>
Combien de fois mon enseignant va-t-il dire le mot state dans ce chapitre ?
</h1>
<div className="counter">
<button
className="minus" aria-label="Decrease count">–
</button>
<h2 className="count">0</h2>
<button className="plus" aria-label="Increase count">+</button>
</div>
</main>
)
}* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background-color: #262626;
color: #D9D9D9;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.container {
display: flex;
flex-direction: column;
}
.container > h1 {
font-size: 1.5rem;
margin-top: 0;
}
.counter {
display: flex;
align-items: flex-end;
align-self: center;
margin-top: 40px;
}
.counter > button {
height: 50px;
width: 50px;
border-radius: 50%;
border: none;
cursor: pointer;
background-color: #737373;
color: #D9D9D9;
font-size: 1.5rem;
}
.counter > button:hover {
background-color: #404040;
color: #D9D9D9;
}
.count {
background-color: white;
height: 100px;
width: 100px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: #262626;
margin-block: 0 10px;
font-size: 2rem;
}
.plus {
margin-left: -20px;
}
.minus {
margin-right: -20px;
z-index: 1;
}Mettre à jour le state avec une fonction callback
Si vous avez besoin de l’ancienne valeur du state pour déterminer la nouvelle, il est fortement conseillé d’utiliser une fonction de rappel (callback function) au lieu d’utiliser directement la valeur du state. Cette fonction callback recevra l’ancienne valeur en paramètre. Vous pourrez ensuite l’utiliser pour déterminer la nouvelle valeur :
import { useState } from "react"
export default function App() {
function add() {
setCount(prevCount => prevCount + 1)
}
function subtract() {
setCount(prevCount => prevCount - 1)
}
return (
<main className="container">
<h1>
Combien de fois mon enseignant va-t-il dire le mot state dans ce chapitre ?
</h1>
<div className="counter">
<button className="minus" onClick={subtract} aria-label="Decrease count">–</button>
<h2 className="count">0</h2>
<button className="plus" onClick={add} aria-label="Increase count">+</button>
</div>
</main>
)
}Exercice
En utilisant l’opérateur ternaire et les states, faites en sorte que lorsqu’on clique sur le bouton, la valeur change à “Non” si elle est à “Oui” et à “Oui” si elle est à “Non”.
export default function App() {
return (
<main>
<h1 className="title">Est-ce que je sors ce soir?</h1>
<button className="value">Oui</button>
</main>
)
}* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background-color: #262626;
color: #D9D9D9;
padding: 20px;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
main {
display: flex;
flex-direction: column;
align-items: center;
}
main > .title {
text-align: center;
}
main > .value {
background-color: white;
border: none;
border-radius: 50%;
height: 100px;
width: 100px;
display: flex;
justify-content: center;
align-items: center;
color: #262626;
font-size: 2rem;
font-weight: bold;
cursor: pointer;
}Manipulation de states complexes
Exercice 1 - Tableaux
- Changez le code ci-dessous pour utiliser un tableau initialisé avec
useState()à la place du tableauthingsElements. Initialisez le state avec un tableau vide ([]) - Modifiez la fonction
addFavoriteThing()pour qu’à chaque fois qu’on appuie sur le boutonAjouter, la fonction ajoute un nouvel élément “Test” à la page
- Maintenant, faites en sorte qu’à chaque fois qu’on appuie sur le bouton, un nouvel élément du tableau
allFavoriteThingsest affiché.
import { useState } from "react"
export default function App() {
const myFavoriteThings = []
const allFavoriteThings = ["💦🌹", "😺", "💡🫖", "🔥🧤", "🟤🎁",
"🐴", "🍎🥧", "🚪🔔", "🛷🔔", "🥩🍝"]
const thingsElements = myFavoriteThings.map(thing => <p key={thing}>{thing}</p>)
function addFavoriteThing() {
myFavoriteThings.push("Test")
}
return (
<main>
<button onClick={addFavoriteThing}>Ajouter</button>
<section aria-live="polite">
{thingsElements}
</section>
</main>
)
}* {
box-sizing: border-box;
}
body {
background-color: #70B85D;
color: white;
padding: 20px;
font-size: 1.3rem;
}
main {
display: flex;
flex-direction: column;
align-items: center;
}
button {
width: 100%;
max-width: 300px;
background-color: transparent;
padding: 1rem;
color: white;
border: 2px solid white;
border-radius: 50px;
cursor: pointer;
font-family: 'Karla', sans-serif;
margin-bottom: 20px;
}
button:hover {
background-color: #FFFEF1;
color: #2C5E2E;
}
button:focus {
outline: 0;
}
p {
margin-block: 10px;
font-size: 2rem;
}Exercice 2 - Objets
Pour les questions suivantes, vous pouvez utiliser les images user.png, star-empty.png et star-filled.png
Remplissez les valeurs du markup avec les propriétés de l’objet/state
contact.Utilisez l’opérateur ternaire pour déterminer quelle image de l’étoile (remplie ou vide) devrait être utilisée, basé sur la valeur de
contact.isFavorite.Modifiez les choses suivantes :
aria-presseddoit être égale à la valeur decontact.isFavoritearia-labeldevrait changer de valeur à “Supprimer des favoris” sicontact.isFavoriteest àtrue.altdevrait être égal àicône étoile rempliequand elle est remplie, eticône étoile videquand elle est vide
Modifiez la valeur de
contact.isFavoritelorsqu’on appuie sur l’étoile. Sa valeur doit passer àtruesi elle était àfalseetfalsesi elle était àtrue.
import { useState } from "react"
import avatar from "./images/user.png"
import starFilled from "./images/star-filled.png"
import starEmpty from "./images/star-empty.png"
export default function App() {
const [contact, setContact] = useState({
firstName: "John",
lastName: "Doe",
phone: "+1 (514) 555-1212",
email: "itsmyrealname@example.com",
isFavorite: false
})
function toggleFavorite() {
console.log("Toggle Favorite")
}
return (
<main>
<article className="card">
<img
src={avatar}
className="avatar"
alt="Photo de profil de John Doe"
/>
<div className="info">
<button
onClick={toggleFavorite}
aria-pressed={false}
className="favorite-button"
>
<img
src={starEmpty}
alt="icône étoile vide"
className="favorite"
/>
</button>
<h2 className="name">
John Doe
</h2>
<p className="contact">+1 (212) 555-1212</p>
<p className="contact">itsmyrealname@example.com</p>
</div>
</article>
</main>
)
}* {
box-sizing: border-box;
}
body {
background-color: #0C4A6E;
margin: 0;
font-family: "Inter", sans-serif;
}
main {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.card {
background-color: white;
width: 200px;
border: 1px solid lightgray;
border-radius: 10px;
height: 350px;
}
.card .avatar {
width: 100%;
padding: 10%;
padding-bottom: 0;
}
.card .name {
margin-block: 13px;
color: #333333;
}
.card .info {
padding: 10px;
}
.card .favorite {
width: 25px;
cursor: pointer;
}
.card .contact {
font-size: 0.75rem;
color: gray;
margin-block: 7px;
}
.card .favorite-button {
border: none;
background: transparent;
}
.card .favorite-button:active {
transform: none;
box-shadow: none;
}