Comment forcer le remontage sur les composants React?

103

Disons que j'ai un composant de vue qui a un rendu conditionnel:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

MyInput ressemble à ceci:

class MyInput extends React.Component {

    ...

    render(){
        return (
            <div>
                <input name={this.props.name} 
                    ref="input" 
                    type="text" 
                    value={this.props.value || null}
                    onBlur={this.handleBlur.bind(this)}
                    onChange={this.handleTyping.bind(this)} />
            </div>
        );
    }
}

Disons que employedc'est vrai. Chaque fois que je le change sur false et que l'autre vue est rendue, seule unemployment-durationest réinitialisée. unemployment-reasonObtient également prérempli avec la valeur de job-title(si une valeur a été donnée avant que la condition ne change).

Si je change le balisage dans la deuxième routine de rendu en quelque chose comme ceci:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Il semble que tout fonctionne bien. On dirait que React ne parvient tout simplement pas à différencier «titre du poste» et «raison du chômage».

S'il vous plaît dites-moi ce que je fais de mal ...

o01
la source
1
Quel est le data-reactidsur chacun des éléments MyInput(ou input, comme on le voit dans DOM) avant et après le employedcommutateur?
Chris
@Chris J'ai omis de spécifier que le composant MyInput rend l'entrée enveloppée dans un fichier <div>. Les data-reactidattributs semblent être différents à la fois sur le div d'encapsulation et sur le champ d'entrée. job-titlel'entrée obtient l'id data-reactid=".0.1.1.0.1.0.1", tandis que l' unemployment-reasonentrée obtient l'iddata-reactid=".0.1.1.0.1.2.1"
o01
1
et qu'en est-il unemployment-duration?
Chris
@Chris Désolé, j'ai parlé trop tôt. Dans le premier exemple (sans le span "diff me") les reactidattributs sont identiques sur job-titleet unemployment-reason, tandis que dans le second exemple (avec le diff span) ils sont différents.
o01
@Chris pour unemployment-durationl' reactidattribut est toujours unique.
o01

Réponses:

108

Ce qui se passe probablement, c'est que React pense qu'un seul MyInput( unemployment-duration) est ajouté entre les rendus. En tant que tel, job-titlejamais n'est remplacé par le unemployment-reason, ce qui explique également pourquoi les valeurs prédéfinies sont permutées.

Lorsque React effectue la comparaison, il déterminera quels composants sont nouveaux et lesquels sont anciens en fonction de leur keypropriété. Si aucune clé de ce type n'est fournie dans le code, elle générera la sienne.

La raison pour laquelle le dernier extrait de code que vous fournissez fonctionne est que React doit essentiellement changer la hiérarchie de tous les éléments sous le parent divet je pense que cela déclencherait un nouveau rendu de tous les enfants (c'est pourquoi cela fonctionne). Si vous aviez ajouté le spanvers le bas au lieu du haut, la hiérarchie des éléments précédents ne changerait pas et ces éléments ne seraient pas rendus (et le problème persisterait).

Voici ce que dit la documentation officielle de React :

La situation se complique lorsque les enfants sont mélangés (comme dans les résultats de recherche) ou si de nouveaux composants sont ajoutés au début de la liste (comme dans les flux). Dans ces cas où l'identité et l'état de chaque enfant doivent être conservés à travers les passes de rendu, vous pouvez identifier de manière unique chaque enfant en lui attribuant une clé.

Lorsque React réconcilie les enfants avec clé, il s'assurera que tout enfant avec clé sera réorganisé (au lieu d'être écrasé) ou détruit (au lieu d'être réutilisé).

Vous devriez être en mesure de résoudre ce problème en fournissant keyvous-même un élément unique au parent divou à tous les MyInputéléments.

Par exemple:

render(){
    if (this.state.employed) {
        return (
            <div key="employed">
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div key="notEmployed">
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

OU

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput key="title" ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
                <MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Maintenant, quand React fera la différence, il verra que les divssont différents et le restituera en incluant tous ses enfants (1er exemple). Dans le 2ème exemple, le diff sera un succès sur job-titleet unemployment-reasonpuisqu'ils ont maintenant des clés différentes.

Vous pouvez bien sûr utiliser toutes les clés de votre choix, à condition qu'elles soient uniques.


Mise à jour août 2017

Pour un meilleur aperçu du fonctionnement des clés dans React, je recommande fortement de lire ma réponse à Comprendre les clés uniques dans React.js .


Mise à jour novembre 2017

Cette mise à jour aurait dû être publiée il y a quelque temps, mais l'utilisation de chaînes littérales dans refest désormais obsolète. Par exemple ref="job-title"devrait maintenant être plutôt ref={(el) => this.jobTitleRef = el}(par exemple). Voir ma réponse à l' avertissement de dépréciation en utilisant this.refs pour plus d'informations.

Chris
la source
10
it will determine which components are new and which are old based on their key property.- c'était ... la clé ... à ma compréhension
Larry
1
Merci beaucoup ! J'avais également compris que tous les composants enfants d'une carte avaient été reconstitués avec succès et je ne pouvais pas comprendre pourquoi.
ValentinVoilean
un bon conseil est d'utiliser une variable d'état (qui change, comme un id par exemple) comme clé pour le div!
Macilias
200

Changez la clé du composant.

<Component key="1" />
<Component key="2" />

Le composant sera démonté et une nouvelle instance de Component sera montée car la clé a changé.

edit : Documenté sur vous n'avez probablement pas besoin d'un état dérivé :

Lorsqu'une clé change, React crée une nouvelle instance de composant plutôt que de mettre à jour l'actuelle. Les clés sont généralement utilisées pour les listes dynamiques mais sont également utiles ici.

Alex K
la source
45
Cela devrait être beaucoup plus évident dans la documentation React. Cela a réalisé exactement ce que je cherchais, mais il a fallu des heures pour le trouver.
Paul
4
Eh bien, cela m'a beaucoup aidé! Merci! J'avais besoin de réinitialiser une animation CSS, mais la seule façon de le faire est de forcer un nouveau rendu. Je conviens que cela devrait être plus évident dans la documentation de réaction
Alex. P.
@ Alex.P. Agréable! Les animations CSS peuvent parfois être délicates. Je serais intéressé de voir un exemple.
Alex K
1
Documentation React décrivant cette technique: reactjs.org/blog/2018/06/07/…
GameSalutes
Je vous remercie. Je suis très reconnaissant pour cela. J'ai essayé de fournir des nombres aléatoires à des accessoires non déclarés comme "randomNumber" mais le seul accessoire qui fonctionnait était la clé.
ShameWare
5

Utilisez setStatedans votre vue pour changer la employedpropriété de l'état. Ceci est un exemple de moteur de rendu React.

 someFunctionWhichChangeParamEmployed(isEmployed) {
      this.setState({
          employed: isEmployed
      });
 }

 getInitialState() {
      return {
          employed: true
      }
 },

 render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}
Dmitriy
la source
Je fais déjà ça. La variable «employée» fait partie de l'état des composants de la vue, et elle est modifiée via sa fonction setState. Désolé de ne pas avoir expliqué cela.
o01
La variable «employée» doit être la propriété de l'état de React. Ce n'est pas évident dans votre exemple de code.
Dmitriy
comment changez-vous cette.state.employed? Montrez le code, s'il vous plaît. Êtes-vous sûr d'utiliser setState à la bonne place dans votre code?
Dmitriy
Le composant de vue est un peu plus complexe que mon exemple le montre. En plus des composants MyInput, il rend également un groupe de boutons radio qui contrôle la valeur de «used» avec un gestionnaire onChange. Dans le gestionnaire onChange, j'ai défini l'état du composant de vue en conséquence. La vue est restituée correctement lorsque la valeur du groupe de boutons radio change, mais React pense que le premier composant MyInput est le même qu'avant le changement de «utilisé».
o01
0

Je travaille sur Crud pour mon application. C'est comme ça que je l'ai fait J'ai Reactstrap comme dépendance.

import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';

const LifeActionCreate = () => {
  let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();

  const onCreate = e => {
    const db = firebase.firestore();

    db.collection('actions').add({
      label: newLifeActionLabel
    });
    alert('New Life Insurance Added');
    setNewLifeActionLabel('');
  };

  return (
    <Card style={{ padding: '15px' }}>
      <form onSubmit={onCreate}>
        <label>Name</label>
        <input
          value={newLifeActionLabel}
          onChange={e => {
            setNewLifeActionLabel(e.target.value);
          }}
          placeholder={'Name'}
        />

        <Button onClick={onCreate}>Create</Button>
      </form>
    </Card>
  );
};

Quelques React Hooks là-dedans

Ruben Verster
la source
Bienvenue à SO! Vous voudrez peut-être revoir comment rédiger une bonne réponse . Ajoutez des explications sur la façon dont vous avez résolu la question au-delà de la simple publication du code.
displacedtexan