ReactJS: setState en padre dentro del componente hijo

89

¿Cuál es el patrón recomendado para hacer un setState en un componente principal desde un componente secundario?

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)

Tengo una variedad de elementos pendientes que se mantienen en el estado de los padres. Quiero tener acceso a estado de los padres y añadir un nuevo elemento de tarea, desde la TodoForm's handleClickcomponentes. Mi idea es hacer un setState en el padre, que renderizará el elemento de tareas recién agregado.

Pavithra
fuente
1
¿Esto ayuda a stackoverflow.com/questions/24147331/… ?
Dhiraj
Solo voy a enviar
jujiyangasli
Recibo un errorsetState(...): 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
Recibo el mismo error de que no puedo establecer Estado en un componente desmontado. ¿Hubo alguna solución para esto?
Kevin Burton

Respuestas:

81

En su padre, puede crear una función como la addTodoItemque hará el setState requerido y luego pasar esa función como accesorios al componente hijo.

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

  ...

});

Puede invocar addTodoItemen el handleClick de TodoForm. Esto hará un setState en el padre que renderizará el elemento de tarea recién agregado. Espero que entiendas la idea.

Violín aquí.

Deepak
fuente
6
¿Qué está haciendo el <<operador this.state.todos << todoItem;aquí?
Gabriel Garrett
@zavtra Little Ruby confusión en curso, supongo
azium
7
Es una mala práctica mutar this.statedirectamente. Es mejor usar setState funcional. reactjs.org/docs/react-component.html#setstate
Rohmer
2
el violín está roto
Hunter Nelson
1
¿Cómo se implementaría esta solución (actualizada) usando React hooks?
ecoe
11

Todos estos son esencialmente correctos, solo pensé que señalaría la nueva (ish) documentación oficial de reacción que básicamente recomienda: -

Debe haber una única "fuente de verdad" para cualquier dato que cambie en una aplicación React. Por lo general, el estado se agrega primero al componente que lo necesita para renderizar. Luego, si otros componentes también lo necesitan, puede elevarlo a su ancestro común más cercano. En lugar de intentar sincronizar el estado entre diferentes componentes, debe confiar en el flujo de datos de arriba hacia abajo.

Consulte https://reactjs.org/docs/lifting-state-up.html . La página también funciona a través de un ejemplo.

TattyFromMelbourne
fuente
8

Puede crear una función addTodo en el componente principal, vincularla a ese contexto, pasarla al componente secundario y llamarla desde allí.

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

Entonces, en Todos.render, harías

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

Llame a esto en TodoForm con

this.props.addToDo(newTodo);
rallrall
fuente
Esto fue muy útil. Sin hacerlo bind(this)en el momento de pasar la función, estaba arrojando un error sin tal función this.setState is not a function.
pratpor
6

Para aquellos que mantienen el estado con React Hook useState, adapté las sugerencias anteriores para hacer una aplicación de control deslizante de demostración a continuación. En la aplicación de demostración, el componente deslizante secundario mantiene el estado del padre.

La demostración también usa useEffecthook. (y menos importante, useRefgancho)

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
fuente
Puede usar esta aplicación con create-react-app y reemplazar todo el código en App.js con el código anterior.
NicoWheat
Hola, soy nuevo en reaccionar y me preguntaba: ¿es necesario usarlo useEffect? ¿Por qué necesitamos tener los datos almacenados tanto en el estado principal como en el secundario?
538ROMEO
1
Los ejemplos no pretenden mostrar por qué necesitamos almacenar datos tanto en el padre como en el hijo, la mayoría de las veces no es necesario. Pero, si se encuentra en una situación en la que el niño debe establecer el estado principal, así es como puede hacerlo. useEffect es necesario si desea establecer el estado principal COMO UN EFECTO del cambio childState.
NicoWheat
3
parentSetState={(obj) => { this.setState(obj) }}
Dezman
fuente
4
Si bien este código puede responder a la pregunta, proporcionar un contexto adicional sobre cómo y / o por qué resuelve el problema mejoraría el valor de la respuesta a largo plazo.
Nic3500
2

Encontré la siguiente solución funcional y simple para pasar argumentos de un componente secundario al componente principal:

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

Mira JSFIDDLE

romano
fuente
0

Si está trabajando con un componente de clase como padre, una forma muy sencilla de pasar un setState a un hijo es pasándolo dentro de una función de flecha. Esto funciona ya que establece un entorno elevado que se puede pasar:

class ClassComponent ... {

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