ReactJS: setState no pai dentro do componente filho

91

Qual é o padrão recomendado para fazer um setState em um pai a partir de um componente filho.

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)

Eu tenho uma série de itens de tarefas que são mantidos no estado do pai. Eu quero acessar o estado do pai e adicionar um novo item todo, a partir do TodoForm's handleClickcomponente. Minha ideia é fazer um setState no pai, que renderizará o item de tarefas recém-adicionado.

Pavithra
fonte
Vou apenas enviar
jujiyangasli de
Estou recebendo um errosetState(...): 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
Estou recebendo o mesmo erro que não consigo setState em um componente não montado. Houve uma solução alternativa para isso?
Kevin Burton de

Respostas:

82

Em seu pai, você pode criar uma função como a addTodoItemque fará o setState necessário e, em seguida, passar essa função como adereços para o componente filho.

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

  ...

});

Você pode invocar addTodoItemno handleClick de TodoForm. Isso fará um setState no pai que renderizará o item de tarefas recém-adicionado. Espero que você tenha entendido.

Fiddle aqui.

Deepak
fonte
6
O que a <<operadora está this.state.todos << todoItem;fazendo aqui?
Gabriel Garrett de
@zavtra Little Ruby confusão acontecendo, eu acho
azium
7
É uma má prática sofrer mutação this.statedireta. Melhor usar setState funcional. reactjs.org/docs/react-component.html#setstate
Rohmer
2
o violino está quebrado
Hunter Nelson
1
Como esta solução (atualizada) seria implementada usando ganchos React?
ecoe
11

Tudo isso está essencialmente correto, eu apenas pensei em apontar para a nova (ish) documentação oficial de reação que basicamente recomenda: -

Deve haver uma única “fonte de verdade” para todos os dados que mudam em um aplicativo React. Normalmente, o estado é adicionado primeiro ao componente que precisa dele para renderização. Então, se outros componentes também precisarem dele, você pode elevá-lo até seu ancestral comum mais próximo. Em vez de tentar sincronizar o estado entre diferentes componentes, você deve confiar no fluxo de dados de cima para baixo.

Consulte https://reactjs.org/docs/lifting-state-up.html . A página também funciona por meio de um exemplo.

TattyFromMelbourne
fonte
8

Você pode criar uma função addTodo no componente pai, vinculá-la a esse contexto, passá-la para o componente filho e chamá-la de lá.

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

Então, em Todos.render, você faria

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

Chame isso em TodoForm com

this.props.addToDo(newTodo);
rallrall
fonte
Isso foi muito útil. Sem fazer bind(this)na hora de passar função, estava jogando erro sem tal função this.setState is not a function.
pratpor
7

Para aqueles que estão mantendo o estado com o React Hook useState, adaptei as sugestões acima para fazer um aplicativo deslizante de demonstração abaixo. No aplicativo de demonstração, o componente deslizante filho mantém o estado do pai.

A demonstração também usa useEffectgancho. (e 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
fonte
Você pode usar este aplicativo com create-react-app e substituir todo o código em App.js com o código acima.
NicoWheat 01 de
Olá, sou novo em reagir e queria saber: é necessário usar useEffect? Por que precisamos ter os dados armazenados no estado pai e filho?
grobouDu06
1
Os exemplos não pretendem mostrar por que precisamos armazenar dados tanto no pai quanto no filho - na maioria das vezes, não é necessário. Mas, se você se encontrar em uma situação em que a criança deve definir o estado de pai, é assim que você pode fazer isso. useEffect é necessário se você deseja definir o estado pai COMO UM EFEITO da mudança childState.
NicoWheat
3
parentSetState={(obj) => { this.setState(obj) }}
dezman
fonte
4
Embora este código possa responder à pergunta, fornecer contexto adicional sobre como e / ou por que ele resolve o problema melhoraria o valor da resposta a longo prazo.
Nic3500
2

Eu encontrei a seguinte solução simples e funcional para passar argumentos de um componente filho para o componente pai:

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

Olhe para JSFIDDLE

romano
fonte
0

Se você estiver trabalhando com um componente de classe como pai, uma maneira muito simples de passar um setState para um filho é passando-o dentro de uma função de seta. Isso funciona porque define um ambiente suspenso que pode ser transmitido:

class ClassComponent ... {

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