Jak zaktualizować stan rodzica w React?

351

Moja struktura wygląda następująco:

Component 1  

 - |- Component 2


 - - |- Component 4


 - - -  |- Component 5  

Component 3

Komponent 3 powinien wyświetlać niektóre dane w zależności od stanu komponentu 5. Ponieważ rekwizyty są niezmienne, nie mogę po prostu zapisać jego stanu w komponencie 1 i przesłać go dalej, prawda? I tak, czytałem o redux, ale nie chcę go używać. Mam nadzieję, że można to rozwiązać tylko za pomocą reakcji. Czy się mylę?

wklm
źródło
20
super-łatwe: przekaż funkcję parent-setState-Function przez właściwość do elementu potomnego: <MyChildComponent setState = {p => {this.setState (p)}} /> W elemencie potomnym wywołaj go przez this.props. setState ({myObj, ...});
Marcel Ennix
@MarcelEnnix, Twój komentarz oszczędza mój czas. Dzięki.
Dinith Minura
<MyChildComponent setState={(s,c)=>{this.setState(s, c)}} />jeśli zamierzasz użyć tego hacka, upewnij się, że obsługujesz oddzwonienie.
Barkermn01
4
Przekazywanie oddzwaniania w celu ustawienia stanu rodzica jest naprawdę złą praktyką, która może prowadzić do problemów konserwacyjnych. Łamie enkapsulację i sprawia, że ​​komponenty 2 4 i 5 są ściśle połączone z 1. Jeśli pójdziesz tą ścieżką, nie będziesz mógł ponownie użyć żadnego z tych komponentów potomnych w innym miejscu. Lepiej masz określone rekwizyty, aby komponenty potomne mogły wyzwalać zdarzenia za każdym razem, gdy coś się wydarzy, wtedy komponent nadrzędny odpowiednio poradziłby sobie z tym zdarzeniem.
Pato Loco,
@MarcelEnnix, dlaczego nawiasy klamrowe wokół this.setState(p)? Próbowałem bez nich i wydaje się, że działa (jestem bardzo nowy w React)
Biganon

Odpowiedzi:

679

Do komunikacji dziecko-rodzic należy przekazać funkcję ustawiania stanu od rodzica do dziecka, tak jak to

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

W ten sposób dziecko może zaktualizować stan rodzica za pomocą wywołania funkcji przekazanej z rekwizytami.

Ale będziesz musiał przemyśleć strukturę swoich komponentów, ponieważ, jak rozumiem, komponenty 5 i 3 nie są powiązane.

Jednym z możliwych rozwiązań jest zawinięcie ich w komponent wyższego poziomu, który będzie zawierał stan zarówno komponentu 1, jak i 3. Ten komponent ustawi stan niższego poziomu za pomocą rekwizytów.

Ivan
źródło
6
dlaczego potrzebujesz this.handler = this.handler.bind (this), a nie tylko funkcji obsługi, która ustawia stan?
chemook78
35
@ chemook78 w ES6 Metody klas React nie wiążą się automatycznie z klasą. Jeśli więc nie dodamy this.handler = this.handler.bind(this)konstruktora, thiswewnątrz handlerfunkcji będzie odwoływać się do zamknięcia funkcji, a nie do klasy. Jeśli nie chcesz powiązać wszystkich funkcji w konstruktorze, istnieją jeszcze dwa sposoby radzenia sobie z tym za pomocą funkcji strzałek. Możesz po prostu napisać moduł obsługi kliknięć jako onClick={()=> this.setState(...)}lub użyć inicjatorów właściwości razem z funkcjami strzałek, jak opisano tutaj babeljs.io/blog/2015/06/07/react-on-es6-plus w sekcji „Funkcje strzałek”
Ivan
1
Oto przykład tego działania: plnkr.co/edit/tGWecotmktae8zjS5yEr?p=preview
Tamb
5
To wszystko ma sens, dlaczego e.preventDefault? i czy to wymaga jquery?
Vincent Buscarello,
1
Szybkie pytanie, czy to uniemożliwia przypisanie stanu lokalnego dziecku?
ThisGuyCantEven
50

Znalazłem następujące działające rozwiązanie do przekazania argumentu funkcji onClick z elementu potomnego do komponentu nadrzędnego:

Wersja z przekazaniem metody ()

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

Spójrz na JSFIDDLE

Wersja z przekazywaniem funkcji strzałki

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

Spójrz na JSFIDDLE

rzymski
źródło
Ten jest dobry! czy mógłbyś wyjaśnić ten kawałek: <ChildB handleToUpdate = {handleToUpdate.bind(this)} />Dlaczego musiałem ponownie związać?
Dane
@Dane - musisz powiązać kontekst tego, aby być rodzicem, tak aby thiswywołany w dziecku thisodnosi się do stanu rodzica, a nie dziecka. To zamknięcie w najlepszym wydaniu!
Casey
@Casey, ale czy nie robimy tego w konstruktorze? A czy to nie wystarczy?
Dane
Masz całkowitą rację! Tęsknie za tym. Tak, jeśli zrobiłeś to już w konstruktorze, możesz zacząć!
Casey
Jesteś kumplem z legendy! Dzięki temu komponenty będą ładnie niezależne, bez konieczności tworzenia komponentów nadrzędnych w celu obsługi wymiany stanu
adamj
13

Chcę podziękować najbardziej uprzywilejowanej odpowiedzi za przekazanie mi pomysłu na mój własny problem, w zasadzie jego odmiany z funkcją strzałki i przekazania param z komponentu potomnego:

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

Mam nadzieję, że to komuś pomoże.

Ardhi
źródło
co jest specjalnego w funkcji strzałek nad połączeniem bezpośrednim?
Ashish Kamble
@AshishKamble thisfunkcje strzałek odnoszą się do kontekstu rodzica (tj. ParentKlasy).
CPHPython
To jest duplikat odpowiedzi. Możesz dodać komentarz do zaakceptowanej odpowiedzi i wspomnieć o tej eksperymentalnej funkcji, aby użyć funkcji strzałki w klasie.
Arashsoft
10

Podoba mi się odpowiedź dotycząca przekazywania funkcji, to bardzo przydatna technika.

Z drugiej strony możesz to osiągnąć za pomocą pub / sub lub przy użyciu wariantu, dyspozytora, tak jak robi to Flux . Teoria jest bardzo prosta. Niech komponent 5 wyśle ​​komunikat, którego komponent 3 oczekuje. Komponent 3 następnie aktualizuje swój stan, który uruchamia ponowne renderowanie. Wymaga to stanowych komponentów, które w zależności od twojego punktu widzenia mogą, ale nie muszą być anty-wzorcem. Jestem przeciwko nim osobiście i wolałbym, żeby coś innego nasłuchiwało wysyłek i zmian stanu od samego początku (Redux robi to, ale dodaje dodatkową terminologię).

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

Pakiety rozsyłające z Flux są bardzo proste, po prostu rejestrują połączenia zwrotne i wywołują je, gdy nastąpi jakakolwiek wysyłka, przechodząc przez zawartość na wysyłce (w powyższym zwięzłym przykładzie nie ma payloadz wysyłką, po prostu identyfikator wiadomości). Możesz to bardzo łatwo dostosować do tradycyjnego pubu / sub (np. Używając EventEmitter z wydarzeń lub innej wersji), jeśli ma to dla ciebie większy sens.

Matt Style
źródło
Komponenty My Reacts „działają” w przeglądarce, jak w oficjalnym samouczku ( facebook.github.io/react/docs/tutorial.html ) Próbowałem dołączyć Fluxa do browserrify, ale przeglądarka mówi, że nie znaleziono Dispatchera :(
wklm
2
Użyłem składni moduł ES2016, który wymaga transpilacji (używam Babel , ale są też inne, transformacja babelify może być używana z browserrify), transpiluje się dovar Dispatcher = require( 'flux' ).Dispatcher
Matt Styles
8

Znalazłem następujące działające rozwiązanie do przekazania argumentu funkcji onClick z elementu potomnego do komponentu nadrzędnego z parametrem:

klasa rodzicielska:

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

klasa dziecięca:

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>
    )
} }
H. parnian
źródło
2
Czy ktoś może powiedzieć, czy jest to akceptowalne rozwiązanie (szczególnie zainteresowany przekazaniem parametru zgodnie z sugestią).
ilans
param1jest po prostu wyświetlany na konsoli, nie można go przypisać, zawsze przypisujetrue
Ashish Kamble
Nie mogę mówić o jakości rozwiązania, ale z sukcesem mi to wychodzi.
James
6

Gdy potrzebujesz komunikacji między dzieckiem a rodzicem na niższym poziomie, lepiej skorzystać z kontekstu . W komponencie nadrzędnym zdefiniuj kontekst, który może być wywołany przez dziecko, takie jak

W komponencie nadrzędnym w twoim przypadku komponent 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
}

Teraz w komponencie potomnym (komponent 5 w twoim przypadku), po prostu powiedz temu komponentowi, że chce użyć kontekstu swojego rodzica.

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

projekt demo można znaleźć na repo

Rajan Twanabashu
źródło
Nie potrafię zrozumieć tej odpowiedzi, czy możesz wyjaśnić więcej na ten temat
Ashish Kamble
5

Wydaje się, że możemy przekazywać dane od rodzica do dziecka, ponieważ reakcja promuje jednokierunkowy przepływ danych, ale aby sama rodzic zaktualizował się, gdy coś się wydarzy w jego „komponencie potomnym”, zwykle używamy tak zwanej „funkcji zwrotnej”.

Przekazujemy dziecku funkcję zdefiniowaną w obiekcie nadrzędnym jako „rekwizyty” i wywołujemy tę funkcję od dziecka wyzwalającego ją w komponencie nadrzędnym.


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>

W tym przykładzie możemy przekazać dane z SubChild -> Child -> Parent, przekazując funkcję do jego bezpośredniego potomka.

Vishal Bisht
źródło
4

Wielokrotnie korzystałem z najwyżej ocenianej odpowiedzi z tej strony, ale ucząc się React, znalazłem lepszy sposób na zrobienie tego, bez wiązania i bez wbudowanej funkcji wewnątrz rekwizytów.

Spójrz tutaj:

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

Klawisz znajduje się w funkcji strzałki:

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

Możesz przeczytać więcej tutaj . Mam nadzieję, że będzie to przydatne dla kogoś =)

Александр Немков
źródło
Ma to dla mnie o wiele większy sens. Dziękuję Ci!
Brett Rowberry
3

-Możemy utworzyć ParentComponent i za pomocą metody handleInputChange w celu zaktualizowania stanu ParentComponent. Zaimportuj ChildComponent, a my przekażemy dwa rekwizyty z komponentu nadrzędnego na komponent podrzędny, tj. Funkcję i liczenie funkcji 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} />
    );
  }
}
  • Teraz tworzymy plik ChildComponent i zapisujemy jako ChildComponent.jsx. Ten składnik jest bezstanowy, ponieważ element potomny nie ma stanu. Używamy biblioteki typów rekwizytów do sprawdzania typów rekwizytów.

    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;
Sachin Metkari
źródło
Jak to działa, gdy dziecko ma dziecko, które wpływa na rekwizyt jego rodzica?
Bobort
3

Jeśli ten sam scenariusz nie jest rozpowszechniony wszędzie, możesz użyć kontekstu React, szczególnie jeśli nie chcesz wprowadzać całego obciążenia, jakie wprowadzają biblioteki zarządzania stanem. Ponadto łatwiej się uczyć. Ale bądź ostrożny, możesz go nadużyć i zacząć pisać zły kod. Zasadniczo definiujesz komponent Container (który będzie przechowywał i utrzymywał ten stan dla Ciebie), dzięki czemu wszystkie komponenty będą zainteresowane zapisywaniem / odczytywaniem tego fragmentu danych jako jego elementy potomne (niekoniecznie bezpośrednie elementy potomne)

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

Zamiast tego możesz również użyć zwykłego Reacta.

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

przekazać doSomethingAbout5 aż do składnika 1

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

Jeśli jest to częsty problem, powinieneś zacząć myśleć o przeniesieniu całego stanu aplikacji w inne miejsce. Masz kilka opcji, najczęściej są to:

https://redux.js.org/

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

Zasadniczo zamiast zarządzać stanem aplikacji w komponencie, wysyłasz polecenia, gdy coś się stanie, aby zaktualizować stan. Komponenty również pobierają stan z tego kontenera, więc wszystkie dane są scentralizowane. Nie oznacza to, że nie można już używać stanu lokalnego, ale jest to bardziej zaawansowany temat.

Pato Loco
źródło
2

więc jeśli chcesz zaktualizować komponent nadrzędny,

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

Tutaj onChange jest atrybutem, którego metoda „handler” jest powiązana z jego instancją. przekazaliśmy moduł obsługi metody do komponentu klasy Child, aby otrzymać właściwość onChange w argumencie props.

Atrybut onChange zostanie ustawiony w obiekcie rekwizytów w następujący sposób:

props ={
onChange : this.handler
}

i przekazany do komponentu potomnego

Tak więc komponent potomny może uzyskać dostęp do wartości nazwy w obiekcie props, takim jak ten props.onChange

Odbywa się to za pomocą rekwizytów renderujących.

Teraz komponent potomny ma przycisk „Kliknij” z ustawionym zdarzeniem onclick, aby wywołać metodę modułu obsługi przekazaną mu przez onChnge w obiekcie argumentu props. Więc teraz this.props.on Change in Child przechowuje metodę wyjściową w klasie Parent Odniesienia i kredyty: Bity i kawałki

Preetham NT
źródło
Przepraszamy .. za opóźnienie, tutaj onChange jest atrybutem z metodą „handler” powiązaną z jego instancją. przekazaliśmy moduł obsługi metody do komponentu klasy Child, aby otrzymać właściwość onChange w argumencie props. Atrybut onChange zostanie ustawiony w obiekcie props, takim jak ten: props = {onChange: this.handler} i przekazany do komponentu potomnego, aby komponent potomny mógł uzyskać dostęp do wartości nazwy w obiekcie props, takim jak ten props.onZmień użycie rekwizytów. Referencje i kredyty: [ blog.bitsrc.io/...
Preetham NT
0

Tak to robię.

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>
    }
}
JESTEM NAJLEPSZA
źródło
0

Większość odpowiedzi podanych powyżej dotyczy projektów opartych na React.Component. Jeśli używasz useStatenajnowszych aktualizacji biblioteki React, postępuj zgodnie z tą odpowiedzią

leeCoder
źródło
-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.
pewny
źródło
Powoduje to problem z wydajnością: stackoverflow.com/q/36677733/3328979
Arashsoft