• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 09/09/2020

Intégrez Redux dans votre application

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Nous y voilà, c'est le moment de mettre en pratique tout ce que l'on a vu dans le précédent chapitre et d'ajouter Redux à notre projet.

On va créer l'architecture Redux petit bout par petit bout : reducer, action, store et state global. À la fin, on connectera tous les éléments ensemble afin d'obtenir une architecture fonctionnelle. Donc si l'utilité d'un élément créé ou son fonctionnement vous échappe, poursuivez jusqu'au bout du chapitre et tout devrait être plus clair.

Je vous en ai parlé, Redux est une librairie Javascript et elle n'est pas installée par défaut avec React Native. Donc première étape, installer les librairies. Car oui, on va avoir besoin de 2 librairies pour faire fonctionner Redux dans notre application. Dans un terminal, à la racine de votre projet, vous pouvez saisir les commandes :

npm install --save redux
npm install --save react-redux
npm install

Redux installé, on va pouvoir passer à la création de notre tout premier reducer, c'est parti ! :pirate:

Créez un reducer

Les reducers se trouvent dans le store et, en React, on a pour habitude de placer toute la logique liée au store dans un dossier à part. Commencez donc par créer un dossier Store avec un sous-dossier Reducers. Dans le sous-dossier Reducers, créez un fichier favoriteReducer.js.

Dans le reducer favoriteReducer.js, nous allons créer notre reducer et nous allons l'appeler  toggleFavorite . Rappelez-vous, un reducer prend en paramètre un  state  et une  action  : 

// Store/Reducers/favoriteReducer.js
function toggleFavorite(state, action) {
let nextState
switch (action.type) {
default:
return state
}
}

State initial

Le state initial est la définition du state par défaut de votre reducer. Un peu comme on le fait dans le constructeur de nos components, c'est ici que vous allez déclarer les valeurs du state par défaut :

// Store/Reducers/favoriteReducer.js
const initialState = { favoritesFilm: [] }
function toggleFavorite(state = initialState, action) {
let nextState
switch (action.type) {
default:
return state
}
}

Pour notre application, on initialise le state avec un tableau  favoritesFilm vide. La première fois que vous allez appeler votre reducer  toggleFavorite  , le state sera nul et donc initialisé avec la valeur de votre  initialState  . 

Gestion des types d'action

À présent, nous allons gérer les types d'action. Deux options s'offrent à nous :

  • Soit on crée deux actions : une pour l'ajout d'un film et une autre pour la suppression.

  • Soit on crée une seule action et on gère tout dans le reducer. C'est-à-dire que, dans le reducer, si le film n'est pas présent dans le state, on l'ajoute, sinon on le supprime.

On va partir sur la deuxième solution. Cela permet de déporter toute la logique de traitement des films favoris dans une seule et unique fonction. Ainsi, dans nos components, on n'aura plus qu'à appeler une action du type "L'utilisateur a cliqué sur le bouton favoris" (action que l'on peut traduire par  TOGGLE_FAVORITE ), et le reducer se chargera de tout, c'est-à-dire d'ajouter ou de supprimer le film. 

On va donc gérer une action  TOGGLE_FAVORITE  dans notre reducer : 

// Store/Reducers/favoriteReducer.js
const initialState = { favoritesFilm: [] }
function toggleFavorite(state = initialState, action) {
let nextState
switch (action.type) {
case 'TOGGLE_FAVORITE':
return nextState
default:
return state
}
}

Traitement d'une action

Bon, créer un  switch case, c'est bien, :) mais maintenant, il faut que l'on gère la partie traitement de l'action. Posez-vous les questions :

Que doit-on faire ici et que doit-on retourner dans le nouveau state ?

Ici, on doit :

  1. Ajouter un film aux favoris s'il n'est pas présent dans les films favoris

  2. Supprimer un film des favoris s'il est présent dans les films favoris

  3. Retourner le nouveau state avec les films favoris mis à jour

Et tout cela en respectant à la lettre le principe d'immuable propre aux reducers et au state. Cela en fait, des choses à penser. :p Je vous mets le code et on en discute juste après, OK ? 

// Store/Reducers/favoriteReducer.js
const initialState = { favoritesFilm: [] }
function toggleFavorite(state = initialState, action) {
let nextState
switch (action.type) {
case 'TOGGLE_FAVORITE':
const favoriteFilmIndex = state.favoritesFilm.findIndex(item => item.id === action.value.id)
if (favoriteFilmIndex !== -1) {
// Le film est déjà dans les favoris, on le supprime de la liste
nextState = {
...state,
favoritesFilm: state.favoritesFilm.filter( (item, index) => index !== favoriteFilmIndex)
}
}
else {
// Le film n'est pas dans les films favoris, on l'ajoute à la liste
nextState = {
...state,
favoritesFilm: [...state.favoritesFilm, action.value]
}
}
return nextState || state
default:
return state
}
}
export default toggleFavorite

La première chose que l'on réalise dans l'action est de vérifier que le film passé via l'action existe dans la liste des films favoris. Souvenez-vous, on fait passer l'objet d'une action (ici notre film) dans le champ value (d'où le code  action.value ). Pour savoir si le film est déjà présent dans la liste des films favoris, on utilise la fonction  findIndex  en Javascript qui retourne l'index de l'élément dans le tableau s'il existe, sinon elle renvoie -1.

Premier test
if (favoriteFilmIndex !== -1) {
// Le film est déjà dans les favoris, on le supprime de la liste
nextState = {
...state,
favoritesFilm: state.favoritesFilm.filter( (item, index) => index !== favoriteFilmIndex)
}
}

if (favoriteFilmIndex !== -1)  signifie que le film passé via l'action existe déjà dans notre liste de film favoris. Il faut donc le supprimer. C'est ici qu'il faut faire attention à respecter le principe d'immuable.

On ne touche pas au state et à ses données, voici ce qu'on fait ici :

  1. On initialise un nouvel objet  nextState  avec une copie du state  ...state .

  2. Puis, on redéfinit les films favoris de l'objet  nextState  avec un tableau qui correspond aux films favoris du  state ,  auquel on a enlevé le film à l'index spécifié (fonction  filter ). 

Notre  state  reste inchangé et notre  nextState  comprend les films favoris, moins le film passé via l'action. On respecte bien le principe d'immuable et notre fonctionnalité fait ce qu'il faut. Enfin, on testera cela plus tard, mais cela semble bien parti. :)

Deuxième test
else {
// Le film n'est pas dans les films favoris, on l'ajoute à la liste
nextState = {
...state,
favoritesFilm: [...state.favoritesFilm, action.value]
}
}

else  signifie que le film passé via l'action n'existe pas dans notre liste de films favoris. Il faut donc l'ajouter. Là, c'est plus simple, on va créer une copie du tableau de film favoris  ...state.favoritesFilm  , auquel on ajoute le film passé via l'action.

Retour
return nextState || state

En retour, on va utiliser la syntaxe  nextState || state  qui renvoie l'objet  nextState  si celui-ci n'est pas undefined, sinon on renvoie l'objet  state . C'est la sécurité dont je vous ai parlé dans le précédent chapitre. On assure le coup si cela se passe mal avec l'ajout et la suppression de films des favoris. Vous comprenez l'importance ici de garder votre state immuable ? :D

C'est tout bon, vous venez de créer votre tout premier reducer. C'est un grand pas dans le développement sur Redux, bravo ! Finalement, ce n'est pas si compliqué que cela, il faut juste faire attention à respecter les bonnes pratiques.

Je ne sais pas ce que vous en avez pensé, mais moi, quand j'ai créé mon premier reducer, j'étais vraiment surpris et à la fois content d'avoir toute une fonctionnalité dans une fonction si petite, et surtout de manière si propre. Après, c'était mon ressenti, ce n'est peut-être pas le vôtre en ce moment. :lol:

N'oubliez pas aussi d'exporter vos reducers, comme on l'a fait ici, on va en avoir besoin pour créer le store.

export default toggleFavorite

Créez le store

Nous allons maintenant nous atteler à la création du store. Une fois de plus, nous allons créer un fichier Javascript spécialement pour cela. Je vous invite donc à créer un fichier configureStore.js dans le dossier Store. 

Dans ce fichier, nous allons utiliser une fonction de la librairie Redux :  createStore  . Vous l'aurez compris, cette fonction permet de créer un store : 

// Store/configureStore.js
import { createStore } from 'redux';
import toggleFavorite from './Reducers/favoriteReducer'
export default createStore(toggleFavorite)

Vous vous souvenez quand je vous ai dit que les reducers étaient définis dans le store ? Et bien, c'est exactement ce qu'il se passe ici. On initialise le store en lui faisant passer notre reducer.Pour l'instant, on n'a qu'un seul reducer, mais, par la suite, on aura l'occasion d'initialiser notre store avec plusieurs reducers.

Ajoutez le store à notre application

Notre store est créé, il contient notre reducer  toggleFavorite  et, par conséquent, le state des films favoris dans l'application. On va maintenant ajouter le store à notre application. Cela va nous permettre d'accéder au store dans tous les components de notre application.

Encore une fois, Redux nous facilite la tâche, il met à disposition un component Provider. Ce component n'a qu'une seule et unique fonction, il distribue votre store à toute votre application. C'est grâce à ce component que vous allez pouvoir accéder à votre store et à ses reducers. Le provider a une prop  store  que l'on va initialiser avec le store récemment configuré :

// App.js
import React from 'react'
import Navigation from './Navigation/Navigation'
import { Provider } from 'react-redux'
import Store from './Store/configureStore'
export default class App extends React.Component {
render() {
return (
<Provider store={Store}>
<Navigation/>
</Provider>
)
}
}

On en a terminé avec le store. Il est configuré et, grâce au provider, il est disponible dans toute l'application. Mais on n'a pas encore terminé. Il faut maintenant que l'on récupère le store dans les components où on va en avoir besoin. On va faire ce que l'on appelle une connexion entre un component et le store Redux

Connectez le store

Là, c'est le moment de s'accrocher, on va aborder une partie un peu complexe. Tenez bon ! :D

Le premier endroit où l'on souhaite accéder au store, et donc à nos films favoris, c'est le component détail du film FilmDetail. On va connecter le store à notre component FilmDetail. Une nouvelle fois, Redux nous permet de réaliser cette opération facilement avec la mise à disposition de la fonction  connect  : 

// Components/FilmDetail.js
//...
import { connect } from 'react-redux'
//...
export default connect()(FilmDetail)

Voilà qui est fait. Le store Redux est connecté à notre component FilmDetail.

Bon, avoir accès au store dans notre component FilmDetail, c'est bien, mais ce que nous voulons ici, c'est le state de notre application, non ?

C'est vrai, et vous avez raison, on va demander à Redux de connecter également le state de notre application au component FilmDetail. En suivant la documentation sur le repo react-redux de GitHub, on y apprend qu'il faut créer une fonction  mapStateToProps  et l'ajouter en paramètre de la fonction  connect  , comme ceci :

// Components/FilmDetail.js
//...
const mapStateToProps = (state) => {
return state
}
export default connect(mapStateToProps)(FilmDetail)

Waouh c'est allé vite, qu'a-t-on fait ici ?

C'est vrai que je suis allé un peu vite ici. :lol: Reprenons.

Si je reprends la documentation, voici ce que l'on apprend sur la fonction  mapStateToProps  :

[mapStateToProps(state, [ownProps]): stateProps] (Function): If this argument is specified, the new component will subscribe to Redux store updates. This means that any time the store is updated, mapStateToProps will be called. The results of mapStateToProps must be a plain object, which will be merged into the component’s props.

Première chose très très intéressante, on y apprend que, si on spécifie  mapStateToProps  dans la fonction  connect  , automatiquement, le component est abonné aux changements du store Redux. Cela signifie qu'à présent, dès que le store et le state de l'application vont être mis à jour par vos actions, automatiquement, notre component va être informé de ce changement.

Ensuite, on apprend que la valeur retournée par la fonction  mapStateToProps  est mappée aux props de notre component.

Ici, le paramètre  state  correspond au state global, celui de notre application. On a choisi ici de retourner le state de notre application dans la fonction  mapStateToProps  . Vous savez ce que cela signifie ?

Cela signifie que l'on vient, à l'instant, de mapper le state de notre application dans les props du component FilmDetail. :waw: À présent, dans les props du component FilmDetail, vous avez accès au state de l'application et donc aux films favoris.

Vous ne me croyez pas ? On va vérifier cela tout de suite ensemble avec la bonne vieille technique des logs : :lol:

// Components/FilmDetail.js
class FilmDetail extends React.Component {
//...
render() {
console.log(this.props)
return (
//...
)
}
}

Placez-vous sur votre application, recherchez un film, cliquez sur ce film et accédez à son détail. Vous devriez voir votre  props  avec les infos de la navigation, mais aussi et surtout :

09:36:58: Object {
09:36:58:   "favoritesFilm": Array [],
09:36:58:   ...

Mais, ce ne seraient pas les données de notre state global, par hasard ? :soleil: On a réussi à connecter notre store aux props de notre component FilmDetail.

Ne pas récupérez tout le store

Imaginez que demain, vous gérez, dans le state de l'application, les informations de l'utilisateur, la liste des films déjà vus, les recherches effectuées par l'utilisateur, etc. Avec notre solution actuelle, vous allez tout récupérer dans le component FilmDetail. Alors que nous, à la base, ne voulons récupérer que la liste des films favoris. 

Cela m'amène à une autre bonne pratique. Quand vous mappez le state de l'application à un component, vous devez spécifier les informations qui vous intéressent et ne pas retourner tout le state comme on l'a fait précédemment. Ainsi, vous ne connecterez que les éléments nécessaires :

// Components/FilmDetail.js
// ...
const mapStateToProps = (state) => {
return {
favoritesFilm: state.favoritesFilm
}
}
export default connect(mapStateToProps)(FilmDetail)

Ici, on n'a mappé que ce qui nous intéresse, à savoir la liste des films favoris. Vous pouvez vérifier avec le log des  props  du component FilmDetail, vous accédez toujours à la liste des films favoris.

Mappez les props

Avec ce que l'on a vu depuis le début de ce cours et l'utilisation de Redux, vous êtes peut-être en train de vous demander :

OK, c'est bien d'avoir mappé les films favoris dans les props du component FilmDetail. Mais pourquoi ne pas avoir mappé directement les films favoris dans le state du component FilmDetail ? Après tout, c'est quand le state est mis à jour avec  setState  que le component est re-rendu, n'est-ce pas ? Et on va bien avoir à re-rendre le component FilmDetail quand l'utilisateur ajoute le film aux favoris ou le supprime ?

Vous avez tout à fait raison. En fait, je ne vous ai pas tout dit. Il y a deux actions qui peuvent pousser un component à se re-rendre :

  1. Un changement dans son state (avec  setState()  )

  2. Un changement dans ses props

Si, dans votre application, les props d'un component change, celui-ci passe automatiquement dans le cycle de vie updating et se re-rend

Prenons l'exemple de notre application, ce sera plus clair. :)

Lorsque vous allez ajouter un film aux favoris, le state de votre application va être mis à jour. Le store Redux va détecter ce changement et notifier tous les components connectés à votre store Redux, vous savez, tous les components que l'on a connectés grâce à la définition du paramètre  mapStateToProps .

Le component FilmDetail est connecté au store Redux. Il va recevoir la nouvelle liste de films favoris et la mapper à ses props. Les props du component FilmDetail ayant changé, le cycle de vie updating va être exécuté et va pousser le component FilmDetail à se re-rendre avec vos nouvelles props

Graphiquement, dans notre application actuelle, on a ceci :

Fonctionnement détaillé de Redux dans le cas d'un ajout ou d'une suppression de film des favoris dans notre application
Fonctionnement détaillé de Redux dans le cas d'un ajout ou d'une suppression de film des favoris dans notre application

Voilà, vous avez ici tout le fonctionnement de Redux. C'est plutôt bien pensé, non ? :) Tout ce fonctionnement est possible du fait que le changement des props d'un component le pousse à se re-rendre.

Notre store Redux est en place. Notre reducer est fonctionnel. On arrive à fournir le state global à nos components abonnés. En bref, on a réalisé toute la partie Store et View sur le schéma ci-dessus. C'est déjà énorme, bravo ! ;)

Il nous reste un dernier point à voir ou plutôt une dernière partie du schéma à réaliser. C'est la partie Action.

Dans notre fonctionnalité, nous souhaitons ajouter un film aux favoris, mais, rappelez-vous, avec Redux, tout passe par des actions. Je vous propose, dans le prochain chapitre, de créer ensemble notre toute première action avec Redux.

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