• 15 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 07/03/2022

Utilisez tout le potentiel des selectors

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

Utilisez les props dans les selectors

Créons un composant PlayerScore  qui affiche le score d’un joueur dans Tennis Score.

Le composant PlayerScore affiche le score de chaque joueur.
L’interface de Tennis Score avec PlayerScore

Pour récupérer les données nécessaires à l’affichage du score, nous allons de nouveau utiliser useSelector  .

Pourquoi ne pas envoyer directement les données en props depuis le composant App  ?

Cela serait effectivement possible, mais cela ne correspond pas vraiment à la philosophie de Redux. La documentation officielle de Redux recommande de connecter le plus de composants possible, car cela améliore les performances.

Mais nous allons quand même utiliser les props ! En effet, le composant PlayerScore  a besoin de connaître le nom et l’identifiant du joueur dont il doit afficher le score. Il va donc recevoir une props playerId  et une props playerName  .

Nous allons ensuite utiliser la props playerId  dans une fonction de selector pour accéder au score du joueur :

import { useSelector } from "react-redux";
export function PlayerScore({ playerId, playerName }) {
// playerId est soit "player1" soit "player2"
// on l'utilise dans le selector pour accéder au score du joueur !
const score = useSelector((state) => state[playerId]);
return (
<div className="player-score">
<p>{playerName}</p>
<p>{score}</p>
</div>
);
}

Et voilà, chaque composant PlayerScore  récupère ainsi uniquement le score du joueur passé en props !

Jusqu'à maintenant, nous avons utilisé les selectors uniquement pour accéder à une portion du state. Mais les selectors peuvent faire bien plus que cela ! 😎

Faites des calculs dans les selectors

Reprenons notre composant PlayerScore  pour ajouter le mot “Avantage” lorsqu'un des joueurs a l’avantage.

Pour faire cela, on peut ajouter un useSelector  pour récupérer state.advantage  , puis vérifier dans le composant si playerId  correspond à ce state :

const advantage = useSelector((state) => state.advantage);
const hasAdvantage = advantage === playerId;
// si `hasAdvantage` est `true` on affiche le mot "Avantage"

Le code ci-dessus fonctionne, mais on peut faire mieux ! Au lieu d’extraire advantage  et de le comparer dans le composant, on peut faire la comparaison directement dans le selector :

const hasAdvantage = useSelector((state) => state.advantage === playerId);

Est-ce que cela fait vraiment une grosse différence ? J’ai l’impression que c’est presque pareil.

Dans le cas présent, c’est un peu vrai. Mais nous allons voir qu'il est possible d’écrire les selectors en dehors des composants React. Déplacer la logique dans les selectors devient alors beaucoup plus intéressant, car cela permet de simplifier nos composants et réutiliser la logique du selector !

Déclarez les selectors en dehors des composants

Lorsqu’un selector devient complexe ou que l’on a besoin de le réutiliser, on peut déplacer le selector en dehors du composant.

Cela permet également de donner un nom au selector, ce qui aide à la lecture du code.

Reprenons l’exemple du hasAdvantage  et voyons ce qu’il se passe si l'on déclare le selector en dehors du composant :

const selectPlayerHasAdvantage = (state) => state.advantage === playerId;
// Erreur ⛔ ️ la variable playerId n’existe pas !
export function PlayerScore({ playerId, playerName }) {
const hasAdvantage = useSelector(selectPlayerHasAdvantage);
// ...
}

La fonction selector  n’a plus accès à la variable playerId  ! Pour remédier à cela, nous allons créer une fonction qui recevra en paramètre le playerId  et qui retournera la fonction de selector.

Autrement dit, on va créer une fonction qui retourne une fonction (un selector). Voici à quoi ressemble le code :

const selectPlayerHasAdvantage = (playerId) => {
return (state) => state.advantage === playerId;
}

On peut maintenant utiliser cette fonction dans notre composant, sans oublier de passer playerId  en paramètre !

const hasAdvantage = useSelector(selectPlayerHasAdvantage(playerId));

Comprenez ce qui provoque un render

Lorsque le state de Redux change, React-Redux réexécute les selectors, puis compare leur résultat avec le résultat précédent. Si le résultat est différent, alors le composant qui contient ce selector va être de nouveau rendu pour que les informations affichées soient toujours à jour.

Comme on l’a vu précédemment, useSelector  utilise l’opérateur ===  pour comparer les valeurs. C’est d’ailleurs pour cette raison qu’il ne faut pas changer le state directement. Il faut utiliser le destructuring ou Immer dans le reducer.

Mais que se passe-t-il si l’on crée une nouvelle référence dans un selector ? Par exemple, on pourrait créer un selector selectPlayersScore  qui sélectionne uniquement les scores des joueurs et les retourne dans un objet :

const selectPlayersScores = state => {
// ⛔️ Ce code n'est pas correcte
// Il ne faut pas créer de référence dans un reducer !
return {
player1: state.player1,
player2: state.player2,
}
}

Si vous avez bien suivi le chapitre précédent sur les données en JavaScript, vous avez peut-être déjà compris le problème. On crée une nouvelle référence à chaque exécution de selectPlayersScores  , et le résultat ne sera donc jamais égal au résultat précédent ! Même si les scores des joueurs n'ont pas changé, le composant qui utilise ce selector va être inutilement mis à jour.

Il faut donc faire bien attention de ne jamais créer des objets ou des tableaux dans les selectors. Dans le cas du selectPlayersScores  , il est préférable de faire deux selectors :

const selectPlayer1Score = state => state.player1;
const selectPlayer2Score = state => state.player2;

... ou bien de faire un selector qui attend un paramètre playerId  :

const selectPlayerScore = (playerId) => {
return (state) => state[playerId];
};

Voyons maintenant comment créer un composant PlayerScore  avec tout ce qu’on vient de voir :

Vous pouvez retrouver le code de ce screencast sur la branche P2C3-player-score du repository.

Exercez-vous

C’est maintenant à vous de créer le composant PlayerScore  . En partant de la correction du chapitre précédent, vous allez devoir :

  • Créer le composant PlayerScore  et le connecter à Redux.

  • Extraire les selectors dans un fichier selectors.js  pour mieux organiser l’application.

  • Créer un composant PlayerPoints  qui affiche le nombre de jeux gagnés par chacun des joueurs (en utilisant la propriété history  du state).

L'interface de Tennis Score avec le nouveau composant PlayerPoints ajouté au-dessus de l’affichage du score.
Le composant PlayerPoints ajouté à l’interface de Tennis Score

Une fois que vous avez terminé, allez jeter un œil à la version corrigée sur la branche P2C3-solution du repository.

Bonus : Saurez-vous afficher pour chaque joueur le nombre d’échanges consécutifs à gagner pour remporter le jeu ?

Par exemple si le score est de 15-40, le joueur 1 est à 4 échanges de la victoire alors que le joueur 2 est à 1 échange. Si le score est de 40-40, les deux joueurs sont à 2 échanges de la victoire.

Voici, à présent, le code de correction de l'exercice :

Vous pouvez retrouver le code du bonus sur la branche P2C3-bonus du repository.

En résumé

  • Un selector est une fonction que l’on passe à useSelector  pour extraire un morceau du state.

  • Il est possible d’utiliser les props dans les selectors, cela permet de faire des composants dynamiques tel que le composant PlayerScore  .

  • Pour simplifier les composants, on peut déclarer les selectors dans un fichier séparé ; si le composant utilise des props, il faut utiliser une fonction qui retourne le selector.

  • Il ne faut jamais créer de référence dans les selectors, car React-Redux va croire qu’ils changent à chaque changement de state.

Jusqu’à maintenant, nous avons uniquement manipulé des changements synchrones, c'est-à-dire immédiats. Dans le chapitre suivant, nous allons voir comment on peut gérer des événements asynchrones. 

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