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.


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>
  }
}

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


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]);

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?


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.


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}
         />

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


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.


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);

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);

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


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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.