Comment mettre à jour l'état des parents dans React?


350

Ma structure se présente comme suit:

Component 1  

 - |- Component 2


 - - |- Component 4


 - - -  |- Component 5  

Component 3

Le composant 3 devrait afficher certaines données en fonction de l'état du composant 5. Étant donné que les accessoires sont immuables, je ne peux pas simplement enregistrer son état dans le composant 1 et le transmettre, non? Et oui, j'ai lu sur redux, mais je ne veux pas l'utiliser. J'espère qu'il est possible de le résoudre simplement en réagissant. Ai-je tort?


20
super-facile: passez la propriété parent-setState-Function via au composant enfant: <MyChildComponent setState = {p => {this.setState (p)}} /> Dans le composant enfant, appelez-le via this.props. setState ({myObj, ...});
Marcel Ennix

@MarcelEnnix, Votre commentaire me fait gagner du temps. Merci.
Dinith Minura

<MyChildComponent setState={(s,c)=>{this.setState(s, c)}} />si vous allez utiliser ce hack, assurez-vous de prendre en charge le rappel.
Barkermn01

4
Passer un rappel pour définir l'état du parent est une très mauvaise pratique qui pourrait entraîner des problèmes de maintenance. Il rompt l'encapsulation et rend les composants 2, 4 et 5 étroitement couplés à 1. Si vous suivez ce chemin, vous ne pourrez réutiliser aucun de ces composants enfants ailleurs. Il est préférable que vous ayez des accessoires spécifiques afin que les composants enfants puissent déclencher des événements chaque fois que quelque chose se produit, alors le composant parent traitera cet événement correctement.
Pato Loco

@MarcelEnnix, pourquoi les accolades autour this.setState(p)? J'ai essayé sans eux et cela semble fonctionner (je suis très nouveau sur React)
Biganon

Réponses:


678

Pour la communication enfant-parent, vous devez passer une fonction définissant l'état du parent à l'enfant, comme ceci

class Parent extends React.Component {
  constructor(props) {
    super(props)

    this.handler = this.handler.bind(this)
  }

  handler() {
    this.setState({
      someVar: 'some value'
    })
  }

  render() {
    return <Child handler = {this.handler} />
  }
}

class Child extends React.Component {
  render() {
    return <Button onClick = {this.props.handler}/ >
  }
}

De cette façon, l'enfant peut mettre à jour l'état du parent avec l'appel d'une fonction passée avec des accessoires.

Mais vous devrez repenser la structure de vos composants, car si je comprends bien, les composants 5 et 3 ne sont pas liés.

Une solution possible consiste à les envelopper dans un composant de niveau supérieur qui contiendra l'état des composants 1 et 3. Ce composant définira l'état de niveau inférieur via des accessoires.


6
pourquoi avez-vous besoin de this.handler = this.handler.bind (this) et pas seulement de la fonction de gestionnaire qui définit l'état?
chemook78

35
@ chemook78 dans les méthodes des classes ES6 React ne se lie pas automatiquement à la classe. Donc, si nous n'ajoutons pas this.handler = this.handler.bind(this)de constructeur, l' thisintérieur de la handlerfonction référencera la fermeture de la fonction, pas la classe. Si vous ne souhaitez pas lier toutes vos fonctions dans le constructeur, il existe deux autres façons de gérer cela en utilisant les fonctions fléchées. Vous pouvez simplement écrire le gestionnaire de clics en tant que onClick={()=> this.setState(...)}, ou vous pouvez utiliser les initialiseurs de propriété avec les fonctions de flèche comme décrit ici babeljs.io/blog/2015/06/07/react-on-es6-plus sous "Fonctions de flèche"
Ivan

1
Voici un exemple de ceci en action: plnkr.co/edit/tGWecotmktae8zjS5yEr?p=preview
Tamb

5
Tout cela a du sens, pourquoi e.preventDefault? et cela nécessite-t-il jquery?
Vincent Buscarello

1
Question rapide, cela n'autorise-t-il pas l'attribution d'un État local au sein de l'enfant?
ThisGuyCantEven

50

J'ai trouvé la solution de travail suivante pour passer l'argument de la fonction onClick de l'enfant au composant parent:

Version avec passage d'une méthode ()

//ChildB component
class ChildB extends React.Component {

    render() {

        var handleToUpdate  =   this.props.handleToUpdate;
        return (<div><button onClick={() => handleToUpdate('someVar')}>
            Push me
          </button>
        </div>)
    }
}

//ParentA component
class ParentA extends React.Component {

    constructor(props) {
        super(props);
        var handleToUpdate  = this.handleToUpdate.bind(this);
        var arg1 = '';
    }

    handleToUpdate(someArg){
            alert('We pass argument from Child to Parent: ' + someArg);
            this.setState({arg1:someArg});
    }

    render() {
        var handleToUpdate  =   this.handleToUpdate;

        return (<div>
                    <ChildB handleToUpdate = {handleToUpdate.bind(this)} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentA />,
        document.querySelector("#demo")
    );
}

Regardez JSFIDDLE

Version avec passage d'une fonction Flèche

//ChildB component
class ChildB extends React.Component {

    render() {

        var handleToUpdate  =   this.props.handleToUpdate;
        return (<div>
          <button onClick={() => handleToUpdate('someVar')}>
            Push me
          </button>
        </div>)
    }
}

//ParentA component
class ParentA extends React.Component { 
    constructor(props) {
        super(props);
    }

    handleToUpdate = (someArg) => {
            alert('We pass argument from Child to Parent: ' + someArg);
    }

    render() {
        return (<div>
            <ChildB handleToUpdate = {this.handleToUpdate} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentA />,
        document.querySelector("#demo")
    );
}

Regardez JSFIDDLE


Celui-ci est bien! pourriez-vous expliquer cette pièce: <ChildB handleToUpdate = {handleToUpdate.bind(this)} />Pourquoi avoir à relier à nouveau?
Dane

@Dane - vous devez lier le contexte de ceci pour être le parent afin que lorsqu'il thisest appelé à l'intérieur de l'enfant, se thisréfère à l'état du parent et non à celui de l'enfant. C'est la fermeture à son meilleur!
Casey

@Casey, mais ne le faisons-nous pas dans le constructeur? Et cela ne suffit-il pas ??
Dane

Vous avez tout à fait raison! J'ai manqué ça. Oui, si vous l'avez déjà fait dans le constructeur, alors vous êtes prêt à partir!
Casey

Vous êtes un compagnon de légende! Cela gardera les composants bien autonomes sans avoir à être obligé de créer des composants parents pour gérer les échanges d'état
adamj

13

Je tiens à remercier la réponse la plus votée de m'avoir donné l'idée de mon propre problème, essentiellement la variation de celui-ci avec la fonction flèche et la transmission des paramètres du composant enfant:

 class Parent extends React.Component {
  constructor(props) {
    super(props)
    // without bind, replaced by arrow func below
  }

  handler = (val) => {
    this.setState({
      someVar: val
    })
  }

  render() {
    return <Child handler = {this.handler} />
  }
}

class Child extends React.Component {
  render() {
    return <Button onClick = {() => this.props.handler('the passing value')}/ >
  }
}

J'espère que cela aide quelqu'un.


Quelle est la particularité de la fonction flèche par rapport à l'appel direct?
Ashish Kamble

@AshishKamble les thisfonctions de flèche dans se réfèrent au contexte du parent (c. Parent-à- d. Classe).
CPHPython

Ceci est une réponse en double. Vous pouvez ajouter un commentaire à la réponse acceptée et mentionner cette fonctionnalité expérimentale pour utiliser la fonction flèche en classe.
Arashsoft

10

J'aime la réponse concernant le passage des fonctions, c'est une technique très pratique.

D'un autre côté, vous pouvez également y parvenir en utilisant pub / sub ou en utilisant une variante, un répartiteur, comme le fait Flux . La théorie est super simple, demandez au composant 5 d'envoyer un message que le composant 3 écoute. Le composant 3 met alors à jour son état qui déclenche le nouveau rendu. Cela nécessite des composants avec état qui, selon votre point de vue, peuvent ou non être un anti-modèle. Je suis contre eux personnellement et préférerais que quelque chose d'autre écoute les dépêches et les changements d'état de haut en bas (Redux le fait, mais ajoute une terminologie supplémentaire).

import { Dispatcher } from flux
import { Component } from React

const dispatcher = new Dispatcher()

// Component 3
// Some methods, such as constructor, omitted for brevity
class StatefulParent extends Component {
  state = {
    text: 'foo'
  } 

  componentDidMount() {
    dispatcher.register( dispatch => {
      if ( dispatch.type === 'change' ) {
        this.setState({ text: 'bar' })
      }
    }
  }

  render() {
    return <h1>{ this.state.text }</h1>
  }
}

// Click handler
const onClick = event => {
  dispatcher.dispatch({
    type: 'change'
  })
}

// Component 5 in your example
const StatelessChild = props => {
  return <button onClick={ onClick }>Click me</button> 
}

Le bundle dispatcher avec Flux est très simple, il enregistre simplement les rappels et les appelle quand une expédition se produit, en passant par le contenu de l'envoi (dans l'exemple laconique ci-dessus, il n'y a pas payloadavec l'envoi, simplement un identifiant de message). Vous pouvez l'adapter très facilement au pub / sub traditionnel (par exemple en utilisant l'EventEmitter d'événements, ou une autre version) si cela vous semble plus logique.


Mes composants Reacts "s'exécutent" dans le navigateur comme dans un tutoriel officiel ( facebook.github.io/react/docs/tutorial.html ) J'ai essayé d'inclure Flux avec browserify, mais le navigateur dit que Dispatcher est introuvable :(
wklm

2
La syntaxe que j'ai utilisée était la syntaxe du module ES2016 qui nécessite une transpilation (j'utilise Babel , mais il y en a d'autres, la transformation babelify peut être utilisée avec browserify), elle transpile versvar Dispatcher = require( 'flux' ).Dispatcher
Matt Styles

8

J'ai trouvé la solution de travail suivante pour passer l'argument de la fonction onClick de l'enfant au composant parent avec param:

classe parent:

class Parent extends React.Component {
constructor(props) {
    super(props)

    // Bind the this context to the handler function
    this.handler = this.handler.bind(this);

    // Set some state
    this.state = {
        messageShown: false
    };
}

// This method will be sent to the child component
handler(param1) {
console.log(param1);
    this.setState({
        messageShown: true
    });
}

// Render the child component and set the action property with the handler as value
render() {
    return <Child action={this.handler} />
}}

classe enfant:

class Child extends React.Component {
render() {
    return (
        <div>
            {/* The button will execute the handler function set by the parent component */}
            <Button onClick={this.props.action.bind(this,param1)} />
        </div>
    )
} }

2
Quelqu'un peut-il dire si c'est une solution acceptable (particulièrement intéressé à passer le paramètre comme suggéré).
ilans

param1ne fait qu'être affiché sur la console et n'est pas assigné, il attribue toujourstrue
Ashish Kamble

Je ne peux pas parler de la qualité de la solution, mais cela réussit à passer le paramètre pour moi.
James

6

Chaque fois que vous avez besoin de communiquer entre un enfant et un parent à n'importe quel niveau, il est préférable d'utiliser le contexte . Dans le composant parent, définissez le contexte qui peut être invoqué par l'enfant, tel que

Dans le composant parent de votre composant de cas 3

static childContextTypes = {
        parentMethod: React.PropTypes.func.isRequired
      };

       getChildContext() {
        return {
          parentMethod: (parameter_from_child) => this.parentMethod(parameter_from_child)
        };
      }

parentMethod(parameter_from_child){
// update the state with parameter_from_child
}

Maintenant, dans le composant enfant (composant 5 dans votre cas), dites simplement à ce composant qu'il souhaite utiliser le contexte de son parent.

 static contextTypes = {
       parentMethod: React.PropTypes.func.isRequired
     };
render(){
    return(
      <TouchableHighlight
        onPress={() =>this.context.parentMethod(new_state_value)}
         underlayColor='gray' >   

            <Text> update state in parent component </Text>              

      </TouchableHighlight>
)}

vous pouvez trouver un projet de démonstration au repo


Je ne peux pas comprendre cette réponse, pouvez-vous expliquer plus à ce sujet
Ashish Kamble

5

Il semble que nous ne pouvons transmettre des données que du parent à l'enfant car react favorise le flux de données unidirectionnel, mais pour que le parent se mette à jour lorsqu'il se passe quelque chose dans son "composant enfant", nous utilisons généralement ce qu'on appelle une "fonction de rappel".

Nous passons la fonction définie dans le parent à l'enfant en tant que "accessoires" et appelons cette fonction à partir de l'enfant en la déclenchant dans le composant parent.


class Parent extends React.Component {
  handler = (Value_Passed_From_SubChild) => {
    console.log("Parent got triggered when a grandchild button was clicked");
    console.log("Parent->Child->SubChild");
    console.log(Value_Passed_From_SubChild);
  }
  render() {
    return <Child handler = {this.handler} />
  }
}
class Child extends React.Component {
  render() {
    return <SubChild handler = {this.props.handler}/ >
  }
}
class SubChild extends React.Component { 
  constructor(props){
   super(props);
   this.state = {
      somethingImp : [1,2,3,4]
   }
  }
  render() {
     return <button onClick = {this.props.handler(this.state.somethingImp)}>Clickme<button/>
  }
}
React.render(<Parent />,document.getElementById('app'));

 HTML
 ----
 <div id="app"></div>

Dans cet exemple, nous pouvons faire passer des données de SubChild -> Child -> Parent en passant la fonction à son enfant direct.


4

J'ai utilisé plusieurs fois la réponse la mieux notée de cette page, mais en apprenant React, j'ai trouvé une meilleure façon de le faire, sans liaison et sans fonction intégrée dans les accessoires.

Regardez ici:

class Parent extends React.Component {

  constructor() {
    super();
    this.state={
      someVar: value
    }
  }

  handleChange=(someValue)=>{
    this.setState({someVar: someValue})
  }

  render() {
    return <Child handler={this.handleChange} />
  }

}

export const Child = ({handler}) => {
  return <Button onClick={handler} />
}

La clé est dans une fonction de flèche:

handleChange=(someValue)=>{
  this.setState({someVar: someValue})
}

Vous pouvez en lire plus ici . J'espère que cela sera utile pour quelqu'un =)


Cela a tellement plus de sens pour moi. Je vous remercie!
Brett Rowberry

3

-Nous pouvons créer ParentComponent et avec la méthode handleInputChange pour mettre à jour l'état ParentComponent. Importez le ChildComponent et nous transmettons deux accessoires du parent au composant enfant, c'est-à-dire la fonction et le comptage de handleInputChange.

import React, { Component } from 'react';
import ChildComponent from './ChildComponent';

class ParentComponent extends Component {
  constructor(props) {
    super(props);
    this.handleInputChange = this.handleInputChange.bind(this);
    this.state = {
      count: '',
    };
  }

  handleInputChange(e) {
    const { value, name } = e.target;
    this.setState({ [name]: value });
  }

  render() {
    const { count } = this.state;
    return (
      <ChildComponent count={count} handleInputChange={this.handleInputChange} />
    );
  }
}
  • Maintenant, nous créons le fichier ChildComponent et l'enregistrons sous ChildComponent.jsx. Ce composant est sans état car le composant enfant n'a pas d'état. Nous utilisons la bibliothèque prop-types pour la vérification du type d'accessoires.

    import React from 'react';
    import { func, number } from 'prop-types';
    
    const ChildComponent = ({ handleInputChange, count }) => (
      <input onChange={handleInputChange} value={count} name="count" />
    );
    
    ChildComponent.propTypes = {
      count: number,
      handleInputChange: func.isRequired,
    };
    
    ChildComponent.defaultProps = {
      count: 0,
    };
    
    export default ChildComponent;

Comment cela fonctionne-t-il lorsque l'enfant a un enfant qui affecte l'hélice de ses parents?
Bobort

3

Si ce même scénario n'est pas répandu partout, vous pouvez utiliser le contexte de React, spécialement si vous ne voulez pas introduire tous les frais généraux introduits par les bibliothèques de gestion d'état. De plus, c'est plus facile à apprendre. Mais attention, vous pourriez en abuser et commencer à écrire du mauvais code. Fondamentalement, vous définissez un composant Conteneur (qui détiendra et gardera cet élément d'état pour vous) faisant de tous les composants intéressés à écrire / lire ce morceau de données ses enfants (pas nécessairement des enfants directs)

https://reactjs.org/docs/context.html

À la place, vous pouvez également utiliser plain React correctement.

<Component5 onSomethingHappenedIn5={this.props.doSomethingAbout5} />

passez doSomethingAbout5 au composant 1

    <Component1>
        <Component2 onSomethingHappenedIn5={somethingAbout5 => this.setState({somethingAbout5})}/>
        <Component5 propThatDependsOn5={this.state.somethingAbout5}/>
    <Component1/>

S'il s'agit d'un problème courant, vous devriez commencer à penser à déplacer l'ensemble de l'état de l'application vers un autre endroit. Vous avez quelques options, les plus courantes sont:

https://redux.js.org/

https://facebook.github.io/flux/

Fondamentalement, au lieu de gérer l'état de l'application dans votre composant, vous envoyez des commandes lorsque quelque chose se produit pour obtenir l'état mis à jour. Les composants extraient également l'état de ce conteneur afin que toutes les données soient centralisées. Cela ne signifie pas que vous ne pouvez plus utiliser l'état local, mais c'est un sujet plus avancé.


2

donc, si vous voulez mettre à jour le composant parent,

 class ParentComponent extends React.Component {
        constructor(props){
            super(props);
            this.state = {
               page:0
            }
        }

        handler(val){
            console.log(val) // 1
        }

        render(){
          return (
              <ChildComponent onChange={this.handler} />
           )
       }
   }


class ChildComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
             page:1
        };
    }

    someMethod = (page) => {
        this.setState({ page: page });
        this.props.onChange(page)
    }

    render() {
        return (
       <Button
            onClick={() => this.someMethod()} 
       > Click
        </Button>
      )
   }
}

Ici onChange est un attribut avec la méthode "handler" liée à son instance. nous avons passé le gestionnaire de méthode au composant de classe Child, pour recevoir via la propriété onChange dans son argument props.

L'attribut onChange sera défini dans un objet props comme ceci:

props ={
onChange : this.handler
}

et passé au composant enfant

Ainsi, le composant enfant peut accéder à la valeur de nom dans l'objet props comme celui-ci props.onChange

Son fait grâce à l'utilisation d'accessoires de rendu.

Maintenant, le composant enfant a un bouton «Click» avec un événement onclick défini pour appeler la méthode du gestionnaire qui lui est passée via onChnge dans son objet argument props. Alors maintenant, this.props.onChange in Child contient la méthode de sortie dans la classe Parent Reference and credits: Bits and Pieces


Désolé .. pour le retard, ici onChange est un attribut avec la méthode "handler" liée à son instance. nous avons passé le gestionnaire de méthode au composant de classe Child, pour recevoir via la propriété onChange dans son argument props. L'attribut onChange sera défini dans un objet props comme ceci: props = {onChange: this.handler} et transmis au composant enfant Pour que le composant Child puisse accéder à la valeur du nom dans l'objet props comme celui-ci props.onChange Son fait via l'utilisation d'accessoires de rendu. Référence et crédits: [ blog.bitsrc.io/…
Preetham NT

0

C'est comme ça que je le fais.

type ParentProps = {}
type ParentState = { someValue: number }
class Parent extends React.Component<ParentProps, ParentState> {
    constructor(props: ParentProps) {
        super(props)
        this.state = { someValue: 0 }

        this.handleChange = this.handleChange.bind(this)
    }

    handleChange(value: number) {
        this.setState({...this.state, someValue: value})
    }

    render() {
        return <div>
            <Child changeFunction={this.handleChange} defaultValue={this.state.someValue} />
            <p>Value: {this.state.someValue}</p>
        </div>
    }
}

type ChildProps = { defaultValue: number, changeFunction: (value: number) => void}
type ChildState = { anotherValue: number }
class Child extends React.Component<ChildProps, ChildState> {
    constructor(props: ChildProps) {
        super(props)
        this.state = { anotherValue: this.props.defaultValue }

        this.handleChange = this.handleChange.bind(this)
    }

    handleChange(value: number) {
        this.setState({...this.state, anotherValue: value})
        this.props.changeFunction(value)
    }

    render() {
        return <div>
            <input onChange={event => this.handleChange(Number(event.target.value))} type='number' value={this.state.anotherValue}/>
        </div>
    }
}

0

La plupart des réponses ci-dessus concernent les conceptions basées sur React.Component. Si vous l'utilisez useStatedans les récentes mises à niveau de la bibliothèque React, suivez cette réponse


-3
<Footer 
  action={()=>this.setState({showChart: true})}
/>

<footer className="row">
    <button type="button" onClick={this.props.action}>Edit</button>
  {console.log(this.props)}
</footer>

Try this example to write inline setState, it avoids creating another function.

Cela cause un problème de performances: stackoverflow.com/q/36677733/3328979
Arashsoft
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.