ReactJS: setState บนพาเรนต์ภายในคอมโพเนนต์ลูก


91

อะไรคือรูปแบบที่แนะนำสำหรับการทำ 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's handleClickส่วนประกอบ ความคิดของฉันคือการทำ setState บนพาเรนต์ซึ่งจะแสดงรายการสิ่งที่ต้องทำที่เพิ่มใหม่


1
สิ่งนี้ช่วยได้ไหมstackoverflow.com/questions/24147331/…
Dhiraj

แค่สแปมที่นี่ ... 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 บนส่วนประกอบที่ไม่ได้ต่อเชื่อม มีวิธีแก้ปัญหานี้หรือไม่?
Kevin Burton

คำตอบ:


82

ในผู้ปกครองของคุณคุณสามารถสร้างฟังก์ชัน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 บนพาเรนต์ซึ่งจะแสดงรายการสิ่งที่ต้องทำที่เพิ่มใหม่ หวังว่าคุณจะได้รับความคิด

ซอที่นี่


6
ตัว<<ดำเนินการในthis.state.todos << todoItem;การทำอะไรที่นี่?
Gabriel Garrett

@zavtra ความสับสนของ Little Ruby เกิดขึ้นฉันเดา
azium

7
การกลายพันธุ์this.stateโดยตรงเป็นการปฏิบัติที่ไม่ดี ดีกว่าที่จะใช้ setState ที่ใช้งานได้ reactjs.org/docs/react-component.html#setstate
Rohmer


1
วิธีการแก้ไข (ปรับปรุง) นี้จะถูกนำไปใช้โดยใช้ React hooks?
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

7

สำหรับผู้ที่รักษาสถานะด้วย 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 ด้วยโค้ดด้านบน
NicoWheat

สวัสดีฉันเพิ่งเริ่มตอบสนองและสงสัยว่า: จำเป็นต้องใช้useEffectหรือไม่? เหตุใดเราจึงต้องจัดเก็บข้อมูลทั้งในสถานะแม่และลูก
538ROMEO

1
ตัวอย่างไม่ได้มีวัตถุประสงค์เพื่อแสดงว่าเหตุใดเราจึงต้องจัดเก็บข้อมูลทั้งในผู้ปกครองและเด็กโดยส่วนใหญ่แล้วคุณไม่จำเป็นต้องทำ แต่ถ้าคุณพบว่าตัวเองอยู่ในสถานการณ์ที่เด็กควรตั้งค่าสถานะความเป็นพ่อแม่นี่คือวิธีที่คุณสามารถทำได้ useEffect เป็นสิ่งที่จำเป็นหากคุณต้องการตั้งค่าสถานะแม่เป็นผลของ childState ที่เปลี่ยนแปลง
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} /></>
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.