Apprends à rendre des tableaux de données sous forme de listes dans React en utilisant map(), comprends la prop key et pourquoi elle est importante pour les performances et l'exactitude.
Dans React, tu dois fréquemment afficher une liste d'éléments — une liste d'utilisateurs, de produits, de messages, ou toute collection de données. Le modèle standard est d'utiliser la méthode Array.map() de JavaScript pour transformer un tableau de données en un tableau d'éléments JSX.
La méthode map() crée un nouveau tableau en appelant une fonction sur chaque élément du tableau original. Quand tu retournes du JSX depuis cette fonction, React rend chaque élément :
function FruitList() {
const fruits = ['Apple', 'Banana', 'Cherry'];
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}Remarque comment l'appel à map() est enveloppé dans des accolades {} à l'intérieur du JSX. C'est parce que le JSX utilise les accolades pour intégrer des expressions JavaScript. React peut rendre des tableaux d'éléments directement, donc map() retournant un tableau d'éléments <li> fonctionne parfaitement.
Tu peux aussi faire un map sur des tableaux d'objets :
function UserList() {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
];
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Le modèle est toujours le même : prends un tableau, appelle map(), retourne du JSX pour chaque élément.
Tu as peut-être remarqué l'attribut key sur chaque élément de liste. La prop key est un attribut spécial que React utilise pour identifier quels éléments d'une liste ont changé, ont été ajoutés ou ont été supprimés.
Quand React re-rend une liste, il doit faire correspondre les éléments du rendu précédent avec les éléments du nouveau rendu. Sans keys, React devrait comparer les éléments uniquement par leur position (index), ce qui est lent et peut causer des bugs.
Règles pour les keys :
Les keys doivent être uniques parmi les frères et sœurs. Chaque élément dans la même liste doit avoir une key différente. Les keys n'ont pas besoin d'être globalement uniques — juste uniques au sein de cette liste spécifique.
Les keys doivent être stables. Le même élément de données devrait toujours produire la même key, d'un rendu à l'autre.
Les keys ne doivent pas changer. Si une key change entre les rendus, React détruira l'ancien composant et en créera un nouveau à partir de zéro.
// Bon : utiliser un id unique et stable
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
// Mauvais : utiliser Math.random() — change à chaque rendu !
{users.map((user) => (
<li key={Math.random()}>{user.name}</li>
))}Que se passe-t-il sans keys ? React t'avertira dans la console : Each child in a list should have a unique "key" prop. Plus important encore, sans keys appropriées, React peut réutiliser incorrectement des éléments DOM quand les éléments sont réordonnés, entraînant des bugs visuels — par exemple, des valeurs d'input apparaissant dans le mauvais élément de liste.
Pourquoi ne pas utiliser l'index du tableau comme key ? Utiliser l'index (key={index}) est une solution de repli courante, mais cela peut causer des problèmes quand les éléments sont réordonnés, insérés ou supprimés. L'index d'un élément change quand la liste change, ce qui casse la capacité de React à suivre quel élément est lequel. Utilise des keys basées sur l'index seulement en dernier recours quand tu n'as pas d'identifiant stable et que la liste est statique (jamais réordonnée ou filtrée).
Quand tes éléments de liste deviennent complexes, c'est une bonne pratique d'extraire chaque élément dans son propre composant. Cela garde ton code propre et rend chaque élément plus facile à gérer.
function TodoItem({ todo }) {
return (
<li style={{ color: todo.done ? 'gray' : 'black' }}>
{todo.done ? '✓ ' : '○ '}
{todo.text}
</li>
);
}
function TodoList() {
const todos = [
{ id: 1, text: 'Learn React', done: true },
{ id: 2, text: 'Build a project', done: false },
{ id: 3, text: 'Deploy to production', done: false },
];
return (
<ul>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}Important : La prop key est placée sur le composant dans l'appel à map(), pas à l'intérieur du composant lui-même. React utilise key en interne et ne la passe pas comme prop à ton composant. Si tu as besoin de l'id à l'intérieur du composant, passe-le comme une prop séparée :
{todos.map((todo) => (
<TodoItem key={todo.id} id={todo.id} todo={todo} />
))}Extraire les éléments de liste dans des composants facilite aussi l'ajout de fonctionnalités comme des gestionnaires de clic, des effets de survol ou des animations sur des éléments individuels sans encombrer le composant parent.
Choisir la bonne key est important. Voici un guide pratique :
Meilleur choix : Utiliser un id unique depuis tes données. La plupart des données du monde réel — provenant de bases de données, d'APIs ou d'entrées utilisateur — viennent avec un identifiant unique. C'est toujours le meilleur choix pour une key :
// Données depuis une API
const posts = [
{ id: 'abc123', title: 'First Post' },
{ id: 'def456', title: 'Second Post' },
];
posts.map((post) => <Article key={post.id} post={post} />);Acceptable : Utiliser une propriété unique et stable.
S'il n'y a pas de champ id mais qu'une autre propriété est unique et stable (comme un nom d'utilisateur ou un email), tu peux l'utiliser :
users.map((user) => <UserCard key={user.email} user={user} />);Dernier recours : Utiliser l'index. Utilise l'index du tableau seulement quand la liste est statique et ne sera jamais réordonnée, filtrée, ou n'aura pas d'éléments ajoutés/supprimés :
// Sûr uniquement pour des listes purement statiques
const colors = ['Red', 'Green', 'Blue'];
colors.map((color, index) => <span key={index}>{color}</span>);Générer un id si nécessaire. Si tu crées des données côté client (par exemple, une app de todos), génère un id unique quand l'élément est créé — pas pendant le rendu :
// Générer l'id lors de la création de l'élément, pas dans map()
function addTodo(text) {
const newTodo = {
id: crypto.randomUUID(), // Générer une seule fois lors de la création
text,
done: false,
};
setTodos([...todos, newTodo]);
}Tu peux rendre des listes imbriquées en imbriquant des appels à map(). C'est courant lors de l'affichage de données hiérarchiques :
function CategoryList() {
const categories = [
{
name: 'Fruits',
items: ['Apple', 'Banana', 'Cherry'],
},
{
name: 'Vegetables',
items: ['Carrot', 'Broccoli', 'Spinach'],
},
];
return (
<div>
{categories.map((category) => (
<div key={category.name}>
<h3>{category.name}</h3>
<ul>
{category.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
))}
</div>
);
}Chaque niveau d'imbrication a besoin de ses propres keys, mais les keys doivent seulement être uniques parmi leurs frères et sœurs — pas à travers différentes listes.
Tu peux aussi filtrer avant de mapper, pour afficher seulement certains éléments :
function ActiveUsers({ users }) {
return (
<ul>
{users
.filter((user) => user.isActive)
.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Un modèle courant est d'enchaîner filter() et map() ensemble. Tu peux aussi combiner cela avec sort() pour contrôler l'ordre d'affichage :
{users
.filter((u) => u.isActive)
.sort((a, b) => a.name.localeCompare(b.name))
.map((u) => <UserCard key={u.id} user={u} />)}<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState } = React;
function App() {
const [items, setItems] = useState([
{ id: 1, name: 'Learn HTML', done: true },
{ id: 2, name: 'Learn CSS', done: true },
{ id: 3, name: 'Learn JavaScript', done: true },
{ id: 4, name: 'Learn React', done: false },
]);
return (
<div>
<h2>Learning Path</h2>
<ul>
{items.map((item) => (
<li key={item.id} style={{
textDecoration: item.done ? 'line-through' : 'none',
color: item.done ? 'gray' : 'black',
}}>
{item.name}
</li>
))}
</ul>
<p>{items.filter((i) => i.done).length} of {items.length} completed</p>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>Pourquoi devrais-tu éviter d'utiliser l'index du tableau comme key quand les éléments de liste peuvent être réordonnés ?