Réagir: pourquoi le composant enfant ne se met pas à jour lorsque le prop change

135

Pourquoi, dans l'exemple de pseudo-code suivant, Child ne s'affiche pas à nouveau lorsque Container change foo.bar?

Container {
  handleEvent() {
    this.props.foo.bar = 123
  },

  render() {
    return <Child bar={this.props.foo.bar} />
}

Child {
  render() {
    return <div>{this.props.bar}</div>
  }
}

Même si j'appelle forceUpdate()après avoir modifié la valeur dans Container, Child affiche toujours l'ancienne valeur.

Tuomas Toivonen
la source
5
Est-ce votre code? On dirait que ce n'est pas un code React valide
Je pense que la valeur des accessoires ne devrait pas changer dans le composant du conteneur au lieu de cela, elle devrait être modifiée dans le composant parent par setState et cet état devrait être mappé aux accessoires des conteneurs
Piyush Patel
Utilisez un opérateur de diffusion comme celui-ci <Child bar = {... this.props.foo.bar} />
Sourav Singh
@AdrianWydmanski et les 5 autres personnes qui ont voté pour: en.wikipedia.org/wiki/Pseudocode
David Newcomb
Les accessoires @PiyushPatel sont mis à jour lorsqu'un composant est re-rendu sur place, comme le montre l'exemple de pseudo-code. Un autre exemple de ceci est avec quelque chose comme using <Route exact path="/user/:email" component={ListUserMessagePage} />, un lien sur la même page mettra à jour les accessoires sans créer une nouvelle instance et exécuter les événements habituels de cycle de vie.
David Newcomb

Réponses:

94

Mettez à jour l'enfant pour que l'attribut «clé» soit égal au nom. Le composant sera rendu à chaque fois que la clé change.

Child {
  render() {
    return <div key={this.props.bar}>{this.props.bar}</div>
  }
}
polina-c
la source
5
Merci pour cela. J'utilisais redux store et je ne pouvais pas obtenir le rendu de mon composant jusqu'à ce que j'aie ajouté une clé. (J'ai utilisé la bibliothèque uuid pour générer une clé aléatoire, et cela a fonctionné).
Maiya
En utilisant des hooks, mon enfant ne rendrait pas à nouveau lors de la définition de l'état sur un tableau existant. Mon bidouille (probablement une mauvaise idée) était de mettre en même temps l'état d'un accessoire factice et l' ajouter à la matrice de dépendance useEffect: [props.notRenderingArr, props.dummy]. Si la longueur du tableau change toujours, vous pouvez définir le tableau de dépendances useEffect sur [props.notRenderingArr.length].
hipnosis
5
c'était aussi ce dont j'avais besoin. Je suis perplexe, quand est-ce keyobligatoire, vs juste setState? J'ai des enfants qui dépendent de l'état de leurs parents, mais ils ne déclenchent pas de rendu ...
dcsan
Très bonne réponse. Mais pourquoi ce comportement?
Rishav
1
J'utilisais l'index comme clé mais cela ne fonctionnait pas correctement. Maintenant j'utilise uuid et c'est parfait :) Merci à @Maiya
Ali Rehman
90

Parce que les enfants ne sont pas rendus si les accessoires du parent changent, mais si son STATE change :)

Ce que vous montrez est le suivant: https://facebook.github.io/react/tips/communicate-between-components.html

Il transmettra les données du parent à l'enfant via des accessoires, mais il n'y a pas de logique de rendu.

Vous devez définir un état pour le parent, puis rendre l'enfant à l'état de changement de parent. Cela pourrait aider. https://facebook.github.io/react/tips/expose-component-functions.html

François Richard
la source
5
Pourquoi setState provoquera-t-il un nouveau rendu mais pas forcerUpdate? Également peu hors sujet, mais comment les composants sont-ils mis à jour lorsque les accessoires sont passés de redux et que son état est mis à jour par l'action?
Tuomas Toivonen
les accessoires ne sont pas mis à jour même si vous mettez à jour le composant, les accessoires que vous avez passés sont toujours là. Flux est un autre sujet oui :)
François Richard
3
state! = accessoires dont vous avez besoin pour en savoir plus à ce sujet. Vous ne faites pas de mise à jour avec les accessoires
François Richard
vous pouvez le voir directement dans le code source, ce n'est tout simplement pas le même processus
Webwoman
donc ce que vous avez dit ici a été changé ou je ne l'ai pas bien compris, j'ai posé une question à ce sujet, au fait, stackoverflow.com/questions/58903921/…
ILoveReactAndNode
51

J'ai eu le même problème. C'est ma solution, je ne suis pas sûr que ce soit la bonne pratique, dites-moi si non:

state = {
  value: this.props.value
};

componentDidUpdate(prevProps) {
  if(prevProps.value !== this.props.value) {
    this.setState({value: this.props.value});
  }
}

UPD: Vous pouvez maintenant faire la même chose en utilisant React Hooks: (uniquement si le composant est une fonction)

const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
Petrichor
la source
3
C'est définitivement la bonne réponse, sans parler de la plus simple
Guilherme Ferreira
1
J'utilise également cette approche.
rawsly
@ vancy-pants Le componentDidUpdatedans ce cas va dans le composant Child.
Nick Friskel
4
Vous n'avez pas besoin et ne devez pas transformer les accessoires en état dans un composant enfant. Utilisez simplement les accessoires directement. Il FunctionComponentsmettra automatiquement à jour les composants enfants si les accessoires changent.
oemera
1
Cette approche fonctionne également lorsque vous avez des accessoires dans des composants enfants dérivés de l'état parent
Chefk5
10

Selon la philosophie de React, le composant ne peut pas changer ses accessoires. ils doivent être reçus du parent et doivent être immuables. Seul le parent peut changer les accessoires de ses enfants.

belle explication sur l' état vs les accessoires

aussi, lisez ce fil Pourquoi ne puis-je pas mettre à jour les accessoires dans react.js?

Sujan Thakare
la source
Cela ne signifie-t-il pas également que le conteneur ne peut pas modifier les accessoires qu'il reçoit du magasin redux?
Tuomas Toivonen
3
Container ne reçoit pas les accessoires directement du magasin, ils sont fournis en lisant une partie d'un arbre d'état redux (déroutant ??). !! la confusion vient du fait que nous ne connaissons pas la fonction magique connect par react-redux. si vous voyez le code source de la méthode de connexion, il crée essentiellement un composant d'ordre supérieur qui a un état avec la propriété storeState et est abonné au magasin redux. lorsque storeState change, tout le rendu a lieu et les accessoires modifiés sont fournis au conteneur en lisant l'état modifié. j'espère que cela répond à la question
Sujan Thakare
Bonne explication, penser à un composant d'ordre supérieur m'aide à comprendre ce qui se passe
Tuomas Toivonen
7

Vous devez utiliser la setStatefonction. Sinon, l'état ne sauvegardera pas votre modification, quelle que soit la manière dont vous utilisez forceUpdate.

Container {
    handleEvent= () => { // use arrow function
        //this.props.foo.bar = 123
        //You should use setState to set value like this:
        this.setState({foo: {bar: 123}});
    };

    render() {
        return <Child bar={this.state.foo.bar} />
    }
    Child {
        render() {
            return <div>{this.props.bar}</div>
        }
    }
}

Votre code ne semble pas valide. Je ne peux pas tester ce code.

JamesYin
la source
Cela ne devrait-il pas être le cas <Child bar={this.state.foo.bar} />?
Josh Broadhurst
Droite. Je mets à jour la réponse. Mais comme je l'ai dit, ce code n'est peut-être pas valide. Copiez simplement la question.
JamesYin
5

Lors de la création de composants React à partir de functionset useState.

const [drawerState, setDrawerState] = useState(false);

const toggleDrawer = () => {
      // attempting to trigger re-render
      setDrawerState(!drawerState);
};

Ça ne marche pas

         <Drawer
            drawerState={drawerState}
            toggleDrawer={toggleDrawer}
         />

Cela fonctionne (ajout d'une clé)

         <Drawer
            drawerState={drawerState}
            key={drawerState}
            toggleDrawer={toggleDrawer}
         />
Nelles
la source
3

Confirmé, l'ajout d'une clé fonctionne. J'ai parcouru la documentation pour essayer de comprendre pourquoi.

React veut être efficace lors de la création de composants enfants. Il ne rendra pas un nouveau composant s'il est identique à un autre enfant, ce qui accélère le chargement de la page.

L'ajout d'une clé force React à rendre un nouveau composant, réinitialisant ainsi l'état pour ce nouveau composant.

https://reactjs.org/docs/reconciliation.html#recursing-on-children

tjr226
la source
2

Utilisez la fonction setState. Alors tu pourrais faire

       this.setState({this.state.foo.bar:123}) 

à l'intérieur de la méthode d'événement handle.

Une fois que l'état est mis à jour, il déclenchera des modifications et un nouveau rendu aura lieu.

Indraneel Bende
la source
1
Cela devrait être this.setState ({foo.bar:123}), non?
Charith Jayasanka le
0

Vous devriez probablement faire de l'enfant un composant fonctionnel s'il ne conserve aucun état et restitue simplement les accessoires, puis l'appelle depuis le parent. Une alternative à cela est que vous pouvez utiliser des hooks avec le composant fonctionnel (useState) qui provoquera le nouveau rendu du composant sans état.

Vous ne devez pas non plus modifier les propas car elles sont immuables. Maintenir l'état du composant.

Child = ({bar}) => (bar);
satyam soni
la source
0

Je rencontrais le même problème. J'avais un Tooltipcomposant qui recevait un showTooltipaccessoire, que je mettais à jour sur un Parentcomposant en fonction d'une ifcondition, il était mis à jour dans le Parentcomposant mais le Tooltipcomposant ne rendait pas.

const Parent = () => {
   let showTooltip = false;
   if(....){ showTooltip = true; }
   return(
      <Tooltip showTooltip={showTooltip}></Tooltip>
   )
}

L'erreur que je faisais est de déclarer showTooltipcomme un let.

J'ai réalisé ce que je faisais mal, je violais les principes du fonctionnement du rendu, le remplacer par des crochets a fait le travail.

const [showTooltip, setShowTooltip] =  React.useState<boolean>(false);
Divyanshu Rawat
la source
0

définir les accessoires modifiés dans mapStateToProps de la méthode de connexion dans le composant enfant.

function mapStateToProps(state) {
  return {
    chanelList: state.messaging.chanelList,
  };
}

export default connect(mapStateToProps)(ChannelItem);

Dans mon cas, le canal de channelList est mis à jour donc j'ai ajouté chanelList dans mapStateToProps

Rajesh Nasit
la source
0
export default function DataTable({ col, row }) {
  const [datatable, setDatatable] = React.useState({});
  useEffect(() => {
    setDatatable({
      columns: col,
      rows: row,
    });
  /// do any thing else 
  }, [row]);

  return (
    <MDBDataTableV5
      hover
      entriesOptions={[5, 20, 25]}
      entries={5}
      pagesAmount={4}
      data={datatable}
    />
  );
}

cet exemple utilise useEffectpour changer d'état lors d'un propschangement.

ibrahim alsimiry
la source