ReactJS: setState для родительского внутри дочернего компонента

89

Каков рекомендуемый шаблон для выполнения setState родительского элемента из дочернего компонента.

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)

У меня есть массив задач, которые хранятся в родительском состоянии. Я хочу , чтобы получить доступ состояния родителя и добавить новый пункт TODO, из TodoForm«S handleClickкомпоненты. Моя идея состоит в том, чтобы сделать setState для родителя, который будет отображать только что добавленный элемент todo.

Павитра
источник
1
помогает ли это stackoverflow.com/questions/24147331/… ?
Дхирадж
Просто собираюсь спамить
jujiyangasli 01
Я получаю сообщение об ошибкеsetState(...): 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.
Мэтт
Я получаю ту же ошибку, что не могу установить состояние на отключенном компоненте. Был ли обходной путь для этого?
Кевин Бертон,

Ответы:

81

В своем родителе вы можете создать функцию, addTodoItemкоторая будет выполнять требуемый setState, а затем передать эту функцию в качестве реквизита дочернему компоненту.

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

  ...

});

Вы можете вызвать addTodoItemметод handleClick в TodoForm. Это сделает setState для родительского элемента, который отобразит только что добавленный элемент todo. Надеюсь, вы уловили идею.

Поиграй здесь.

Дипак
источник
6
Что здесь делает <<оператор this.state.todos << todoItem;?
Gabriel Garrett
@zavtra Маленькая Руби путаница продолжается, я думаю
azium
7
this.stateНепосредственная мутация - плохая практика . Лучше использовать функциональный setState. reactjs.org/docs/react-component.html#setstate
Ромер,
2
скрипка сломана
Хантер Нельсон
1
Как это (обновленное) решение будет реализовано с помощью перехватчиков React?
ecoe
11

Все это по сути правильно, я просто подумал, что укажу на новую (ish) официальную документацию по реагированию, которая в основном рекомендует: -

Должен быть единый «источник истины» для любых данных, которые изменяются в приложении React. Обычно состояние сначала добавляется к компоненту, которому оно нужно для рендеринга. Затем, если он нужен другим компонентам, вы можете поднять его до их ближайшего общего предка. Вместо того, чтобы пытаться синхронизировать состояние между различными компонентами, вам следует полагаться на нисходящий поток данных.

См. Https://reactjs.org/docs/lifting-state-up.html . На странице также работает пример.

TattyFromMelbourne
источник
8

Вы можете создать функцию addTodo в родительском компоненте, привязать ее к этому контексту, передать ее дочернему компоненту и вызвать оттуда.

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

Затем в Todos.render вы бы сделали

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

Вызовите это в TodoForm с помощью

this.props.addToDo(newTodo);
раллролл
источник
Это было так полезно. Не выполняя bind(this)во время передачи функции, это не вызывало ошибки такой функции this.setState is not a function.
pratpor
6

Для тех, кто поддерживает состояние с помощью React Hook useState, я адаптировал приведенные выше предложения, чтобы создать демонстрационное приложение-слайдер ниже. В демонстрационном приложении компонент дочернего слайдера поддерживает состояние родителя.

В демо также используется useEffectхук. (и, что менее важно, useRefкрючок)

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
источник
Вы можете использовать это приложение с create-response-app и заменить весь код в App.js приведенным выше кодом.
NicoWheat 01
Привет, я новичок в реакции и задавался вопросом: нужно ли использовать useEffect? Зачем нам нужно хранить данные как в родительском, так и в дочернем состоянии?
538ROMEO,
1
Примеры не предназначены для того, чтобы показать, почему нам нужно хранить данные и в родительском, и в дочернем - в большинстве случаев вам это не нужно. Но если вы оказались в ситуации, когда дочерний элемент должен установить родительское состояние, вы можете сделать это следующим образом. useEffect необходим, если вы хотите установить родительское состояние КАК ЭФФЕКТ изменения childState.
NicoWheat
3
parentSetState={(obj) => { this.setState(obj) }}
Дезман
источник
4
Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, как и / или почему он решает проблему, улучшило бы долгосрочную ценность ответа.
Nic3500 02
2

Я нашел следующее рабочее и простое решение для передачи аргументов от дочернего компонента родительскому компоненту:

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

Посмотрите на JSFIDDLE

Римский
источник
0

Если вы работаете с компонентом класса в качестве родителя, один очень простой способ передать setState дочернему элементу - передать его внутри функции стрелки. Это работает, поскольку он устанавливает поднятую среду, которую можно передавать:

class ClassComponent ... {

    modifyState = () =>{
        this.setState({...})   
    }
    render(){
          return <><ChildComponent parentStateModifier={modifyState} /></>
    }
}
Хулио Перейра
источник