Vous n'avez peut-être pas besoin d'un Effet
Les Effets sont une échappatoire au paradigme de React. Ils vous permettent de « sortir » de React et de synchroniser vos composants avec un système externe comme un widget non-React, le réseau ou le DOM du navigateur. S'il n'y a pas de système externe impliqué (par exemple, si vous souhaitez mettre à jour l'état d'un composant lorsque certaines props ou l'état changent), vous ne devriez pas avoir besoin d'un Effet. Supprimer les Effets inutiles rendra votre code plus facile à suivre, plus rapide à exécuter et moins sujet aux erreurs.
Vous allez apprendre
- Pourquoi et comment supprimer les Effets inutiles de vos composants
- Comment mettre en cache des calculs coûteux sans Effets
- Comment réinitialiser et ajuster l'état d'un composant sans Effets
- Comment partager la logique entre les gestionnaires d'événements
- Quelle logique doit être déplacée vers les gestionnaires d'événements
- Comment notifier les composants parents des changements
Comment supprimer les Effets inutiles
Il existe deux cas courants où vous n'avez pas besoin d'Effets :
- Vous n'avez pas besoin d'Effets pour transformer des données en vue de leur rendu.Par exemple, supposons que vous souhaitiez filtrer une liste avant de l'afficher. Vous pourriez être tenté d'écrire un Effet qui met à jour une variable d'état lorsque la liste change. Cependant, c'est inefficace. Lorsque vous mettez à jour l'état, React appellera d'abord vos fonctions de composant pour calculer ce qui doit être à l'écran. Ensuite, React« validera »ces changements dans le DOM, mettant à jour l'écran. Ensuite, React exécutera vos Effets. Si votre Effetégalementmet immédiatement à jour l'état, cela redémarre tout le processus depuis le début ! Pour éviter les passes de rendu inutiles, transformez toutes les données au niveau supérieur de vos composants. Ce code sera automatiquement réexécuté chaque fois que vos props ou votre état changent.
- Vous n'avez pas besoin d'Effets pour gérer les événements utilisateur.Par exemple, supposons que vous souhaitiez envoyer une requête POST
/api/buyet afficher une notification lorsque l'utilisateur achète un produit. Dans le gestionnaire d'événement de clic du bouton Acheter, vous savez exactement ce qui s'est passé. Au moment où un Effet s'exécute, vous ne savez pasce quel'utilisateur a fait (par exemple, quel bouton a été cliqué). C'est pourquoi vous traiterez généralement les événements utilisateur dans les gestionnaires d'événements correspondants.
Vousavezbesoin d'Effets pourvous synchroniseravec des systèmes externes. Par exemple, vous pouvez écrire un Effet qui maintient un widget jQuery synchronisé avec l'état React. Vous pouvez également récupérer des données avec des Effets : par exemple, vous pouvez synchroniser les résultats de recherche avec la requête de recherche actuelle. Gardez à l'esprit que lesframeworksmodernes fournissent des mécanismes de récupération de données intégrés plus efficaces que d'écrire des Effets directement dans vos composants.
Pour vous aider à acquérir la bonne intuition, examinons quelques exemples concrets courants !
Mise à jour de l'état en fonction des props ou de l'état
Supposons que vous ayez un composant avec deux variables d'état :firstNameetlastName. Vous souhaitez calculer unfullNameà partir de celles-ci en les concaténant. De plus, vous voulez quefullNamese mette à jour chaque fois quefirstNameoulastNamechangent. Votre premier réflexe pourrait être d'ajouter une variable d'étatfullNameet de la mettre à jour dans un Effet :
C'est plus compliqué que nécessaire. C'est aussi inefficace : cela effectue un cycle de rendu complet avec une valeur obsolète pourfullName, puis re-rend immédiatement avec la valeur mise à jour. Supprimez la variable d'état et l'Effet :
Lorsque quelque chose peut être calculé à partir des props ou de l'état existants,ne le mettez pas dans l'état.Calculez-le plutôt pendant le rendu.Cela rend votre code plus rapide (vous évitez les mises à jour "en cascade" supplémentaires), plus simple (vous supprimez du code) et moins sujet aux erreurs (vous évitez les bugs causés par des variables d'état qui se désynchronisent). Si cette approche vous semble nouvelle,Penser en Reactexplique ce qui doit aller dans l'état.
Mise en cache de calculs coûteux
Ce composant calculevisibleTodosen prenant lestodosqu'il reçoit par props et en les filtrant selon la propfilter. Vous pourriez être tenté de stocker le résultat dans l'état et de le mettre à jour depuis un Effet :
Comme dans l'exemple précédent, c'est à la fois inutile et inefficace. Tout d'abord, supprimez l'état et l'Effet :
Généralement, ce code est correct ! Mais peut-être quegetFilteredTodos()est lent ou que vous avez beaucoup detodos. Dans ce cas, vous ne voulez pas recalculergetFilteredTodos()si une variable d'état sans rapport commenewTodoa changé.
Vous pouvez mettre en cache (ou« mémoiser ») un calcul coûteux en l'enveloppant dans un HookuseMemo :
Note
React Compilerpeut mémoiser automatiquement les calculs coûteux pour vous, éliminant le besoin d'unuseMemomanuel dans de nombreux cas.
Ou, écrit sur une seule ligne :
Cela indique à React que vous ne voulez pas que la fonction interne se réexécute à moins quetodosoufiltern'aient changé.React mémorisera la valeur de retour degetFilteredTodos()lors du rendu initial. Lors des rendus suivants, il vérifiera sitodosoufiltersont différents. S'ils sont identiques à la dernière fois,useMemoretournera le dernier résultat qu'il a stocké. Mais s'ils sont différents, React appellera à nouveau la fonction interne (et stockera son résultat).
La fonction que vous enveloppez dansuseMemos'exécute pendant le rendu, donc cela ne fonctionne que pour descalculs purs.
Réinitialiser tout l'état lorsqu'une prop change
Ce composantProfilePagereçoit une propuserId. La page contient un champ de commentaire, et vous utilisez une variable d'étatcommentpour contenir sa valeur. Un jour, vous remarquez un problème : lorsque vous naviguez d'un profil à un autre, l'étatcommentn'est pas réinitialisé. Par conséquent, il est facile de poster accidentellement un commentaire sur le mauvais profil utilisateur. Pour résoudre le problème, vous voulez vider la variable d'étatcommentchaque fois que la propuserIdchange :
C'est inefficace carProfilePageet ses enfants vont d'abord s'afficher avec l'ancienne valeur, puis s'afficher à nouveau. C'est aussi compliqué car vous devriez le faire danschaquecomposant qui a un état à l'intérieur deProfilePage. Par exemple, si l'interface utilisateur des commentaires est imbriquée, vous voudriez aussi effacer l'état des commentaires imbriqués.
Au lieu de cela, vous pouvez indiquer à React que le profil de chaque utilisateur est conceptuellement un profildifférenten lui donnant une clé explicite. Divisez votre composant en deux et passez un attributkeydu composant extérieur au composant intérieur :
Normalement, React préserve l'état lorsque le même composant est rendu au même endroit.En passantuserIdcommekeyau composantProfile, vous demandez à React de traiter deux composantsProfile avec des userIddifférents comme deux composants distincts qui ne doivent partager aucun état.Chaque fois que la clé (que vous avez définie suruserId) change, React recréera le DOM etréinitialisera l'étatdu composantProfileet de tous ses enfants. Maintenant, le champcommentsera automatiquement effacé lors de la navigation entre les profils.
Notez que dans cet exemple, seul le composant extérieurProfilePageest exporté et visible par les autres fichiers du projet. Les composants qui rendentProfilePagen'ont pas besoin de lui passer la clé : ils passentuserIdcomme prop normale. Le fait queProfilePagele passe commekeyau composant intérieurProfileest un détail d'implémentation.
Ajuster une partie de l'état lorsqu'une prop change
Parfois, vous voudrez peut-être réinitialiser ou ajuster une partie de l'état lors d'un changement de prop, mais pas la totalité.
Ce composantListreçoit une liste d'itemsen tant que prop, et maintient l'élément sélectionné dans la variable d'étatselection. Vous souhaitez réinitialiser laselection à nullchaque fois que la propitemsreçoit un tableau différent :
Ceci n'est pas idéal non plus. Chaque fois que lesitemschangent, le composantListet ses composants enfants s'afficheront d'abord avec une valeur deselectionobsolète. Ensuite, React mettra à jour le DOM et exécutera les Effets. Enfin, l'appel àsetSelection(null)provoquera un nouveau rendu du composantListet de ses composants enfants, redémarrant tout ce processus.
Commencez par supprimer l'Effet. Ajustez plutôt l'état directement pendant le rendu :
Stocker des informations des rendus précédentsde cette manière peut être difficile à comprendre, mais c'est mieux que de mettre à jour le même état dans un Effet. Dans l'exemple ci-dessus,setSelectionest appelé directement pendant un rendu. React re-rendra le composantListimmédiatementaprès sa sortie avec une instructionreturn. React n'a pas encore rendu les enfants deListni mis à jour le DOM, ce qui permet aux enfants deListd'éviter de rendre la valeur obsolète deselection.
Lorsque vous mettez à jour un composant pendant le rendu, React ignore le JSX retourné et retente immédiatement le rendu. Pour éviter des cascades de tentatives très lentes, React ne vous permet de mettre à jour que l'état dumêmecomposant pendant un rendu. Si vous mettez à jour l'état d'un autre composant pendant un rendu, vous verrez une erreur. Une condition commeitems !== prevItemsest nécessaire pour éviter les boucles. Vous pouvez ajuster l'état ainsi, mais tout autre effet secondaire (comme modifier le DOM ou définir des minuteries) doit rester dans les gestionnaires d'événements ou les Effets pourgarder les composants purs.
Bien que ce modèle soit plus efficace qu'un Effet, la plupart des composants ne devraient pas en avoir besoin non plus.Peu importe comment vous le faites, ajuster l'état en fonction des props ou d'un autre état rend votre flux de données plus difficile à comprendre et à déboguer. Vérifiez toujours si vous pouvezréinitialiser tout l'état avec une cléoutout calculer pendant le renduà la place. Par exemple, au lieu de stocker (et de réinitialiser) l'élémentsélectionné, vous pouvez stocker l'ID de l'élément sélectionné :
Il n'est plus nécessaire de « ajuster » l'état du tout. Si l'élément avec l'ID sélectionné est dans la liste, il reste sélectionné. S'il ne l'est pas, laselectioncalculée pendant le rendu seranullcar aucun élément correspondant n'a été trouvé. Ce comportement est différent, mais sans doute meilleur car la plupart des changements apportés àitemspréservent la sélection.
Partager la logique entre les gestionnaires d'événements
Imaginons que vous ayez une page produit avec deux boutons (Acheter et Paiement) qui permettent tous deux d'acheter ce produit. Vous souhaitez afficher une notification chaque fois que l'utilisateur met le produit dans le panier. AppelershowNotification()dans les gestionnaires de clic des deux boutons semble redondant, vous pourriez donc être tenté de placer cette logique dans un Effet :
Cet Effet est inutile. Il causera aussi très probablement des bugs. Par exemple, supposons que votre application « se souvienne » du panier entre les rechargements de page. Si vous ajoutez un produit au panier une fois et que vous rafraîchissez la page, la notification réapparaîtra. Elle continuera d'apparaître chaque fois que vous rafraîchirez la page de ce produit. C'est parce queproduct.isInCartsera déjàtrueau chargement de la page, donc l'Effet ci-dessus appellerashowNotification().
Lorsque vous n'êtes pas sûr qu'un code doive être dans un Effet ou dans un gestionnaire d'événements, demandez-vouspourquoice code doit s'exécuter. Utilisez les Effets uniquement pour le code qui doit s'exécuterparce quele composant a été affiché à l'utilisateur.Dans cet exemple, la notification doit apparaître parce que l'utilisateura appuyé sur le bouton, et non parce que la page a été affichée ! Supprimez l'Effet et placez la logique partagée dans une fonction appelée depuis les deux gestionnaires d'événements :
Cela supprime à la fois l'Effet inutile et corrige le bug.
Envoyer une requête POST
Ce composantFormenvoie deux types de requêtes POST. Il envoie un événement d'analyse lorsqu'il est monté. Lorsque vous remplissez le formulaire et cliquez sur le bouton Soumettre, il enverra une requête POST au point de terminaison/api/register :
Appliquons les mêmes critères que dans l'exemple précédent.
La requête POST d'analyse doit rester dans un Effet. C'est parce que laraisond'envoyer l'événement d'analyse est que le formulaire a été affiché. (Il se déclencherait deux fois en développement, maisvoir icipour savoir comment gérer cela.)
Cependant, la requête POST vers/api/registern'est pas causée par le fait que le formulaire soitaffiché. Vous ne voulez envoyer la requête qu'à un moment précis : lorsque l'utilisateur appuie sur le bouton. Cela ne devrait se produire quelors de cette interaction particulière. Supprimez le second Effet et déplacez cette requête POST dans le gestionnaire d'événements :
Lorsque vous choisissez de placer une logique dans un gestionnaire d'événements ou dans un Effet, la question principale à laquelle vous devez répondre est dequel type de logiqueil s'agit du point de vue de l'utilisateur. Si cette logique est causée par une interaction particulière, gardez-la dans le gestionnaire d'événements. Si elle est causée par le fait que l'utilisateurvoitle composant à l'écran, gardez-la dans l'Effet.
Chaînes de calculs
Parfois, vous pourriez être tenté d'enchaîner des Effets qui ajustent chacun un morceau d'état en fonction d'un autre état
Ce code présente deux problèmes.
Le premier problème est qu'il est très inefficace : le composant (et ses enfants) doivent être rerendus entre chaque appelsetde la chaîne. Dans l'exemple ci-dessus, dans le pire des cas (setCard→ rendu →setGoldCardCount→ rendu →setRound→ rendu →setIsGameOver→ rendu), il y a trois rerendus inutiles de l'arbre situé en dessous.
Le second problème est que, même s'il n'était pas lent, au fur et à mesure que votre code évolue, vous rencontrerez des cas où la « chaîne » que vous avez écrite ne correspond plus aux nouvelles exigences. Imaginez que vous ajoutez un moyen de parcourir l'historique des coups du jeu. Vous le feriez en mettant à jour chaque variable d'état avec une valeur passée. Cependant, définir l'étatcardsur une valeur passée déclencherait à nouveau la chaîne d'Effets et modifierait les données que vous affichez. Un tel code est souvent rigide et fragile.
Dans ce cas, il est préférable de calculer ce que vous pouvez pendant le rendu et d'ajuster l'état dans le gestionnaire d'événements :
C'est beaucoup plus efficace. De plus, si vous implémentez un moyen de visualiser l'historique du jeu, vous pourrez maintenant définir chaque variable d'état sur un coup passé sans déclencher la chaîne d'Effets qui ajuste toutes les autres valeurs. Si vous avez besoin de réutiliser la logique entre plusieurs gestionnaires d'événements, vous pouvezextraire une fonctionet l'appeler depuis ces gestionnaires.
N'oubliez pas qu'à l'intérieur des gestionnaires d'événements,l'état se comporte comme un instantané.Par exemple, même après avoir appelésetRound(round + 1), la variableroundreflétera la valeur au moment où l'utilisateur a cliqué sur le bouton. Si vous avez besoin d'utiliser la valeur suivante pour des calculs, définissez-la manuellement commeconst nextRound = round + 1.
Dans certains cas, vousne pouvez pascalculer l'état suivant directement dans le gestionnaire d'événements. Par exemple, imaginez un formulaire avec plusieurs menus déroulants où les options du menu suivant dépendent de la valeur sélectionnée du menu précédent. Dans ce cas, une chaîne d'Effets est appropriée car vous vous synchronisez avec le réseau.
Initialisation de l'application
Certaines logiques ne doivent s'exécuter qu'une seule fois au chargement de l'application.
Vous pourriez être tenté de les placer dans un Effet du composant de plus haut niveau :
Cependant, vous découvrirez rapidement qu'ils'exécute deux fois en développement.Cela peut causer des problèmes — par exemple, il pourrait invalider le jeton d'authentification car la fonction n'a pas été conçue pour être appelée deux fois. En général, vos composants doivent être résistants au remontage. Cela inclut votre composant de plus haut niveauApp.
Bien qu'il ne soit peut-être jamais remonté en pratique en production, suivre les mêmes contraintes dans tous les composants facilite le déplacement et la réutilisation du code. Si une logique doit s'exécuterune fois par chargement d'applicationplutôt queune fois par montage de composant, ajoutez une variable de haut niveau pour suivre si elle a déjà été exécutée :
Vous pouvez également l'exécuter pendant l'initialisation du module et avant le rendu de l'application :
Le code au niveau supérieur s'exécute une fois lorsque votre composant est importé — même s'il n'est finalement pas rendu. Pour éviter les ralentissements ou les comportements surprenants lors de l'importation de composants arbitraires, n'abusez pas de ce modèle. Réservez la logique d'initialisation à l'échelle de l'application aux modules de composants racine commeApp.jsou au point d'entrée de votre application.
Notifier les composants parents des changements d'état
Imaginons que vous écrivez un composantToggleavec un état interneisOnqui peut être soittruesoitfalse. Il existe plusieurs façons de le basculer (par clic ou par glisser). Vous souhaitez notifier le composant parent chaque fois que l'état interne duTogglechange, vous exposez donc un événementonChangeet l'appelez depuis un Effet :
Comme précédemment, ce n'est pas idéal. Le composantTogglemet d'abord à jour son état, et React met à jour l'écran. Ensuite, React exécute l'Effet, qui appelle la fonctiononChangepassée depuis un composant parent. Maintenant, le composant parent mettra à jour son propre état, déclenchant un autre cycle de rendu. Il serait préférable de tout faire en un seul cycle.
Supprimez l'Effet et mettez plutôt à jour l'état desdeuxcomposants au sein du même gestionnaire d'événements :
Avec cette approche, le composantToggleet son composant parent mettent tous deux à jour leur état pendant l'événement. Reactregroupe les mises à jourde différents composants, donc il n'y aura qu'un seul cycle de rendu.
Vous pourriez aussi peut-être supprimer l'état complètement, et plutôt recevoirisOndepuis le composant parent :
« Remonter l'état »permet au composant parent de contrôler entièrement leToggleen basculant son propre état. Cela signifie que le composant parent devra contenir plus de logique, mais il y aura globalement moins d'état à gérer. Chaque fois que vous essayez de garder deux variables d'état différentes synchronisées, essayez plutôt de remonter l'état !
Passer des données au parent
Ce composantChildrécupère des données puis les passe au composantParentdans un Effet :
Dans React, les données circulent des composants parents vers leurs enfants. Lorsque vous voyez quelque chose d’incorrect à l’écran, vous pouvez remonter la chaîne des composants pour trouver d’où vient l’information, jusqu’à identifier quel composant passe une prop erronée ou a un état incorrect. Lorsque les composants enfants mettent à jour l’état de leurs parents dans des Effets, le flux de données devient très difficile à suivre. Puisque l’enfant et le parent ont besoin des mêmes données, laissez le composant parent récupérer ces données, etpassez-les vers le basà l’enfant :
C’est plus simple et maintient le flux de données prévisible : les données descendent du parent vers l’enfant.
S’abonner à un store externe
Parfois, vos composants peuvent avoir besoin de s’abonner à des données en dehors de l’état React. Ces données peuvent provenir d’une bibliothèque tierce ou d’une API navigateur intégrée. Comme ces données peuvent changer sans que React le sache, vous devez abonner manuellement vos composants à celles-ci. Cela se fait souvent avec un Effet, par exemple :
Ici, le composant s’abonne à un store de données externe (dans ce cas, l’API navigateurnavigator.onLine). Comme cette API n’existe pas côté serveur (elle ne peut donc pas être utilisée pour le HTML initial), l’état est initialement défini surtrue. Chaque fois que la valeur de ce store de données change dans le navigateur, le composant met à jour son état.
Bien qu’il soit courant d’utiliser des Effets pour cela, React dispose d’un Hook dédié pour s’abonner à un store externe, qui est préféré. Supprimez l’Effet et remplacez-le par un appel àuseSyncExternalStore:
Cette approche est moins sujette aux erreurs que la synchronisation manuelle de données mutables avec l’état React via un Effet. En général, vous écrirez un Hook personnalisé commeuseOnlineStatus()ci-dessus pour éviter de répéter ce code dans chaque composant.En savoir plus sur l’abonnement à des stores externes depuis des composants React.
Récupération de données
De nombreuses applications utilisent des Effets pour lancer la récupération de données. Il est assez courant d’écrire un Effet de récupération de données comme celui-ci :
Vousn’avez pasbesoin de déplacer cet appel fetch dans un gestionnaire d’événement.
Cela peut sembler contradictoire avec les exemples précédents où vous deviez mettre la logique dans les gestionnaires d’événements ! Cependant, considérez que ce n’est pasl’événement de frappequi est la principale raison de récupérer les données. Les champs de recherche sont souvent préremplis depuis l’URL, et l’utilisateur pourrait naviguer en arrière et en avant sans toucher au champ.
Peu importe d’où viennentpageetquery. Tant que ce composant est visible, vous voulez garder lesresultssynchroniséesavec les données du réseau pour lapageet laqueryactuelles. C’est pourquoi c’est un Effet.
Cependant, le code ci-dessus a un bug. Imaginez que vous tapez"hello"rapidement. Alors laqueryva changer de"h", à"he","hel","hell", et"hello". Cela déclenchera des appels fetch séparés, mais il n’y a aucune garantie quant à l’ordre d’arrivée des réponses. Par exemple, la réponse pour"hell"pourrait arriveraprèscelle pour"hello". Comme elle appellerasetResults()en dernier, vous afficherez les mauvais résultats de recherche. Cela s’appelle une« condition de concurrence »: deux requêtes différentes ont « couru » l’une contre l’autre et sont arrivées dans un ordre différent de celui attendu.
Pour corriger la condition de concurrence, vous devezajouter une fonction de nettoyagepour ignorer les réponses obsolètes :
Cela garantit que lorsque votre Effet récupère des données, toutes les réponses sauf la dernière demandée seront ignorées.
Gérer les conditions de concurrence n’est pas la seule difficulté liée à l’implémentation de la récupération de données. Vous pourriez aussi vouloir réfléchir à la mise en cache des réponses (pour que l’utilisateur puisse cliquer sur Retour et voir l’écran précédent instantanément), à la façon de récupérer les données côté serveur (pour que le HTML rendu initialement par le serveur contienne le contenu récupéré au lieu d’un indicateur de chargement), et à la façon d’éviter les cascades réseau (pour qu’un composant enfant puisse récupérer des données sans attendre chaque parent).
Ces problèmes s’appliquent à n’importe quelle bibliothèque d’interface utilisateur, pas seulement React. Les résoudre n’est pas trivial, c’est pourquoi lesframeworksmodernes fournissent des mécanismes de récupération de données intégrés plus efficaces que la récupération de données dans les Effets.
Si vous n’utilisez pas de framework (et ne voulez pas construire le vôtre) mais souhaitez rendre la récupération de données depuis les Effets plus ergonomique, envisagez d’extraire votre logique de récupération dans un Hook personnalisé comme dans cet exemple :
Vous voudrez probablement aussi ajouter une logique pour la gestion des erreurs et pour suivre si le contenu est en cours de chargement. Vous pouvez construire un Hook comme celui-ci vous-même ou utiliser l’une des nombreuses solutions déjà disponibles dans l’écosystème React.Bien que cela seul ne soit pas aussi efficace que d’utiliser le mécanisme de récupération de données intégré d’un framework, déplacer la logique de récupération de données dans un Hook personnalisé facilitera l’adoption d’une stratégie de récupération de données efficace plus tard.
En général, chaque fois que vous devez recourir à l’écriture d’Effets, restez attentif aux occasions où vous pouvez extraire une fonctionnalité dans un Hook personnalisé avec une API plus déclarative et spécialisée commeuseDataci-dessus. Moins vous avez d’appels bruts àuseEffectdans vos composants, plus il sera facile de maintenir votre application.
Récapitulatif
- Si vous pouvez calculer quelque chose pendant le rendu, vous n'avez pas besoin d'un Effet.
- Pour mettre en cache des calculs coûteux, ajoutez
useMemoau lieu deuseEffect. - Pour réinitialiser l'état de tout un arbre de composants, passez-lui une
keydifférente. - Pour réinitialiser un état spécifique en réponse à un changement de prop, définissez-le pendant le rendu.
- Le code qui s'exécute parce qu'un composant a étéaffichédoit être dans les Effets, le reste doit être dans les événements.
- Si vous devez mettre à jour l'état de plusieurs composants, il est préférable de le faire lors d'un seul événement.
- Chaque fois que vous essayez de synchroniser des variables d'état dans différents composants, envisagez de remonter l'état.
- Vous pouvez récupérer des données avec des Effets, mais vous devez implémenter un nettoyage pour éviter les conditions de concurrence.
Try out some challenges
Challenge 1 of 4:Transform data without Effects #
The TodoList below displays a list of todos. When the “Show only active todos” checkbox is ticked, completed todos are not displayed in the list. Regardless of which todos are visible, the footer displays the count of todos that are not yet completed.
Simplify this component by removing all the unnecessary state and Effects.
