Premier projet React pour débutant : du JavaScript au panier
Premier projet React pas à pas : un panier avec quantités, total et recherche. JSX, components, props, map, key et useState, avec le code complet.
Aussi en : العربية

Pour un premier projet React, le plus efficace est de construire une petite interface complète plutôt qu'une suite d'exemples isolés. Dans le cours E27, on construit la boutique de Hamza : des cartes produits, un panier partagé avec quantités, un total calculé et une recherche. En chemin, on voit JSX, les components, les props, map et key, puis useState et les événements.
Pourquoi passer de JavaScript à React ?
Le cours commence par un essai volontaire en JavaScript « vanilla » : Bob clique sur « Casque », le compteur passe à 1… et le total reste à 0 DH. La cause : on a mis à jour le compteur dans le DOM, mais oublié de mettre à jour le total. JavaScript sait parfaitement le faire ; le problème est qu'à chaque changement, il faut se souvenir de tout ce qui doit bouger à l'écran.
L'idée de React : on change les données, on décrit ce qui doit s'afficher, et React se charge de mettre l'interface à jour.
Récupérer le projet
Le code de départ et la solution sont publics :
git clone https://github.com/Laraibi/lara-academy-react-fundamentals.git
cd lara-academy-react-fundamentals
npm ci
npm run devOuvre l'adresse affichée par Vite (par défaut http://127.0.0.1:5173). La solution du cours est sur la branche solution.
JSX : des données dans l'interface
App est un component : une fonction qui renvoie une description de l'interface. Cette écriture qui ressemble à du HTML s'appelle JSX. Entre accolades, on place une expression JavaScript, par exemple {product.price}. Change le prix de 150 à 160, sauvegarde, et la page suit. Deux détails : en JSX on écrit className au lieu de class, et main.jsx relie App à sa place dans la page.
Components et props : réutiliser une carte
Plutôt que de recopier la carte du casque pour le clavier, on l'extrait dans un component ProductCard qui reçoit ses données par props, comme les arguments d'une fonction :
export default function ProductCard({ product, onAdd }) {
return (
<article>
<h2>{product.name}</h2>
<p>{product.price} DH</p>
<button onClick={() => onAdd(product)}>Ajouter {product.name}</button>
</article>
);
}(Classes Tailwind retirées pour la lisibilité.) Les accolades dans le paramètre extraient product de l'objet reçu. Un même component, des données différentes. Et une règle : on lit les props telles qu'elles arrivent ; pour un changement, on le demande au component responsable.
Afficher une liste avec map et key
Avec vingt produits, on n'écrit pas vingt cartes. On range les produits dans un tableau et on utilise map, qui renvoie un nouveau tableau avec une carte par produit :
export const PRODUCTS = [
{ id: 'casque', name: 'Casque', price: 150 },
{ id: 'clavier', name: 'Clavier', price: 200 },
{ id: 'souris', name: 'Souris', price: 80 },
];{visibleProducts.map(product => (
<ProductCard key={product.id} product={product} onAdd={handleAdd} />
))}La key permet à React de distinguer les éléments de la liste. On utilise un id stable pour chaque produit : pas un nombre aléatoire, et pas l'index si la liste peut changer.
Pourquoi let count ne rafraîchit pas l'écran ?
Essai isolé : un bouton qui compte les clics avec let count = 0. Au clic, la console affiche 1, mais l'écran reste à 0. La fonction a bien tourné, mais changer une variable ordinaire ne demande pas à React de recalculer l'interface. En plus, cette variable locale repart de zéro chaque fois que le component s'exécute.
useState et les événements
useState renvoie deux choses : la valeur, et une fonction pour demander son changement.
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(previous => previous + 1);
console.log('Valeur dans ce rendu :', count);
}
return <button onClick={handleClick}>Clics : {count}</button>;
}Ce que le cours fait observer :
onClickreçoit une fonction ; on ne l'appelle pas au milieu du rendu.- Après
setCount, React rappelle le component, calcule le nouvel affichage et ne modifie dans le DOM que ce qui a changé. - Juste après
setCount,countgarde la valeur de ce rendu : la nouvelle valeur apparaît au rendu suivant (d'où leconsole.log). useStates'appelle en haut du component, jamais dans une condition ou dans la fonction du clic.
Où mettre le panier partagé ?
Si chaque carte a son propre state, chacune compte de son côté. Or les cartes demandent l'ajout et le panier affiche le résultat : leur parent commun est App. On y place le state du panier, on descend les données par props, et on passe une fonction onAdd aux cartes. onAdd n'est pas un événement React : c'est une prop que nous avons nommée.
const [cart, setCart] = useState([]);
function handleAdd(product) {
setCart(previous => addItem(previous, product));
}
function handleRemove(id) {
setCart(previous => removeOne(previous, id));
}Ajouter un produit sans modifier le tableau
On ne fait pas push sur le tableau du state : on en crée un nouveau.
export function addItem(cart, product) {
const exists = cart.some(item => item.id === product.id);
if (exists) {
return cart.map(item => item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item);
}
return [...cart, { ...product, quantity: 1 }];
}somerépond à la question : un élément a-t-il déjà cet id ?- Si oui,
maprenvoie chaque élément tel quel, sauf celui-là, recréé avec une quantité + 1. - Sinon, le spread
...recopie les éléments existants et ajoute le produit avec une quantité de 1.
Test du cours : deux fois Casque, une fois Souris → 3 unités, 2 lignes, 380 DH.
Retirer une unité et calculer le total
export function removeOne(cart, id) {
return cart
.map(item => item.id === id
? { ...item, quantity: item.quantity - 1 }
: item)
.filter(item => item.quantity > 0);
}
export function getTotal(cart) {
return cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
}On diminue la quantité, puis filter ne garde que les lignes encore au-dessus de zéro. Le cours conseille d'écrire sa prévision avant de tester : en retirant les unités une à une, le total passe de 380 à 230, puis 80, puis 0.
Le total n'a pas besoin de son propre state : il se calcule depuis le panier à chaque rendu. reduce part de 0, donc un panier vide donne 0.
Une recherche qui ne vide pas le panier
La seule nouvelle information à mémoriser est le texte tapé, pas la liste filtrée :
const [query, setQuery] = useState('');
const visibleProducts = PRODUCTS.filter(product =>
product.name.toLowerCase().includes(query.trim().toLowerCase())
);
<input type="search" value={query} onChange={event => setQuery(event.target.value)} />value lit le state, onChange demande sa mise à jour : c'est un champ contrôlé. Une recherche sans résultat affiche « Aucun produit trouvé. », et le casque déjà ajouté reste dans le panier, car la recherche ne change que ce qui s'affiche dans le catalogue. Aucun useEffect n'a été nécessaire pour la liste ni pour le total.
Questions fréquentes
Quels prérequis avant un premier projet React ?
Un peu de HTML et de CSS, et les fonctions et tableaux en JavaScript. map et filter sont réexpliqués au moment où le projet en a besoin.
Quelle différence entre props et state ?
Les props sont des informations qui arrivent dans un component ; le state est une information qu'il garde et dont on peut demander le changement avec la fonction renvoyée par useState.
Faut-il useEffect pour calculer le total ou filtrer la liste ?
Non. Le total et la liste filtrée se calculent pendant le rendu, à partir du panier et du texte de recherche. Le projet n'utilise aucun useEffect.
Pourquoi le panier n'est-il pas conservé après rechargement ?
Le projet ne contient ni stockage ni API : le state vit en mémoire. Le lien avec une API arrive dans l'épisode suivant.
À retenir
- props : les informations qui arrivent dans un component. state : une information qu'il garde et dont on peut demander le changement.
- Placer le state dans le parent commun des components qui en ont besoin.
- Ne jamais modifier un tableau du state : créer un nouveau tableau (
...,map,filter). - Ce qui se calcule (total, liste filtrée) ne va pas dans un state.
- Une
keystable par élément de liste.
Regarder le cours
Le cours complet (13 min 34, en darija) est sur sa page : De JavaScript à React : ton premier panier (E27).
- 00:00 Pourquoi le total ne suit pas le clic ?
- 00:49 Le projet et le starter React + Tailwind
- 01:49 JSX : des données dans l'interface
- 03:35 Components et props
- 04:33 Afficher une liste avec map et key
- 05:23 Pourquoi let count ne rafraîchit pas l'écran
- 06:08 useState et les événements
- 07:15 Partager le panier dans le parent
- 08:26 Ajouter un produit sans modifier le tableau
- 09:40 Retirer une unité et calculer le total
- 10:50 Recherche contrôlée et collaboration Git
- 12:42 Récapitulatif et défi à refaire
Pour aller plus loin
- Les tableaux en JavaScript avant React : types, map, filter et reduce (E22).
- L'épisode suivant : React consomme une API Node : CRUD et login JWT.
- Le back-end utilisé ensuite : Node.js et Express pour un développeur PHP.
- Le parcours : Développement Web Full Stack.
- Choisir sa formation : YouCode, 1337, OFPPT et bootcamps comparés.
Sources
Quiz : 3 questions pour vérifier
Rien n’est envoyé : tes réponses restent dans ton navigateur.
Rédigé à partir du cours vidéo et de sa transcription, avec l’aide d’outils d’IA, relu et vérifié par Mehdi Laraibi le 4 novembre 2026.



