• 8 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 31/01/2024

Configurez vos reducers avec les EntityAdapters

Comprenez ce qu’est un EntityAdapter

Nous faisons bien souvent des applications avec la même approche, et des fonctionnalités qui se rapprochent bien plus qu’il ne nous paraît.

Si nous stockons une liste de “choses”, il est évident qu’une des fonctionnalités sera d’ajouter une “chose” à cette liste. De plus, si nous stockons une liste, nous souhaitons généralement l’afficher.

Dans notre cas, imaginons qu’un client ayant des allergies alimentaires veuille pouvoir indiquer les aliments à retirer de son burger. Pour ce faire, nous pouvons ajouter la fonctionnalité d’ajout de notes qui seront affichées aux cuisiniers de notre restaurant.

Nous pourrions ajouter une slice, des sélecteurs… ou utiliser un outil qui nous permettrait de simplifier la configuration !

Vous voyez où je veux en venir ? Je parle bien du CRUD :

  • Create (créer),

  • Read (lire),

  • Update (mettre à jour),

  • Delete (supprimer).

Ce sont des opérations que nous pouvons qualifier de génériques. C’est-à-dire que ce sont des actions qui ont des comportements identiques mais qui sont appliquées à des objets différents.

Je vous mets un petit exemple dans le code suivant :

const houses = [
{
  id: 1,
  name: 'house1',
  price: 100000
},
{
  id: 2,
  name: 'house2',
  price: 200000
},
{
  id: 3,
  name: 'house3',
  price: 300000
}
]

const students = [
{
  id: 1,
  name: 'student1',
  average: 20
},
{
  id: 2,
  name: 'student2',
  average: 18
},
{
  id: 3,
  name: 'student3',
  average: 19
}
]

const getHouses = () => {
  return houses
}
const getStudents = () => {
  return students
}

const getHouseById = (id) => {
  return houses.filter(house => house.id === id)
}
const getStudentById = (id) => {
  return students.filter(student => student.id === id)
}

const addHouse = (house) => {
  houses.push(house)
}

const addStudent = (student) => {
  students.push(student)
}

Ci-dessus, students  et houses  sont des listes d’objets différents. À première vue, on ne peut pas avoir les mêmes actions. Mais en prenant un peu de hauteur, addStudent  et addHouse  font la même chose, ajouter un élément dans la liste : students  pour addStudent  et houses  pour addHouse  .

On peut à priori simplifier tout ça.

Je vous propose la petite implémentation ci-après :

const entityAdapter = () => {
  const entities = [];
  return {
    addOne: (entity) => {
      entities.push(entity)
      return this
    },
    selectAll: () => entities,
    selectById: (id) => entities.find(entity => entity.id === id),
  }
 }

const students = entityAdapter()
const houses = entityAdapter()

Nos deux constantes students  et houses  ont maintenant des actions associées ( addOne  , selectAll  etselectById  ). Plus besoin de les créer une à une, grâce à un outil inclus dans Redux Toolkit : EntityAdapter  .

Cet outil permet de configurer notre store et de générer les outils qui vont nous permettre d’effectuer simplement ces opérations CRUD.

L’ EntityAdapter  est une fonction qui génère un ensemble de reducers et de sélecteurs destiné à effectuer des opérations CRUD. Avec cet outil, nous aurons accès à des méthodes comme addOne  pour ajouter une entrée, addMany  pour ajouter plusieurs entrées, setOne  pour modifier une entrée, etc.

Créez un EntityAdapter

Pour utiliser cette fonctionnalité, utilisons createEntityAdapter  de Redux Toolkit.

Cette fonction s’emploie un peu comme dans notre implémentation simplifiée vue dans le paragraphe juste au-dessus, avec la particularité de pouvoir surcharger chaque action du CRUD. Pour ce faire, on peut passer en paramètre un objet qui reprend chaque action devant être adaptée. Chaque action peut ainsi être adaptée en cas de besoin.

Par exemple, admettons que l’ id  de nos élèves ne soit pas stocké dans student.id  mais dans student.studentId  . Notre précédente implémentation ne fonctionnerait pas pour students  .

En effet, cette fonction ne retournera aucun résultat, car entity.id  sera toujours undefined  :

selectById: (id) => entities.find(entity => entity.id === id),

Il nous faut donc un moyen d’adapter notre selectById  selon l’entité, mais uniquement pour cette fonction.

Avec createEntityAdapter  , nous n’avons qu’à ajouter la fonction qui va être appliquée au moment de faire student.selectById  , en passant un objet comme ceci :

{
    selectById: (students, id) => students.find(student => student.studentId === id),
}

Ce qui nous donne le code suivant :

import { createEntityAdapter } from '@reduxjs/toolkit'

const students = createEntityAdapter({
    selectById: (students, id) => students.find(student => student.studentId === id),
})

createEntityAdapter  va donc générer un objet, students  , avec des actions prédéfinies.

Notre fonction selectById  sera exécutée lorsque nous ferons appel à students.selectById  et nous aurons bien un résultat pour un id  donné.

Pour ajouter notre entityAdaptor  à notre store, rien de plus simple. Comme je vous l’ai dit, il génère notre reducer et nos sélecteurs. Ci-dessous, je vous montre comment l’ajouter au store et comment extraire les sélecteurs :

const students = createEntityAdapter({
  selectById: (students, id) => students.find(student => student.studentId === id),
})

const studentsSlice = createSlice({
  ...,
  reducers: {
    add: students.addOne,
  }
})

const store = configureStore({
  reducer: {
    students: studentsSlice.reducer,
  },
})

const studentsSelectors = students.getSelectors(state => state.students)

À vous de jouer

C’est maintenant l’occasion d’ajouter une fonctionnalité à notre store en employant EntityAdapter  .

Je vous propose de reprendre ma proposition qui est de permettre au client d’ajouter des notes pour qu’en cuisine, les préparateurs puissent suivre ces notes et faire des burgers sur mesure.

Qui ne connaît pas un proche ne mangeant pas d’oignons ?

Pour cela, je vous propose d’ajouter un dossier features/notes  , un composant features/notes/Notes.js  et une slice features/notesSlices.js  .

Ajoutez le composant features/notes/Notes.js  dans notre fichier app/App.js  .

Puis implémentez notre fonctionnalité avec les users stories suivantes :

  • En tant que client, je peux ajouter une note en remplissant un champ sur plusieurs lignes. En cliquant sur le bouton “Ajouter une note”, la note est sauvegardée.

  • En tant que client, je peux lister les notes que j’ai saisies. Un bouton “Supprimer” à droite de chaque note permet de retirer la note correspondante.

Une fois que vous avez implémenté tout cela, suivez-moi dans la solution :

En résumé

  • L'  EntityAdapter  est conçu pour gérer les opérations CRUD (Create, Read, Update, Delete) de manière générique.

  • createEntityAdapterde Redux Toolkit permet de configurer facilement un adaptateur pour n'importe quel type d'entité et offre la flexibilité de surcharger ou d'adapter les actions CRUD selon les besoins spécifiques de chaque entité.

  • Utiliser les EntityAdapter  permet d’implémenter avec peu d’efforts nos nouvelles fonctionnalités.        

Nous voilà outillés pour créer de nouvelles fonctionnalités à la chaîne. Nous maîtrisons les EntityAdapter  , nos slices fonctionnent à merveille. 

Mais une chose vous turlupine sans doute. JavaScript est un langage qui s’exécute de façon asynchrone dans la plupart des cas, les appels réseaux par exemple. Jusqu’ici, nous n’avons pas abordé cet angle. Eh bien dans la partie suivante, nous allons nous intéresser à l’asynchronisme dans notre store. Prêt pour voyager dans le temps ? 😂

Exemple de certificat de réussite
Exemple de certificat de réussite