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


90

Який рекомендований шаблон для створення 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)

У мене є масив завдань, які зберігаються у батьківському стані. Я хочу отримати доступ до батьківського стану та додати новий пункт завдання TodoFormз handleClickкомпонента. Моя ідея полягає в тому, щоб зробити setState для батьків, який буде відображати нещодавно доданий елемент todo.



Просто буду спамувати
jujiyangasli

Я отримую помилку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.
Метт

Я отримую ту саму помилку, що не можу встановити State на немонтований компонент. Чи було для цього обхідне рішення?
Кевін Бертон,

Відповіді:


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в TodoForm handleClick. Це зробить setState для батьків, який буде відображати щойно доданий елемент завдання. Сподіваюся, ви зрозуміли ідею.

Скрипатися тут.


6
Що тут робить <<оператор this.state.todos << todoItem;?
Габріель Гарретт

@zavtra Маленька рубінова плутанина, здається, відбувається
azium

7
Погана практика - this.stateбезпосередньо мутувати . Краще використовувати функціональний setState. responsejs.org/docs/react-component.html#setstate
Ромер

2
скрипка зламана
Хантер Нельсон

1
Як би це (оновлене) рішення було реалізовано за допомогою гачків React?
ecoe

11

Це все по суті правильно, я просто думав, що вказав би на нову (іш) офіційну документацію щодо реагування, яка в основному рекомендує: -

Для будь-яких даних, які змінюються у програмі React, має бути єдине “джерело істини”. Зазвичай стан спочатку додається до компонента, який його потребує для візуалізації. Потім, якщо це потрібно і іншим компонентам, ви можете підняти його до найближчого спільного предка. Замість спроб синхронізувати стан між різними компонентами, вам слід покладатися на потік даних зверху вниз.

Див. Https://reactjs.org/docs/lifting-state-up.html . Сторінка також працює на прикладі.


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, я адаптував наведені вище пропозиції, щоб зробити демонстраційний слайдер App нижче. У демонстраційному додатку дочірній компонент повзунка підтримує батьківський стан.

Демо також використовує 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;

Ви можете використовувати цю програму з create-response-app і замінити весь код в App.js на код вище.
NicoWheat

Привіт, я новачок у реагуванні, і мені було цікаво: чи потрібно це використовувати useEffect? Чому нам потрібно зберігати дані як у батьківському, так і в дочірньому стані?
538ROMEO

1
Приклади не мають на меті показати, чому нам потрібно зберігати дані як у батьків, так і в дитини - більшу частину часу вам не потрібно. Але, якщо ви опинилися в ситуації, коли дитина повинна встановити батьківський стан, ось як ви могли це зробити. useEffect необхідний, якщо ви хочете встановити батьківський стан ЯК ЕФЕКТ зміни дочірнього стану.
NicoWheat

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

4
Хоча цей код може відповісти на питання, надання додаткового контексту щодо того, як та / або чому він вирішує проблему, покращило б довгострокове значення відповіді.
Nic3500

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} /></>
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.