Redux: Les composants ne sont pas re-rendus avec connect ()

Créé le 20 août 2015  ·  32Commentaires  ·  Source: reduxjs/redux

J'apprends à connaître Redux en ayant lu la documentation et je commence simplement à modifier l'exemple du monde réel. Dans mon cas, j'ai échangé les appels de l'API HTTP GitHub pour les miens et j'ai échangé les concepts Repo et User avec des concepts analogues dans mon environnement. Jusqu'à présent, cela ne devrait pas être trop différent, non?

Voici où je rencontre des problèmes:

Mes appels d'API réussissent, la réponse JSON devient camelCased et normalisée, _mais_ mon composant n'est pas re-rendu une fois que la demande d'API s'est produite avec succès. Peu de temps après que USER_REQUEST soit distribué et que le composant User se mette à jour pour afficher "Loading ...", USER_SUCCESS est distribué et son état suivant contient une clé entities renseignée:

pasted_image_8_19_15__3_27_pm

Cela signifierait que le problème est dans le middleware, non? Je devrais stocker les entités de l'action dans le magasin, et le composant devrait observer le changement et effectuer un nouveau rendu, oui?

Où, dans l'exemple du monde réel, les données récupérées et normalisées avec succès sont-elles _actuellement_ placées dans le magasin? Je vois où cela est normalisé, mais pas où le résultat de cette normalisation est livré au magasin.

Merci beaucoup!

question

Commentaire le plus utile

Les données sont définies / mises à jour / supprimées dans le magasin via les résultats des actions de gestion dans les réducteurs. Les réducteurs reçoivent l'état actuel d'une tranche de votre application et s'attendent à récupérer un nouvel état. L'une des raisons les plus courantes pour lesquelles vos composants peuvent ne pas être de nouveau rendus est que vous modifiez l'état existant dans votre réducteur au lieu de renvoyer une nouvelle copie d'état avec les modifications nécessaires (consultez la section Dépannage ). Lorsque vous modifiez directement l'état existant, Redux ne détecte pas de différence d'état et ne notifie pas à vos composants que le magasin a changé.

Je vais donc certainement vérifier vos réducteurs et m'assurer que vous ne modifiez pas l'état existant. J'espère que ça t'as aidé!

Tous les 32 commentaires

Les données sont définies / mises à jour / supprimées dans le magasin via les résultats des actions de gestion dans les réducteurs. Les réducteurs reçoivent l'état actuel d'une tranche de votre application et s'attendent à récupérer un nouvel état. L'une des raisons les plus courantes pour lesquelles vos composants peuvent ne pas être de nouveau rendus est que vous modifiez l'état existant dans votre réducteur au lieu de renvoyer une nouvelle copie d'état avec les modifications nécessaires (consultez la section Dépannage ). Lorsque vous modifiez directement l'état existant, Redux ne détecte pas de différence d'état et ne notifie pas à vos composants que le magasin a changé.

Je vais donc certainement vérifier vos réducteurs et m'assurer que vous ne modifiez pas l'état existant. J'espère que ça t'as aidé!

En plus de ce que @ernieturner a dit, assurez-vous que la fonction mapStateToProps de votre fonction connect(mapStateToProps)(YourConnectedComponent) mappe l'état pertinent (dans ce cas entities ) pour que le composant connecté écoute à cette tranche de l’État.

Gardez également à l'esprit que connect() fait une comparaison superficielle: https://github.com/rackt/react-redux/blob/d5bf492ee35ad1be8ffd5fa6be689cd74df3b41e/src/components/createConnect.js#L91

Il est probable que, comme la forme de votre état connecté n'a pas changé, il suppose de ne pas faire de mise à jour.

Ah, compris! Mon mapStateToProps essayait d'extraire un objet de l'état, mais ne spécifiait pas la clé correcte. Au lieu d'extraire entities.users[login] , je l'ai mis à entities.users[id] . L'objet entities.users été saisi par la chaîne login , donc lorsque le numérique id n'a pas été trouvé, aucun accessoire n'a réellement changé, donc aucun rendu n'a été nécessaire.

Merci à tous pour l'aide! J'apprécie vraiment le temps que vous avez pris pour aider.

@timdorr
C'est ridicule. Comment forcer connect() vérifier les changements profonds? Sinon, il faut créer des dizaines de composants de conteneur pour chaque niveau d'état plus profond. Ce n'est pas normal

Vous devez absolument avoir de nombreux composants connectés dans toute application de taille raisonnable. Si vous avez un seul composant connecté de niveau supérieur, vous allez restituer inutilement de grandes parties de votre application à chaque fois que l'état change. Et vous éviterez l'une des principales optimisations de react-redux, qui consiste à ne re-rendre que lorsque l'état auquel il est abonné change.

Si vous devez évaluer une requête d'état complexe, utilisez reselect pour accélérer cela.

@timdorr @wzup

Il peut être utile de préciser que la connexion aux endroits inférieurs de l'arbre n'est pas strictement nécessaire pour résoudre le problème de comparaison approfondie.

La connexion à des endroits inférieurs dans votre arborescence de composants peut en effet aider avec _performance_, mais ce n'est pas la solution fondamentale au problème de comparaison profonde. La solution aux problèmes de comparaison profonde est que vos réducteurs mettent à jour l'état de manière immuable, de sorte qu'une comparaison superficielle soit adéquate pour savoir quand un élément d'état plus profond a changé.

En d'autres termes, la connexion d'un composant de niveau supérieur à un grand élément d'état imbriqué fonctionne très bien _ même avec des comparaisons superficielles_, tant que vos réducteurs renvoient un nouvel état sans muter les objets d'état existants.

@naw Merci pour cette astuce. Sur cette base, j'ai fini par ajouter un nouveau réducteur où je rencontrais ce problème:

const reducers = combineReducers({
    //...bunch of reducers which always return objects of 3 or 4 properties that change sometimes but
    // the shape stays the same
    dataVersion: (state = Symbol(), action = {}) => {
        switch (action.type) {
            case SAME_ACTION_AS_THE_ONE_THAT_UPDATES_A_COMPLEX_PROPERTY:
            case ANOTHER_ACTION_THAT_ALSO_UPDATES_A_COMPLEX_PROPERTY:
                return Symbol():
            default:
                return state;
        }
    }
})

Cela me semble toujours étrange, cependant. Si j'ai un réducteur qui va:

    switch (action.type) {
       case UPDATE_THIS:
           return {a: action.a, b: action.b, c: action.c};
       default:
           return state;
     }

le fait que je retourne un objet nouvellement initialisé qui se trouve être la même forme que l'état précédent devrait être suffisant pour déclencher un previousState !== newState . Si je suis censé renvoyer des données immuables à chaque fois, il semble qu'il soit plus inefficace de faire une comparaison superficielle de la forme plutôt que de simplement l'identité. (En supposant que c'est ce que Redux fait réellement, c'est-à-dire. Le fait que ma solution Symbol contournement

Dans ce cas particulier, l'ajout de composants de conteneur supplémentaires ne résout pas le problème. Malheureusement, je ne peux pas créer de lien vers un projet github complet pour clarification, car il s'agit du contenu de source fermée de mon entreprise dont je traite ici.

@Zacqary La comparaison de l'état actuel à l'état précédent se fait avec un égal strict (===). La comparaison de stateProps (le résultat de mapStateToProps) est effectuée avec un égal peu profond.

@Zacqary

mapStateToProps "assemble" les données de votre composant connecté en accédant au magasin.

mapStateToProps extrait plusieurs pièces du magasin et les assigne aux clés d'un nouvel objet stateProps .

L'objet stateProps résultant sera nouveau à chaque fois (donc son identité change), donc nous ne pouvons pas simplement comparer l'identité d'objet de l'objet stateProps lui-même --- nous devons en descendre un. level et vérifiez l'identité d'objet de chaque valeur, c'est là que shallowEqual entre en jeu, comme l' a dit

Généralement, des problèmes surviennent lorsque vous écrivez des réducteurs de telle manière que l'identité d'objet d'un élément d'état _ne change pas_, alors que certains attributs imbriqués qu'il contient _ ne changent_. C'est un état en mutation plutôt que de renvoyer un nouvel état immuablement, ce qui fait que react-redux pense que rien n'a changé, alors qu'en fait, quelque chose a changé.

le fait que je retourne un objet nouvellement initialisé qui se trouve être la même forme que l'état précédent devrait être suffisant pour déclencher un previousState! == newState.

Si vous retournez un nouvel objet pour une tranche d'état et que mapStateToProps utilise cette tranche d'état comme valeur pour l'une de ses clés, alors react-redux verra que l'identité de l'objet a changé, et pas empêcher un nouveau rendu.

Y a-t-il quelque chose de spécifique qui ne fonctionne pas comme prévu? Je ne sais pas vraiment ce que vous entendez par votre solution Symbol contournement

Cela vaut la peine de se demander si le composant supplémentaire doit simplement être déplacé vers le bas dans le composant connected() pour hériter des accessoires. Je suis arrivé à ce problème en essayant de connect() deux composants, mais c'était une complexité inutile.

@ernieturner Je sais que c'est vieux, mais vous voudrez peut-être mettre à jour le lien de la section Dépannage .

À votre santé

J'ai eu ce problème, enfin pas exactement.
J'ai appliqué les propriétés à l'état, comme:

  constructor(props){
    this.state = {
      text: props.text
    }
  }

Et ça ne marchait pas. J'ai donc appliqué la valeur directement à partir d'accessoires comme

{this.props.text}

Et cela fonctionne très bien :)

@AlexanderKozhevin Sauf erreur de ma part, il manque à votre constructeur un super(props) .
Normalement, vous n'êtes même pas autorisé à utiliser this avant d'appeler super(props) .

@ cdubois-mh C'est vrai, merci pour la note.

J'ai écrit un réducteur de racine d'application personnalisé et j'ai rencontré ce problème.
Renvoyer une copie de l'état entier
{...state}
à la fin du réducteur de racine m'a aidé

@daedalius Voilà comment un réducteur est censé fonctionner.
Il doit renvoyer l'état si l'action n'est pas pertinente ou renvoyer une copie de l'état avec les valeurs modifiées attendues.

Si vous ne renvoyez pas de copie, react ne sera pas toujours en mesure de détecter qu'une valeur a changé.
(par exemple, si vous modifiez une entrée imbriquée)

Vérifiez cet état:

{
    "clients": [
        { "name": "John", "cid": 4578 },
        { "name": "Alex", "cid": 5492 },
        { "name": "Bob",  "cid": 254 }
    ]
}

Si vous modifiez le nom d'un client, mais ne renvoyez pas de clone de l'état, alors vous n'avez pas modifié l'état, vous avez modifié l'objet dans le tableau.

L'objet lui-même aura toujours la même référence qu'avant, donc react manquera le changement.
En retournant un clone, la référence de l'état lui-même sera désormais différente (puisqu'il s'agit d'un nouvel objet) et react lancera tout le processus de re-rendu.

Si je me trompe, veuillez me corriger, mais c'est ainsi que je comprends que les réducteurs fonctionnent.

Oui, c'est correct. De plus, @daedalius , notez que vous ne voulez pas toujours faire une copie superficielle de state - vous ne devriez le faire que si quelque chose a changé, sinon vous risquez de vous retrouver avec un nouveau rendu inutile de vos composants.

Forcer la mise à jour

<AfterConnect
    _forceUpdate={Symbol()}
/>

@BrookShuihuaLee Qu'est-ce que cela signifie? vous n'avez fourni aucune information ou explication sur votre morceau de code. Qu'est ce que ça fait? Pourquoi est-il pertinent pour la discussion actuelle?

@CedSharp La fonction shallowEqual retournera false, si nous définissons _forceUpdate = {Symbol ()}

@BrookShuihuaLee Ce serait génial si vous pouviez inclure une explication comme celle-ci dans votre première réponse, de cette façon les gens comprendront mieux ^^

@CedSharp ouais

@BrookShuihuaLee : Cela semble être une mauvaise idée. Pourquoi voudriez-vous faire cela?

@BrookShuihuaLee , je suis d'accord avec @markerikson .
Il y a une raison pour laquelle vous êtes censé ne PAS renvoyer un clone lorsque l'état n'a pas changé. À moins que vous ne puissiez fournir un exemple valide dans lequel votre solution semble nécessaire, je vous déconseille fortement de contourner le fonctionnement de Redux.

@CedSharp
@markerikson
Parce que le composant AfterConnect sera rendu fréquemment, par conception. Il est possible de le rendre une fois de plus lorsque son composant parent est en cours de rendu. Je ne veux pas faire tous les calculs dans le composant parent et définir des tonnes d'accessoires pour AfterConnect.

@BrookShuihuaLee , je suis désolé, mais en quoi votre composant est-il pertinent pour la discussion en cours?
Si je comprends bien, vous parlez de votre propre composant personnalisé? ForceUpdate est quelque chose de très fortement déconseillé dans le monde React et je ne le recommanderais pas comme solution.

Au lieu de cela, vous pouvez utiliser une solution observable où vous recherchez des changements ailleurs que dans l'état? Je ne sais pas pourquoi vous auriez besoin de restituer ce composant spécifique sans que son état ne soit modifié, mais si vous savez pourquoi il devrait être rendu, vous pourriez peut-être utiliser quelque chose comme mobx?
(https://github.com/mobxjs/mobx)

J'essaie de comprendre quelle est votre solution et pourquoi elle s'applique dans cette situation.

@CedSharp Je ne veux pas dire que tout le monde devrait utiliser forceUpdate. Mais la recommandation est une chose, le choix en est une autre. React conserve également le ReactComponent.prototype.forceUpdate pour les développeurs. Les gens ont besoin de choix.

J'essayais de donner un autre choix. Ce n'est peut-être pas le meilleur choix.

Pas optimal, mais j'ai rencontré ce problème parce que j'ai mal structuré l'état de mon application. J'ai contourné le problème de la comparaison superficielle en mettant simplement à jour un champ peu profond sur la tranche de l'état qui me causait des problèmes.

case SOME_ACTION:
      return {
        ...state,
        ts: (new Date).getTime(),
      };

IMO, ce n'est pas une bonne solution, mais pour quiconque est actuellement dans une liaison (c'est-à-dire que vous devez publier en fin de journée), c'est une solution rapide et vous n'avez pas à forcer un rendu ailleurs.

Le code doit être remanié pour qu'une comparaison superficielle, même sur un état profondément imbriqué, fonctionne.

L'État doit être immuable. Copiez en profondeur l'état dans le réducteur, puis la comparaison superficielle dans connect est suffisante et efficace.

Mon problème était d'avoir un objet avec des tableaux à l'intérieur. Comme il s'agissait d'une comparaison superficielle, il n'a jamais atteint les objets dans les tableaux enfants. Je l'ai résolu en utilisant list:state.obj.list plutôt que lists:state.obj dans mapStateToProps 👍🏼

Je viens de rencontrer ce problème et après avoir lu les deux premières réponses, j'ai vérifié mon réducteur et j'ai remarqué que j'avais une faute de frappe. J'ai renommé une propriété et mis à jour INITIAL_STATE mais j'ai oublié de changer la clé qui était mise à jour par cette action problématique.

Je mettais essentiellement à jour une clé aléatoire qui n'était pas utilisée dans le composant, donc évidemment, elle ne serait pas restituée lorsque cette clé aléatoire était mise à jour.

VÉRIFIEZ TYPOS DANS VOTRE RÉDUCTEUR! :)

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

mickeyreiss-visor picture mickeyreiss-visor  ·  3Commentaires

amorphius picture amorphius  ·  3Commentaires

rui-ktei picture rui-ktei  ·  3Commentaires

cloudfroster picture cloudfroster  ·  3Commentaires

CellOcean picture CellOcean  ·  3Commentaires