Nouveau : Datasets open source gratuits disponibles !Decouvrir →
⚛️
Intermediaire 25 min React

Gestion d'etat avec React

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

  1. Redux Toolkit : Un ensemble d'outils pour simplifier la gestion de l'état avec Redux.
  2. Recoil : Une bibliothèque simple et efficace pour gérer l'état dans les applications React.
  3. 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.

Besoin d'aide sur React ?

Besoin d'aide sur un projet technique ? Decrivez-le pour des conseils personnalises.

Recevoir des conseils

Questions frequentes

Quelle est la différence entre useState et useEffect en React?
useState est utilisé pour ajouter des états à vos composants fonctionnels, tandis que useEffect vous permet d'exécuter des effets secondaires après chaque rendu de votre composant.
Comment puis-je gérer les états complexes en React?
Pour gérer les états complexes, il est recommandé d'utiliser le hook useReducer. Cela vous permet de centraliser la logique de mise à jour de l'état et peut rendre votre code plus facile à comprendre et à maintenir.
Quelle est la meilleure pratique pour mettre à jour les états asynchrones en React?
Pour mettre à jour les états asynchrones, utilisez le hook useEffect. Assurez-vous de ne pas avoir d'effets secondaires indésirables en utilisant un tableau vide de dépendances.

Pages liees

Chaque semaine, le meilleur de la tech francaise

Tendances, salaires, outils et opportunites — directement dans votre boite mail.

Gratuit. Desabonnement en un clic. Pas de spam.