ReactJS: setState na rodzica wewnątrz komponentu potomnego


89

Jaki jest zalecany wzorzec wykonywania setState na rodzicu z komponentu podrzędnego.

var Todos = React.createClass({
  getInitialState: function() {
    return {
      todos: [
        "I am done",
        "I am not done"
      ]
    }
  },

  render: function() {
    var todos = this.state.todos.map(function(todo) {
      return <div>{todo}</div>;
    });

    return <div>
      <h3>Todo(s)</h3>
      {todos}
      <TodoForm />
    </div>;
  }
});

var TodoForm = React.createClass({
  getInitialState: function() {
    return {
      todoInput: ""
    }
  },

  handleOnChange: function(e) {
    e.preventDefault();
    this.setState({todoInput: e.target.value});
  },

  handleClick: function(e) {
    e.preventDefault();
    //add the new todo item
  },

  render: function() {
    return <div>
      <br />
      <input type="text" value={this.state.todoInput} onChange={this.handleOnChange} />
      <button onClick={this.handleClick}>Add Todo</button>
    </div>;
  }
});

React.render(<Todos />, document.body)

Mam tablicę rzeczy do zrobienia, które są utrzymywane w stanie rodzica. Chcę uzyskać dostęp do stanu rodzica i dodać nowy element do zrobienia z komponentu TodoForm's handleClick. Moim pomysłem jest wykonanie setState na rodzicu, który wyrenderuje nowo dodany element do zrobienia.



Po prostu spamuję
jujiyangasli

Pojawia się błądsetState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the MyModal component.
Matt

Otrzymuję ten sam błąd, którego nie mogę ustawić w niezmontowanym elemencie. Czy było jakieś obejście tego problemu?
Kevin Burton

Odpowiedzi:


81

W swoim rodzicu możesz stworzyć funkcję taką jak, addTodoItemktóra wykona wymagane setState, a następnie przekaże tę funkcję jako właściwości do komponentu potomnego.

var Todos = React.createClass({

  ...

  addTodoItem: function(todoItem) {
    this.setState(({ todos }) => ({ todos: { ...todos, todoItem } }));
  },

  render: function() {

    ...

    return <div>
      <h3>Todo(s)</h3>
      {todos}
      <TodoForm addTodoItem={this.addTodoItem} />
    </div>
  }
});

var TodoForm = React.createClass({
  handleClick: function(e) {
    e.preventDefault();
    this.props.addTodoItem(this.state.todoInput);
    this.setState({todoInput: ""});
  },

  ...

});

Możesz wywołać addTodoItemw handleClick TodoForm. Spowoduje to wykonanie setState na rodzicu, który wyrenderuje nowo dodany element do zrobienia. Mam nadzieję, że masz pomysł.

Tutaj skrzypce.


6
Co robi tutaj <<operator this.state.todos << todoItem;?
Gabriel Garrett,

Wydaje mi się, że @zavtra Little Ruby zamieszanie trwa
azium

7
this.stateBezpośrednia mutacja to zła praktyka . Lepiej używać funkcjonalnego setState. actjs.org/docs/react-component.html#setstate
Rohmer

2
skrzypce są zepsute
Hunter Nelson

1
W jaki sposób to (zaktualizowane) rozwiązanie byłoby zaimplementowane przy użyciu zaczepów React?
ecoe

11

Te wszystkie są zasadniczo poprawne, pomyślałem, że wskazałbym na nową (ish) oficjalną dokumentację dotyczącą reakcji, która zasadniczo zaleca: -

Powinno istnieć jedno „źródło prawdy” dla wszystkich danych, które ulegają zmianie w aplikacji React. Zazwyczaj stan jest najpierw dodawany do komponentu, który potrzebuje go do renderowania. Następnie, jeśli inne komponenty również tego potrzebują, możesz podnieść go do ich najbliższego wspólnego przodka. Zamiast próbować zsynchronizować stan między różnymi komponentami, powinieneś polegać na przepływie danych z góry na dół.

Zobacz https://reactjs.org/docs/lifting-state-up.html . Strona działa również na przykładzie.


8

Możesz utworzyć funkcję addTodo w komponencie nadrzędnym, powiązać ją z tym kontekstem, przekazać do komponentu potomnego i wywołać stamtąd.

// in Todos
addTodo: function(newTodo) {
    // add todo
}

Wtedy w Todos.render zrobisz to

<TodoForm addToDo={this.addTodo.bind(this)} />

Wywołaj to w TodoForm za pomocą

this.props.addToDo(newTodo);

To było bardzo przydatne. Bez wykonywania bind(this)w momencie przekazywania funkcji rzucał błąd żadnej takiej funkcji this.setState is not a function.
pratpor

6

Dla tych, którzy utrzymują stan dzięki React Hook useState , dostosowałem powyższe sugestie, aby stworzyć aplikację demonstracyjną suwaka poniżej. W aplikacji demonstracyjnej składnik suwaka podrzędnego utrzymuje stan rodzica.

Demo również używa useEffecthooka. (i co mniej ważne, useRefhak)

import React, { useState, useEffect, useCallback, useRef } from "react";

//the parent react component
function Parent() {

  // the parentState will be set by its child slider component
  const [parentState, setParentState] = useState(0);

  // make wrapper function to give child
  const wrapperSetParentState = useCallback(val => {
    setParentState(val);
  }, [setParentState]);

  return (
    <div style={{ margin: 30 }}>
      <Child
        parentState={parentState}
        parentStateSetter={wrapperSetParentState}
      />
      <div>Parent State: {parentState}</div>
    </div>
  );
};

//the child react component
function Child({parentStateSetter}) {
  const childRef = useRef();
  const [childState, setChildState] = useState(0);

  useEffect(() => {
    parentStateSetter(childState);
  }, [parentStateSetter, childState]);

  const onSliderChangeHandler = e => {
  //pass slider's event value to child's state
    setChildState(e.target.value);
  };

  return (
    <div>
      <input
        type="range"
        min="1"
        max="255"
        value={childState}
        ref={childRef}
        onChange={onSliderChangeHandler}
      ></input>
    </div>
  );
};

export default Parent;

Możesz użyć tej aplikacji z aplikacją create-react-app i zastąpić cały kod w App.js powyższym kodem.
NicoWheat

Cześć, jestem nowy w reagowaniu i zastanawiałem się: czy to konieczne useEffect? Dlaczego musimy przechowywać dane zarówno w stanie rodzica, jak i dziecka?
538ROMEO

1
Przykłady nie mają na celu pokazania, dlaczego musimy przechowywać dane zarówno u rodziców, jak i dzieci - w większości przypadków nie jest to konieczne. Ale jeśli znalazłeś się w sytuacji, w której dziecko powinno ustawić stan rodzicielski, tak możesz to zrobić. useEffect jest konieczny, jeśli chcesz ustawić stan nadrzędny JAKO EFEKT zmiany childState.
NicoWheat

3
parentSetState={(obj) => { this.setState(obj) }}

4
Chociaż ten kod może odpowiedzieć na pytanie, dostarczenie dodatkowego kontekstu dotyczącego tego, jak i / lub dlaczego rozwiązuje problem, poprawiłoby długoterminową wartość odpowiedzi.
Nic3500

2

Znalazłem następujące działające i proste rozwiązanie przekazywania argumentów z komponentu podrzędnego do komponentu nadrzędnego:

//ChildExt component
class ChildExt extends React.Component {
    render() {
        var handleForUpdate =   this.props.handleForUpdate;
        return (<div><button onClick={() => handleForUpdate('someNewVar')}>Push me</button></div>
        )
    }
}

//Parent component
class ParentExt extends React.Component {   
    constructor(props) {
        super(props);
        var handleForUpdate = this.handleForUpdate.bind(this);
    }
    handleForUpdate(someArg){
            alert('We pass argument from Child to Parent: \n' + someArg);   
    }

    render() {
        var handleForUpdate =   this.handleForUpdate;    
        return (<div>
                    <ChildExt handleForUpdate = {handleForUpdate.bind(this)} /></div>)
    }
}

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

Spójrz na JSFIDDLE


0

Jeśli pracujesz z komponentem klasy jako rodzicem, jednym bardzo prostym sposobem przekazania setState do dziecka jest przekazanie go w funkcji strzałkowej. Działa to tak, jak ustawia podniesione środowisko, które można ominąć:

class ClassComponent ... {

    modifyState = () =>{
        this.setState({...})   
    }
    render(){
          return <><ChildComponent parentStateModifier={modifyState} /></>
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.