Pourquoi Migrer de React Class vers React Hooks ?
La transition depuis les classes React à React Hooks est une étape cruciale pour beaucoup de développeurs JavaScript, notamment ceux qui travaillent avec des projets plus récents ou nécessitant de nouvelles fonctionnalités. Les hooks ont été introduits en 2018 et offrent une nouvelle façon de gérer le state et d'autres aspects de la logique dans les composants React. Ils sont conçus pour rendre le code plus simple, plus réutilisable et plus facile à comprendre.
Contexte réel : Pourquoi un dev a besoin de ca au quotidien ?
En raison des avantages que les hooks offrent en termes de lisibilité, de maintenabilité et de performance, de nombreux développeurs ont décidé de migrer leurs projets existants vers le modèle fonctionnel avec React Hooks. Par exemple, dans un projet de gestion de tâches, utiliser un hook comme useState pour gérer l'état des tâches et useEffect pour effectuer des opérations asynchrones permet une meilleure organisation du code et une meilleure séparation des responsabilités.
Un cas d'utilisation concret en 2-3 phrases
Imaginez que vous développez une application de notification. Chaque utilisateur peut avoir plusieurs notifications qui peuvent être marquées comme lues ou non. En utilisant les hooks, vous pouvez gérer l'état des notifications et effectuer des mises à jour asynchrones pour marquer les notifications comme lues, tout en restant dans le modèle fonctionnel de React.
Prerequis
Pour suivre ce tutoriel, vous aurez besoin des connaissances suivantes :
- React : Connaissance avancée des concepts de base de React.
- JavaScript ES6+ : Familiarité avec les fonctions fléchées, les promesses, etc.
- Concepts React Fonctionnels : Comprendre les concepts comme
useState,useEffectetuseContext.
Les outils suivants doivent être installés sur votre système :
- Node.js : Version recommandée est la dernière LTS (Long-Term Support) version. Vous pouvez l'installer depuis nodejs.org.
- npm/yarn : Gestionnaire de paquets pour installer les dépendances.
Concepts fondamentaux
useState
useState est un hook qui permet de ajouter du state local dans un composant fonctionnel. Il retourne une paire: l'état actuel et une fonction à appeler pour le mettre à jour.
import React, { useState } from 'react';
function Example() {
// Déclarez une nouvelle variable d'état qui est appelée "count"
const [count, setCount] = useState(0);
return (
<div>
<p>Vous avez cliqué {count} fois</p>
<button onClick={() => setCount(count + 1)}>
Cliquez-moi
</button>
</div>
);
}
useEffect
useEffect est un hook qui permet d'effectuer des effets secondaires dans les composants fonctionnels. Il peut être utilisé pour des opérations comme le chargement de données, l'abonnement à des événements, etc.
import React, { useState, useEffect } from 'react';
function Example() {
const [data, setData] = useState(null);
useEffect(() => {
// Effectue un appel API pour récupérer les données
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => setData(data));
}, []); // Le tableau vide signifie que cet effet ne sera exécuté qu'une seule fois
return (
<div>
{data ? <p>{data.message}</p> : <p>Loading...</p>}
</div>
);
}
useContext
useContext est un hook qui permet d'accéder aux valeurs fournies par le contexte dans les composants fonctionnels. Cela simplifie la gestion du state global.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style=background: theme === 'dark' ? '#000' : '#fff', color: theme === 'dark' ? '#fff' : '#000'>
Je suis un bouton {theme}
</button>
);
}
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}
Mise en pratique : projet fil rouge
Pour illustrer la transition, nous allons créer un mini-projet simple : un gestionnaire de tâches.
Étape 1 : Création du projet
Commencez par créer un nouveau projet React :
npx create-react-app task-manager
cd task-manager
Étape 2 : Création d'un composant TaskList
Créez un fichier src/TaskList.js et ajoutez le code suivant :
import React, { useState } from 'react';
function TaskList() {
const [tasks, setTasks] = useState([
{ id: 1, text: 'Faire les courses', completed: false },
{ id: 2, text: 'Nettoyer la maison', completed: true },
]);
const toggleTaskCompletion = (id) => {
setTasks(tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
return (
<div>
<h1>Gestionnaire de tâches</h1>
<ul>
{tasks.map(task => (
<li key={task.id} onClick={() => toggleTaskCompletion(task.id)}>
{task.text} - {task.completed ? 'Terminée' : 'Non terminée'}
</li>
))}
</ul>
</div>
);
}
export default TaskList;
Étape 3 : Utilisation du composant TaskList
Modifiez le fichier src/App.js pour utiliser le composant TaskList :
import React from 'react';
import TaskList from './TaskList';
function App() {
return (
<div className="App">
<TaskList />
</div>
);
}
export default App;
Étape 4 : Ajout de nouvelles tâches
Pour ajouter de nouvelles tâches, nous allons modifier le composant TaskList pour inclure un formulaire :
import React, { useState } from 'react';
function TaskList() {
const [tasks, setTasks] = useState([
{ id: 1, text: 'Faire les courses', completed: false },
{ id: 2, text: 'Nettoyer la maison', completed: true },
]);
const [newTaskText, setNewTaskText] = useState('');
const toggleTaskCompletion = (id) => {
setTasks(tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const addTask = () => {
if (newTaskText.trim()) {
setTasks([...tasks, { id: tasks.length + 1, text: newTaskText, completed: false }]);
setNewTaskText('');
}
};
return (
<div>
<h1>Gestionnaire de tâches</h1>
<input
type="text"
value={newTaskText}
onChange={(e) => setNewTaskText(e.target.value)}
placeholder="Ajouter une tâche"
/>
<button onClick={addTask}>Ajouter</button>
<ul>
{tasks.map(task => (
<li key={task.id} onClick={() => toggleTaskCompletion(task.id)}>
{task.text} - {task.completed ? 'Terminée' : 'Non terminée'}
</li>
))}
</ul>
</div>
);
}
export default TaskList;
Étape 5 : Suppression de tâches
Pour supprimer des tâches, nous allons ajouter un bouton de suppression :
import React, { useState } from 'react';
function TaskList() {
const [tasks, setTasks] = useState([
{ id: 1, text: 'Faire les courses', completed: false },
{ id: 2, text: 'Nettoyer la maison', completed: true },
]);
const [newTaskText, setNewTaskText] = useState('');
const toggleTaskCompletion = (id) => {
setTasks(tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const addTask = () => {
if (newTaskText.trim()) {
setTasks([...tasks, { id: tasks.length + 1, text: newTaskText, completed: false }]);
setNewTaskText('');
}
};
const deleteTask = (id) => {
setTasks(tasks.filter(task => task.id !== id));
};
return (
<div>
<h1>Gestionnaire de tâches</h1>
<input
type="text"
value={newTaskText}
onChange={(e) => setNewTaskText(e.target.value)}
placeholder="Ajouter une tâche"
/>
<button onClick={addTask}>Ajouter</button>
<ul>
{tasks.map(task => (
<li key={task.id} onClick={() => toggleTaskCompletion(task.id)}>
{task.text} - {task.completed ? 'Terminée' : 'Non terminée'}
<button onClick={() => deleteTask(task.id)}>Supprimer</button>
</li>
))}
</ul>
</div>
);
}
export default TaskList;
Erreurs fréquentes et debugging
Erreur 1 : TypeError: Cannot read property 'state' of undefined
Cette erreur se produit souvent lorsque vous essayez d'accéder à la propriété state sur un composant fonctionnel qui n'utilise pas les hooks de state.
## ❌ Mauvais
function Example() {
return (
<div>
{this.state.count}
</div>
);
}
## ✅ Correct
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
{count}
</div>
);
}
Erreur 2 : Error: Maximum update depth exceeded
Cette erreur se produit lorsque vous mettez à jour le state de manière infinie. Assurez-vous que votre fonction de mise à jour ne cause pas une boucle indéfinie.
## ❌ Mauvais
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, []);
return (
<div>
{count}
</div>
);
}
## ✅ Correct
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return (
<div>
{count}
</div>
);
}
Erreur 3 : Error: Invalid hook call. Hooks can only be called inside of the body of a function component or a custom Hook.
Cette erreur se produit lorsque vous essayez de déclarer un hook à l'extérieur d'un composant fonctionnel ou d'une fonction personnalisée.
## ❌ Mauvais
import React, { useState } from 'react';
const [count, setCount] = useState(0); // Erreur : pas dans une fonction
function Example() {
return (
<div>
{count}
</div>
);
}
## ✅ Correct
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
{count}
</div>
);
}
Pour aller plus loin
1. useReducer pour gérer des états complexes
Lorsque votre état devient complexe et nécessite beaucoup de logique, vous pouvez utiliser useReducer. C'est une alternative à useState qui est souvent plus utile dans ces cas.
2. useCallback pour optimiser les performances
En utilisant useCallback, vous pouvez éviter la création de fonctions dépendantes du rendu, ce qui améliore les performances en évitant les re-rendus inutiles.
3. useMemo pour stocker des valeurs mises en cache
Lorsque vous avez besoin de calculer une valeur qui est coûteuse à calculer, utilisez useMemo pour la stocker et l'utiliser à nouveau si les dépendances n'ont pas changé.
Défi pratique : Créer un mini-projet de todolist
Créez une application simple de todolist qui permet d'ajouter, de supprimer et de marquer comme terminées des tâches. Utilisez les hooks useState pour gérer l'état.