Pourquoi Gestion d'etat avec React ?
Dans un monde où les applications web deviennent de plus en plus complexes, la gestion de l'état (state management) est une compétence cruciale pour les développeurs. L'état d'une application peut être défini comme la collection de toutes les données qui déterminent l'état actuel de l'application, y compris ses composants et leurs propriétés.
Un dev a besoin de gestion d'état au quotidien parce qu'il permet une meilleure organisation et maintien de l'état de l'application. En effet, sans gestion d'état efficace, il se peut que les composants de l'application ne soient pas à jour avec les données les plus récentes, ce qui peut entraîner des erreurs difficiles à repérer.
Un cas d'usage concret est un application de messagerie instantanée. Chaque conversation doit être stockée dans un état global afin que chaque utilisateur puisse voir les messages qu'il a reçus et envoyés.
Prerequis
- Connaissance du langage JavaScript et des bases de React.
- Familiarité avec les concepts de composants, d'états (state) et de propriétés (props).
- Installation de Node.js et npm pour exécuter le code en local.
Concepts fondamentaux
1. Composant Stateful vs Stateless
Un composant stateful est un composant qui a son propre état interne, tandis qu'un composant stateless n'a pas d'état et ne peut donc pas modifier ses propres propriétés.
// Component Stateful
class Message extends React.Component {
constructor(props) {
super(props);
this.state = { message: 'Bonjour' };
}
updateMessage = () => {
this.setState({ message: 'Au revoir' });
};
render() {
return (
<div>
<p>{this.state.message}</p>
<button onClick={this.updateMessage}>Changer Message</button>
</div>
);
}
}
2. Props vs State
Les props sont des valeurs passées d'un composant parent à un composant enfant, tandis que le state est interne au composant et peut être modifié par celui-ci.
// Utilisation de props et state dans les mêmes composants
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: props.initialCount };
}
increment = () => {
this.setState(prevState => ({ count: prevState.count + 1 }));
};
render() {
return (
<div>
<p>Compteur : {this.state.count}</p>
<button onClick={this.increment}>Incrementer</button>
</div>
);
}
}
3. Context API
La Context API permet de partager des données entre les composants sans avoir à passer explicitement les propriétés à travers chaque niveau d'abstraction.
// Création d'un contexte
const ThemeContext = React.createContext('light');
// Utilisation du contexte dans un composant
class ThemedButton extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{theme => <button style=backgroundColor: theme === 'dark' ? 'black' : 'white', color: theme === 'dark' ? 'white' : 'black'>Click me</button>}
</ThemeContext.Consumer>
);
}
}
4. Redux
Redux est une bibliothèque de gestion d'état pour JavaScript qui permet de gérer les états de l'application de manière décentralisée et prévisible.
// Action type
const ADD_TODO = 'ADD_TODO';
// Action creator
function addTodo(text) {
return { type: ADD_TODO, text };
}
// Reducer
const initialState = [];
function todosReducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return [...state, { text: action.text }];
default:
return state;
}
}
5. MobX
MobX est une bibliothèque de gestion d'état simple et efficace qui fait le lien entre les composants React et l'état application.
// Création d'un observable store
import { observable, action } from 'mobx';
class TodoStore {
@observable todos = [];
@action addTodo(text) {
this.todos.push({ text });
}
}
// Utilisation du store dans un composant React
class TodoList extends React.Component {
render() {
return (
<div>
{this.props.todoStore.todos.map(todo => <li key={todo.text}>{todo.text}</li>)}
<button onClick={() => this.props.todoStore.addTodo('Nouvelle tâche')}>Ajouter une tâche</button>
</div>
);
}
}
const todoStore = new TodoStore();
ReactDOM.render(<TodoList todoStore={todoStore} />, document.getElementById('root'));
Mise en pratique : projet fil rouge
Nous allons créer un gestionnaire de tâches simple avec React et Redux. Cet application permettra aux utilisateurs d'ajouter, de supprimer et de marquer comme terminées les tâches.
Étape 1 : Initialisation du projet
npx create-react-app task-manager
cd task-manager
npm install redux react-redux
Étape 2 : Création des actions et reducers
Créez un fichier store.js pour configurer Redux :
// src/store.js
import { createStore } from 'redux';
const initialState = {
tasks: []
};
function taskReducer(state = initialState, action) {
switch (action.type) {
case 'ADD_TASK':
return { ...state, tasks: [...state.tasks, action.payload] };
case 'REMOVE_TASK':
return { ...state, tasks: state.tasks.filter(task => task.id !== action.payload) };
case 'TOGGLE_TASK':
return { ...state, tasks: state.tasks.map(task => (task.id === action.payload ? { ...task, completed: !task.completed } : task)) };
default:
return state;
}
}
const store = createStore(taskReducer);
export default store;
Étape 3 : Création des composants
Créez un fichier TaskList.js pour afficher la liste des tâches :
// src/components/TaskList.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
function TaskList() {
const tasks = useSelector(state => state.tasks);
const dispatch = useDispatch();
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
{task.text}
<button onClick={() => dispatch({ type: 'TOGGLE_TASK', payload: task.id })}>Terminer</button>
<button onClick={() => dispatch({ type: 'REMOVE_TASK', payload: task.id })}>Supprimer</button>
</li>
))}
</ul>
);
}
export default TaskList;
Étape 4 : Création du formulaire d'ajout de tâche
Créez un fichier TaskForm.js pour ajouter de nouvelles tâches :
// src/components/TaskForm.js
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
function TaskForm() {
const [text, setText] = useState('');
const dispatch = useDispatch();
const handleSubmit = e => {
e.preventDefault();
if (text.trim()) {
dispatch({ type: 'ADD_TASK', payload: { id: Date.now(), text, completed: false } });
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
placeholder="Ajouter une tâche"
/>
<button type="submit">Ajouter</button>
</form>
);
}
export default TaskForm;
Étape 5 : Intégration des composants dans App.js
// src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import TaskList from './components/TaskList';
import TaskForm from './components/TaskForm';
function App() {
return (
<Provider store={store}>
<div className="App">
<h1>Gestionnaire de Tâches</h1>
<TaskForm />
<TaskList />
</div>
</Provider>
);
}
export default App;
Étape 6 : Lancement du projet
npm start
Erreurs frequentes et debugging
1. Prop non défini
Erreur :
TypeError: Cannot read property 'text' of undefined
Code incorrect :
const TaskList = ({ tasks }) => {
return (
<ul>
{tasks.map(task => (
<li key={task.id}>{task.text}</li> // task peut être undefined
))}
</ul>
);
};
Code correct :
const TaskList = ({ tasks }) => {
return (
<ul>
{tasks.map((task, index) => (
<li key={index}>{task.text}</li> // Utilisation de l'index comme clé temporaire
))}
</ul>
);
};
2. Dispatch non défini
Erreur :
TypeError: Cannot read property 'dispatch' of undefined
Code incorrect :
const TaskForm = () => {
const [text, setText] = useState('');
const dispatch = useSelector(state => state.dispatch); // Erreur : dispatch n'est pas une propriété de l'état
const handleSubmit = e => {
e.preventDefault();
if (text.trim()) {
dispatch({ type: 'ADD_TASK', payload: { id: Date.now(), text, completed: false } });
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
placeholder="Ajouter une tâche"
/>
<button type="submit">Ajouter</button>
</form>
);
};
Code correct :
const TaskForm = () => {
const [text, setText] = useState('');
const dispatch = useDispatch();
const handleSubmit = e => {
e.preventDefault();
if (text.trim()) {
dispatch({ type: 'ADD_TASK', payload: { id: Date.now(), text, completed: false } });
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
placeholder="Ajouter une tâche"
/>
<button type="submit">Ajouter</button>
</form>
);
};
3. State non modifiable
Erreur :
TypeError: Cannot assign to read only property 'length' of object '[object Array]'
Code incorrect :
const TaskList = ({ tasks }) => {
const [newTasks, setNewTasks] = useState([...tasks]); // Erreur : on tente de modifier le state directement
return (
<ul>
{newTasks.map(task => (
<li key={task.id}>{task.text}</li>
))}
</ul>
);
};
Code correct :
const TaskList = ({ tasks }) => {
const [newTasks, setNewTasks] = useState([...tasks]);
const removeTask = id => {
setNewTasks(newTasks.filter(task => task.id !== id));
};
return (
<ul>
{newTasks.map((task, index) => (
<li key={index}>
{task.text}
<button onClick={() => removeTask(task.id)}>Supprimer</button>
</li>
))}
</ul>
);
};
Pour aller plus loin
- Redux Toolkit : Un ensemble d'outils pour simplifier la gestion de l'état avec Redux.
- Recoil : Une bibliothèque simple et efficace pour gérer l'état dans les applications React.
- Zustand : Un hook state management déclaratif pour React.
Défi pratique
Implémentez une application de blog avec React, Redux et MobX. L'application devrait permettre aux utilisateurs de créer, lire, mettre à jour et supprimer des articles.