ReactJS: setState sul genitore all'interno del componente figlio


89

Qual è il modello consigliato per eseguire un setState su un genitore da un componente figlio.

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)

Ho una serie di cose da fare che viene mantenuta nello stato del genitore. Voglio accedere lo stato del genitore e aggiungere un nuovo elemento todo, dal TodoForm's handleClickcomponente. La mia idea è di fare un setState sul genitore, che renderà l'elemento todo appena aggiunto.




Ricevo un erroresetState(...): 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

Ricevo lo stesso errore che non riesco a impostareState su un componente smontato. C'era una soluzione alternativa per questo?
Kevin Burton,

Risposte:


81

Nel tuo genitore, puoi creare una funzione come quella addTodoItemche farà il setState richiesto e poi passare quella funzione come oggetti di scena al componente figlio.

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

  ...

});

Puoi invocare addTodoItemin handleClick di TodoForm. Questo farà un setState sul genitore che renderà l'elemento todo appena aggiunto. Spero che tu abbia l'idea.

Vieni qui.


6
Cosa ci fa qui l' <<operatore this.state.todos << todoItem;?
Gabriel Garrett

@zavtra Little Ruby confusione in corso immagino
aziendeum

7
È una cattiva pratica mutare this.statedirettamente. Meglio usare setState funzionale. reactjs.org/docs/react-component.html#setstate
Rohmer

2
il violino è rotto
Hunter Nelson

1
Come sarebbe implementata questa soluzione (aggiornata) utilizzando gli hook React?
ecoe

11

Questi sono tutti essenzialmente corretti, ho solo pensato di indicare la nuova (ish) documentazione di reazione ufficiale che fondamentalmente raccomanda: -

Dovrebbe esserci un'unica "fonte di verità" per ogni dato che cambia in un'applicazione React. Di solito, lo stato viene prima aggiunto al componente che ne ha bisogno per il rendering. Quindi, se anche altri componenti ne hanno bisogno, puoi sollevarlo fino al loro antenato comune più vicino. Invece di provare a sincronizzare lo stato tra diversi componenti, dovresti fare affidamento sul flusso di dati dall'alto verso il basso.

Vedi https://reactjs.org/docs/lifting-state-up.html . La pagina funziona anche attraverso un esempio.


8

Puoi creare una funzione addTodo nel componente genitore, associarla a quel contesto, passarla al componente figlio e chiamarla da lì.

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

Quindi, in Todos.render, lo faresti

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

Chiamalo in TodoForm con

this.props.addToDo(newTodo);

Questo è stato così utile. Senza farlo bind(this)al momento del passaggio della funzione, non generava alcun errore this.setState is not a function.
pratpor

6

Per coloro che mantengono lo stato con React Hook useState , ho adattato i suggerimenti di cui sopra per creare un'app di scorrimento demo di seguito. Nell'app demo, il componente slider figlio mantiene lo stato del genitore.

La demo utilizza anche useEffecthook. (e meno importante, useRefgancio)

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;

Puoi utilizzare questa app con create-react-app e sostituire tutto il codice in App.js con il codice sopra.
NicoWheat

Ciao, sono nuovo a reagire e mi chiedevo: è necessario utilizzare useEffect? Perché è necessario che i dati siano archiviati sia nello stato genitore che in quello figlio?
538ROMEO

1
Gli esempi non hanno lo scopo di mostrare perché abbiamo bisogno di memorizzare i dati sia nel genitore che nel figlio, la maggior parte delle volte non è necessario. Ma, se ti sei trovato in una situazione in cui il bambino dovrebbe impostare lo stato di genitore, è così che potresti farlo. useEffect è necessario se si desidera impostare lo stato padre COME EFFETTO della modifica dello stato figlio.
NicoWheat

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

4
Sebbene questo codice possa rispondere alla domanda, fornire un contesto aggiuntivo su come e / o perché risolve il problema migliorerebbe il valore a lungo termine della risposta.
Nic3500

2

Ho trovato la seguente soluzione funzionante e semplice per passare argomenti da un componente figlio al componente genitore:

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

Guarda JSFIDDLE


0

Se stai lavorando con un componente di classe come genitore, un modo molto semplice per passare un setState a un figlio è passarlo all'interno di una funzione freccia. Funziona in quanto imposta un ambiente sollevato che può essere trasferito:

class ClassComponent ... {

    modifyState = () =>{
        this.setState({...})   
    }
    render(){
          return <><ChildComponent parentStateModifier={modifyState} /></>
    }
}
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.