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アイテムの配列があります。親の状態にアクセスし、TodoFormhandleClickコンポーネントから新しいToDoアイテムを追加したいと思います。私の考えは、親に対してsetStateを実行することです。これにより、新しく追加されたtodoアイテムがレンダリングされます。



ここでスパムを送信します... npmjs.com/package/react-event-observer
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.
Matt

マウントされていないコンポーネントでsetStateを実行できないのと同じエラーが発生します。これに対する回避策はありましたか?
ケビンバートン

回答:


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

  ...

});

addTodoItemTodoFormのhandleClickで呼び出すことができます。これにより、親に対してsetStateが実行され、新しく追加されたToDoアイテムがレンダリングされます。あなたがアイデアを得ることを願っています。

ここでフィドル。


6
ここで<<オペレーターは何をしthis.state.todos << todoItem;ていますか?
ガブリエルギャレット2016年

@zavtra少しルビーの混乱が起こっていると思います
azium 2016

7
this.state直接変更するのは悪い習慣です。関数型setStateを使用することをお勧めします。reactjs.org/docs/react-component.html#setstate
Rohmer

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 HookuseStateで状態を維持している人のために、私は上記の提案を適応させて、以下のデモスライダーアプリを作成しました。デモアプリでは、子スライダーコンポーネントが親の状態を維持します。

デモも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のすべてのコードを上記のコードに置き換えることができます。
NicoWheat

こんにちは、私は反応するのが初めてで、疑問に思っていました:使用する必要がありますuseEffectか?なぜ親と子の両方の状態でデータを保存する必要があるのですか?
538ROMEO

1
これらの例は、親と子の両方にデータを保存する必要がある理由を示すことを目的としたものではありません。ほとんどの場合、保存する必要はありません。しかし、子供が親の状態を設定する必要がある状況に自分自身を見つけた場合、これはあなたがそれを行うことができる方法です。親の状態をchildStateの変更の効果として設定する場合は、useEffectが必要です。
ニコウィート

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

4
このコードは質問に答えることができますが、問題を解決する方法や理由に関する追加のコンテキストを提供すると、回答の長期的な価値が向上します。
nic3500 2018

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を子に渡す非常に簡単な方法の1つは、矢印関数内でそれを渡すことです。これは、次のように渡すことができる吊り上げられた環境を設定するときに機能します。

class ClassComponent ... {

    modifyState = () =>{
        this.setState({...})   
    }
    render(){
          return <><ChildComponent parentStateModifier={modifyState} /></>
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.