ReactJS: setState auf dem übergeordneten Element innerhalb der untergeordneten Komponente

88

Was ist das empfohlene Muster für die Ausführung eines setState für ein übergeordnetes Element aus einer untergeordneten Komponente?

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)

Ich habe eine Reihe von Aufgabenelementen, die im Status der Eltern verwaltet werden. Ich möchte auf den Status des übergeordneten Elements zugreifen und über die Komponente TodoForm' ein neues Aufgabenelement hinzufügen handleClick. Meine Idee ist es, einen setState für das übergeordnete Element zu erstellen, der das neu hinzugefügte Aufgabenelement rendert.

Pavithra
quelle
Ich
jujiyangasli
Ich erhalte FehlersetState(...): 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
Ich erhalte den gleichen Fehler, den ich für eine nicht gemountete Komponente nicht setzen kann. Gab es eine Problemumgehung dafür?
Kevin Burton

Antworten:

79

In Ihrem übergeordneten addTodoItemElement können Sie eine Funktion erstellen , die den erforderlichen setState ausführt, und diese Funktion dann als Requisiten an die untergeordnete Komponente übergeben.

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

  ...

});

Sie können addTodoItemin TodoForms handleClick aufrufen. Dadurch wird auf dem übergeordneten Element ein setState ausgeführt, der das neu hinzugefügte Aufgabenelement rendert. Hoffe du kommst auf die Idee.

Geige hier.

Deepak
quelle
6
Was macht der <<Betreiber this.state.todos << todoItem;hier?
Gabriel Garrett
@ Zavtra Little Ruby Verwirrung los, denke ich
Azium
7
Es ist eine schlechte Praxis, this.statedirekt zu mutieren . Besser funktionale setState verwenden. reactjs.org/docs/react-component.html#setstate
Rohmer
2
Die Geige ist gebrochen
Hunter Nelson
1
Wie würde diese (aktualisierte) Lösung mithilfe von React-Hooks implementiert?
Öko
11

Diese sind alle im Wesentlichen korrekt, ich dachte nur, ich würde auf die neue (ish) offizielle Reaktionsdokumentation verweisen, die grundsätzlich empfiehlt: -

Es sollte eine einzige „Quelle der Wahrheit“ für alle Daten geben, die sich in einer React-Anwendung ändern. Normalerweise wird der Status zuerst der Komponente hinzugefügt, die ihn zum Rendern benötigt. Wenn andere Komponenten es ebenfalls benötigen, können Sie es zu ihrem nächsten gemeinsamen Vorfahren anheben. Anstatt zu versuchen, den Status zwischen verschiedenen Komponenten zu synchronisieren, sollten Sie sich auf den Datenfluss von oben nach unten verlassen.

Siehe https://reactjs.org/docs/lifting-state-up.html . Die Seite funktioniert auch anhand eines Beispiels.

TattyFromMelbourne
quelle
8

Sie können eine addTodo-Funktion in der übergeordneten Komponente erstellen, an diesen Kontext binden, an die untergeordnete Komponente übergeben und von dort aus aufrufen.

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

Dann würden Sie in Todos.render tun

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

Nennen Sie dies in TodoForm mit

this.props.addToDo(newTodo);
rallrall
quelle
Das war so nützlich. Ohne bind(this)zum Zeitpunkt der Übergabe der Funktion zu tun , warf es Fehler keine solche Funktion this.setState is not a function.
Pratpor
5

Für diejenigen, die den Status mit dem React Hook beibehalten useState, habe ich die obigen Vorschläge angepasst, um unten eine Demo-Slider-App zu erstellen. In der Demo-App behält die untergeordnete Schiebereglerkomponente den Status des übergeordneten Elements bei.

Die Demo verwendet auch useEffectHook. (und weniger wichtig, useRefHaken)

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;
NicoWheat
quelle
Sie können diese App mit der Create-React-App verwenden und den gesamten Code in App.js durch den obigen Code ersetzen.
NicoWheat
Hallo, ich bin neu zu reagieren und habe mich gefragt: Ist es notwendig zu verwenden useEffect? Warum müssen die Daten sowohl im übergeordneten als auch im untergeordneten Status gespeichert werden?
538ROMEO
1
Die Beispiele sollen nicht zeigen, warum wir Daten sowohl bei Eltern als auch bei Kindern speichern müssen - die meiste Zeit brauchen Sie das nicht. Wenn Sie sich jedoch in einer Situation befinden, in der das Kind den Elternstatus festlegen sollte, können Sie dies auf diese Weise tun. useEffect ist erforderlich, wenn Sie den übergeordneten Status als einen Effekt der Änderung des childState festlegen möchten.
NicoWheat
3
parentSetState={(obj) => { this.setState(obj) }}
dezman
quelle
4
Während dieser Code die Frage beantworten kann, würde die Bereitstellung eines zusätzlichen Kontexts darüber, wie und / oder warum das Problem gelöst wird, den langfristigen Wert der Antwort verbessern.
Nic3500
2

Ich habe die folgende funktionierende und einfache Lösung gefunden, um Argumente von einer untergeordneten Komponente an die übergeordnete Komponente zu übergeben:

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

Schauen Sie sich JSFIDDLE an

römisch
quelle
0

Wenn Sie mit einer Klassenkomponente als übergeordnetes Element arbeiten, können Sie einen setState sehr einfach an ein untergeordnetes Element übergeben, indem Sie ihn innerhalb einer Pfeilfunktion übergeben. Dies funktioniert, da eine hochgezogene Umgebung festgelegt wird, die weitergegeben werden kann:

class ClassComponent ... {

    modifyState = () =>{
        this.setState({...})   
    }
    render(){
          return <><ChildComponent parentStateModifier={modifyState} /></>
    }
}
Julio Pereira
quelle