【问题标题】:ReactJS: setState on parent inside child componentReactJS:在子组件内的父级上设置状态
【发布时间】:2015-05-19 23:32:35
【问题描述】:

从子组件对父组件执行 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)

我有一个在父状态下维护的待办事项数组。 我想访问父级的状态并从TodoFormhandleClick 组件中添加一个新的待办事项。 我的想法是在 parent 上做一个 setState,它将呈现新添加的 todo 项。

【问题讨论】:

  • 只是在这里发送垃圾邮件...npmjs.com/package/react-event-observer
  • 我收到错误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.
  • 我遇到了同样的错误,我无法在未安装的组件上设置状态。有解决办法吗?

标签: reactjs


【解决方案1】:

如果您使用类组件作为父组件,将 setState 传递给子组件的一种非常简单的方法是在箭头函数中传递它。这是因为它设置了一个可以传递的提升环境:

class ClassComponent ... {

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

【讨论】:

    【解决方案2】:

    对于那些使用 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;
    

    【讨论】:

    • 你可以通过create-react-app来使用这个应用,并将App.js中的所有代码替换为上面的代码。
    • 嗨,我是新手,我想知道:是否有必要使用 useEffect ?为什么我们需要将数据同时存储在父状态和子状态中?
    • 这些示例并不是为了说明为什么我们需要同时在父子节点和子节点中存储数据——大多数时候你不需要这样做。但是,如果您发现自己处于孩子应该设置父状态的情况,那么您可以这样做。如果您想将父状态设置为子状态更改的效果,则 useEffect 是必需的。
    • @GLAND_PROPRE 我本可以更清楚地回答:不需要useEffect。我们可以直接在onSliderChangeHandler函数内部调用setParentState(e.target.value)
    • @IanDunn 我相信useCallback 会阻止在每次渲染时创建新函数。这里有一个很好的解释,什么时候需要什么时候不需要。 dmitripavlutin.com/dont-overuse-react-usecallback
    【解决方案3】:

    在您的父组件中,您可以创建一个类似 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: ""});
      },
    
      ...
    
    });
    

    您可以在 TodoForm 的 handleClick 中调用addTodoItem。这将在父级上执行 setState,这将呈现新添加的 todo 项。希望你能明白。

    Fiddle here.

    【讨论】:

    • this.state.todos &lt;&lt; todoItem; 中的&lt;&lt; 运算符在这里做什么?
    • @zavtra 我猜小红宝石正在发生混乱
    • 直接修改this.state 是不好的做法。最好使用函数式 setState。 reactjs.org/docs/react-component.html#setstate
    • 小提琴坏了
    • 如何使用 React hooks 实现这个(更新的)解决方案?
    【解决方案4】:
    parentSetState={(obj) => { this.setState(obj) }}
    

    【讨论】:

    • 虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。
    【解决方案5】:

    这些基本上都是正确的,我只是想我会指出新的(ish)官方反应文档,它基本上推荐:-

    对于在 React 应用程序中发生变化的任何数据,都应该有一个单一的“事实来源”。通常,状态首先添加到需要它进行渲染的组件中。然后,如果其他组件也需要它,您可以将它提升到它们最近的共同祖先。与其尝试在不同组件之间同步状态,不如依赖自上而下的数据流。

    https://reactjs.org/docs/lifting-state-up.html。该页面还通过示例工作。

    【讨论】:

      【解决方案6】:

      我找到了以下将参数从子组件传递到父组件的有效且简单的解决方案:

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

      Look at JSFIDDLE

      【讨论】:

        【解决方案7】:

        您可以在父组件中创建一个 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
        猜你喜欢
        • 2015-08-16
        • 2020-10-06
        • 2018-10-05
        • 2021-03-14
        • 1970-01-01
        • 2021-03-21
        • 2015-04-15
        • 2018-04-29
        • 1970-01-01
        相关资源
        最近更新 更多