Come aggiornare lo stato dei genitori in React?


350

La mia struttura ha il seguente aspetto:

Component 1  

 - |- Component 2


 - - |- Component 4


 - - -  |- Component 5  

Component 3

Il componente 3 dovrebbe visualizzare alcuni dati a seconda dello stato del componente 5. Dato che gli oggetti di scena sono immutabili, non posso semplicemente salvare il suo stato nel componente 1 e inoltrarlo, giusto? E sì, ho letto su Redux, ma non voglio usarlo. Spero che sia possibile risolverlo solo con reagire. Ho sbagliato?


20
super-facile: passa la funzione parent-setState-Function tramite la proprietà al componente figlio: <MyChildComponent setState = {p => {this.setState (p)}} /> Nel componente figlio chiamalo tramite this.props. setState ({myObj, ...});
Marcel Ennix,

@MarcelEnnix, il tuo commento mi fa risparmiare tempo. Grazie.
Dinith Minura,

<MyChildComponent setState={(s,c)=>{this.setState(s, c)}} />se hai intenzione di utilizzare questo hack assicurati di supportare il callback.
Barkermn01,

4
Passare un callback per impostare lo stato del genitore è una brutta pratica che potrebbe portare a problemi di manutenzione. Rompe l'incapsulamento e rende i componenti 2 4 e 5 strettamente accoppiati a 1. Se percorri questo percorso, non sarai in grado di riutilizzare nessuno di questi componenti figlio altrove. È meglio avere oggetti di scena specifici in modo che i componenti figlio possano innescare eventi ogni volta che succede qualcosa, quindi il componente padre gestirà correttamente quell'evento.
Pato Loco,

@MarcelEnnix, perché le parentesi graffe in giro this.setState(p)? Ho provato senza di loro e sembra funzionare (sono molto nuovo su React)
Biganon il

Risposte:


678

Per la comunicazione bambino-genitore è necessario passare una funzione che imposta lo stato da genitore a figlio, in questo modo

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

In questo modo il bambino può aggiornare lo stato del genitore con la chiamata di una funzione passata con oggetti di scena.

Ma dovrai ripensare la struttura dei tuoi componenti, perché come ho capito i componenti 5 e 3 non sono correlati.

Una possibile soluzione è di avvolgerli in un componente di livello superiore che conterrà lo stato di entrambi i componenti 1 e 3. Questo componente imposterà lo stato di livello inferiore attraverso i puntelli.


6
perché hai bisogno di this.handler = this.handler.bind (this) e non solo della funzione handler che imposta lo stato?
chemook78,

35
@ chemook78 nei metodi delle classi ES6 React non si associano automaticamente alla classe. Quindi se non aggiungiamo this.handler = this.handler.bind(this)nel costruttore, thisall'interno della handlerfunzione farà riferimento alla chiusura della funzione, non alla classe. Se non vuoi associare tutte le tue funzioni al costruttore, ci sono altri due modi per gestirlo usando le funzioni freccia. Puoi semplicemente scrivere il gestore di clic come onClick={()=> this.setState(...)}, oppure puoi utilizzare gli inizializzatori di proprietà insieme alle funzioni freccia come descritto qui babeljs.io/blog/2015/06/07/react-on-es6-plus sotto "Funzioni freccia"
Ivan

1
Ecco un esempio di questo in azione: plnkr.co/edit/tGWecotmktae8zjS5yEr?p=preview
Tamb

5
Tutto ciò ha senso, perché e.preventDefault? e questo richiede jquery?
Vincent Buscarello,

1
Domanda veloce, questo non consente l'assegnazione di uno stato locale all'interno del bambino?
ThisGuyCantEven

50

Ho trovato la seguente soluzione di lavoro per passare l'argomento della funzione onClick dal figlio al componente padre:

Versione con passaggio a method ()

//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")
    );
}

Guarda JSFIDDLE

Versione con passaggio di una funzione Freccia

//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")
    );
}

Guarda JSFIDDLE


Questo è buono! potresti spiegare questo pezzo: <ChildB handleToUpdate = {handleToUpdate.bind(this)} />perché doversi legare di nuovo?
Dane,

@Dane - devi associare il contesto di questo per essere il genitore in modo che quando thisviene chiamato all'interno del figlio, si thisriferisca allo stato del genitore e non a quello del figlio. Questa è la chiusura al suo meglio!
Casey,

@ Casey ma non lo stiamo facendo nel costruttore? E non è abbastanza ??
Dane,

Hai perfettamente ragione! Ho perso questo. Sì, se l'hai già fatto nel costruttore, allora sei a posto!
Casey,

Sei un compagno di leggenda! Ciò manterrà i componenti ben autonomi senza dover essere costretti a creare componenti principali per gestire gli scambi di stato
adamj

13

Voglio ringraziare la risposta più votata per avermi dato l'idea del mio problema fondamentalmente la sua variazione con la funzione freccia e il passaggio del parametro dal componente figlio:

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

Spero che aiuti qualcuno.


qual è la particolarità della funzione freccia sulla chiamata diretta?
Ashish Kamble,

@AshishKamble le thisfunzioni freccia in si riferiscono al contesto del genitore (cioè Parentclasse).
CPHPython,

Questa è una risposta duplicata. È possibile aggiungere un commento alla risposta accettata e menzionare questa funzione sperimentale per utilizzare la funzione freccia in classe.
Arashsoft,

10

Mi piace la risposta per quanto riguarda le funzioni di passaggio, è una tecnica molto utile.

Il rovescio della medaglia è anche possibile ottenere questo risultato utilizzando pub / sub o utilizzando una variante, un dispatcher, come fa Flux . La teoria è super semplice: il componente 5 invia un messaggio per il componente 3 in ascolto. Il componente 3 aggiorna quindi il suo stato che attiva il rendering. Ciò richiede componenti con stato, che, a seconda del punto di vista, possono essere o meno un anti-pattern. Sono contro di loro personalmente e preferirei che qualcos'altro stia ascoltando le spedizioni e cambi lo stato dall'alto verso il basso (Redux fa questo, ma aggiunge una terminologia aggiuntiva).

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

I bundle del dispatcher con Flux sono molto semplici, registra semplicemente i callback e li invoca quando si verifica un dispacciamento, passando attraverso i contenuti del dispatch (nell'esempio precedente sopra non c'è nessun payloadcon il dispatch, semplicemente un ID messaggio). Potresti adattare questo al pub / sub tradizionale (ad es. Usando EventEmitter dagli eventi, o qualche altra versione) molto facilmente se questo ha più senso per te.


I componenti di My Reacts "funzionano" nel browser come in un tutorial ufficiale ( facebook.github.io/react/docs/tutorial.html ) Ho provato a includere Flux con browserify, ma il browser dice che Dispatcher non è stato trovato :(
wklm

2
La sintassi che ho usato è stata la sintassi del modulo ES2016 che richiede la transpilation (io uso Babel , ma ce ne sono altri, la trasformazione babelify può essere usata con browserify), si traspila invar Dispatcher = require( 'flux' ).Dispatcher
Matt Styles,

8

Ho trovato la seguente soluzione di lavoro per passare l'argomento della funzione onClick da figlio al componente padre con param:

classe genitore:

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 per bambini:

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
Qualcuno può dire se è una soluzione accettabile (particolarmente interessato a passare il parametro come suggerito).
Ilans,

param1viene visualizzato sulla console, non viene assegnato, assegna sempretrue
Ashish Kamble,

Non posso parlare della qualità della soluzione, ma questo ha superato il parametro per me.
James,

6

Quando mai hai bisogno di comunicare tra figlio e genitore a qualsiasi livello verso il basso, allora è meglio usare il contesto . Nel componente genitore definire il contesto che può essere invocato dal figlio come

Nel componente padre nel tuo caso componente 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
}

Ora nel componente figlio (componente 5 nel tuo caso), basta dire a questo componente che vuole usare il contesto del suo genitore.

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

puoi trovare il progetto Demo al repository


Non riesco a capire questa risposta, puoi spiegarne di più
Ashish Kamble,

5

Sembra che possiamo solo trasmettere i dati da padre a figlio poiché la reazione promuove il flusso di dati unidirezionale, ma per fare in modo che il genitore si aggiorni quando succede qualcosa nel suo "componente figlio", generalmente usiamo quella che viene chiamata "funzione di richiamata".

Passiamo la funzione definita nel genitore al figlio come "oggetti di scena" e chiamiamo quella funzione dal figlio attivandola nel componente genitore.


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>

In questo esempio possiamo far passare i dati da SubChild -> Child -> Parent passando la funzione al suo Child diretto.


4

Ho usato molte volte la risposta più votata da questa pagina, ma durante l'apprendimento di React ho trovato un modo migliore per farlo, senza legatura e senza funzione incorporata all'interno dei puntelli.

Guarda qui:

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

Il tasto è in una funzione freccia:

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

Puoi leggere di più qui . Spero che questo sia utile per qualcuno =)


Questo ha molto più senso per me. Grazie!
Brett Rowberry,

3

-Possiamo creare ParentComponent e con il metodo handleInputChange per aggiornare lo stato ParentComponent. Importa ChildComponent e passiamo due oggetti di scena dal componente genitore al componente figlio, ad esempio la funzionehandleInputChange e contiamo.

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} />
    );
  }
}
  • Ora creiamo il file ChildComponent e salviamo come ChildComponent.jsx. Questo componente è apolide perché il componente figlio non ha uno stato. Utilizziamo la libreria dei tipi di prop per il controllo del tipo di prop.

    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;

Come funziona quando il bambino ha un bambino che influenza il sostegno del suo genitore?
Bobort,

3

Se questo stesso scenario non è diffuso ovunque, è possibile utilizzare il contesto di React, specialmente se non si desidera introdurre tutto il sovraccarico introdotto dalle librerie di gestione dello stato. Inoltre, è più facile da imparare. Ma fai attenzione, potresti abusarne e iniziare a scrivere codice errato. Fondamentalmente definisci un componente Container (che manterrà e manterrà quel pezzo di stato per te) rendendo tutti i componenti interessati a scrivere / leggere quel pezzo di dati suoi figli (non necessariamente indirizzare figli)

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

In alternativa, puoi anche utilizzare semplicemente React correttamente.

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

passare doSomethingAbout5 fino al componente 1

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

Se questo è un problema comune, dovresti iniziare a pensare di spostare l'intero stato dell'applicazione in un altro posto. Hai alcune opzioni, le più comuni sono:

https://redux.js.org/

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

Fondamentalmente, invece di gestire lo stato dell'applicazione nel tuo componente, invii comandi quando succede qualcosa per ottenere lo stato aggiornato. I componenti estraggono anche lo stato da questo contenitore, quindi tutti i dati sono centralizzati. Questo non significa che non è più possibile utilizzare lo stato locale, ma questo è un argomento più avanzato.


2

quindi, se si desidera aggiornare il componente padre,

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

Qui onChange è un attributo con il metodo "handler" associato alla sua istanza. abbiamo passato il gestore del metodo al componente della classe Child, per ricevere tramite la proprietà onChange l'argomento props.

L'attributo onChange verrà impostato in un oggetto props come questo:

props ={
onChange : this.handler
}

e passato al componente figlio

Quindi il componente Child può accedere al valore del nome nell'oggetto props come questo props.onChange

È fatto attraverso l'uso di oggetti di scena.

Ora il componente Child ha un pulsante "Click" con un evento onclick impostato per chiamare il metodo handler passato ad esso tramite onChnge nell'oggetto argomento props. Quindi ora this.props.onChange in Child contiene il metodo di output nella classe Parent Riferimenti e crediti: Bits and Pieces


Ci dispiace .. per ritardo, qui onChange è un attributo con il metodo "gestore" associato alla sua istanza. abbiamo passato il gestore del metodo al componente della classe Child, per ricevere tramite la proprietà onChange l'argomento props. L'attributo onChange verrà impostato in un oggetto props come questo: props = {onChange: this.handler} e passato al componente figlio In modo che il componente Child possa accedere al valore del nome nell'oggetto props come questo props.onChange l'uso di oggetti di scena. Riferimenti e crediti: [ blog.bitsrc.io/…
Preetham NT

0

Questo è il modo in cui lo faccio.

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 maggior parte delle risposte fornite sopra riguarda progetti basati su React.Component. Se stai utilizzando useStatei recenti aggiornamenti della libreria React, segui questa risposta


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

E 'causare problemi di prestazioni: stackoverflow.com/q/36677733/3328979
Arashsoft
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.