简体   繁体   English

修复未重新渲染的子组件的方法(由于数据的更改是作为props而不是状态传递的)?

[英]Ways to fix a child component that is not re-rendering (due to change in data being passed in as props, not state)?

Background 背景

I am working on a Meteor app that uses ReactJS as the rendering library. 我正在使用一个使用ReactJS作为渲染库的Meteor应用程序。

Currently, I'm having trouble with having a Child component re-render when data has been updated, even though the parent is accessing the updated data & supposedly passing it down to the child. 目前,我在更新数据时重新渲染子组件时遇到问题,即使父级正在访问更新的数据并且据称将其传递给子级。

The Parent component is a Table of Data. Parent组件是一个数据表。 The Child component is a Click-to-edit date field. 子组件是单击编辑日期字段。

The way it (theoretically) works : Parent component passes the existing data for date into child component as a prop. 它(理论上)的工作方式 :父组件将日期的现有数据作为prop传递给子组件。 Child component takes that existing props data, handles it & sets some states using it and then has 2 options: 子组件获取现有的道具数据,处理它并使用它设置一些状态,然后有2个选项:

  • default: display data 默认:显示数据
  • if user clicks on data field: change to input & allow user to select date (using react-datepicker), changing the state--when user clicks outside of field, triggers a return to display only & saves the updated data from state to database 如果用户点击数据字段:更改为输入并允许用户选择日期(使用react-datepicker),更改状态 - 当用户在字段外单击时,触发返回仅显示并将更新的数据从状态保存到数据库

I am using the child component twice per row in the table, and each time it is used, it needs access to the most current date data from the database. 我在表中每行使用子组件两次,每次使用它时,它需要访问数据库中的最新日期数据。 So if it the data is changed in one field, the second field should reflect that change. 因此,如果数据在一个字段中更改,则第二个字段应反映该更改。

Problem 问题

The second field is not reflecting changes in the database unless I manually refresh the page and force the child component to render with the new data. 除非我手动刷新页面并强制子组件使用新数据进行渲染,否则第二个字段不会反映数据库中的更改。 The edited field is reflecting the data change, because it's reflecting what's stored in state. 编辑的字段反映数据的变化,因为它反映了什么是存储在状态。

After reading React documentation, I am sure the problem is because the date is coming in as a prop, then being handled as a state--and because the component isn't going to re-render from a prop change. 阅读完React文档之后,我确定问题是因为日期是作为一个prop进入,然后作为一个状态处理 - 并且因为该组件不会从prop更改中重新呈现。

My Question 我的问题

What do I do to fix this? 我该怎么做才能解决这个问题?

All of the reading I've done of the docs has strongly recommended staying away from things like forceUpdate() and getDerivedStateFromProps(), but as a result I'm not sure how to have data pass through the way that I want it to. 我对文档所做的所有阅读强烈建议远离诸如forceUpdate()和getDerivedStateFromProps()之类的东西,但结果我不确定如何通过我想要的方式传递数据。

Thoughts? 思考?

My Code 我的守则

I've abbreviated the code a little bit & removed variable names specific to my project, but I can provide more of the actual if it'll help. 我已经将代码缩减了一点并删除了特定于我的项目的变量名称,但是如果它有帮助我可以提供更多的实际代码。 I think my question is more conceptual than it is straight up debugging. 我认为我的问题比直接调试更具概念性。

Parent

ParentComponent () {
    //Date comes as a prop from database subscription
    var date1 = this.props.dates.date1 
    var date2 = this.props.dates.date2
    return(
        <ChildComponent 
            id='handle-date-1'
            selected={[date1, date2]} />
        <ChildComponent 
            id='handle-date-2'
            selected={[date1, date2]} />
    )
}

Child 儿童

ChildComponent() {
    constructor(props) {
        super(props);
        this.state = {
            date1: this.props.selected[0],
            date2: this.props.selected[1],
            field: false,
        };
    }

    handleDatePick() {
        //Handles the event listeners for clicks in/out of the div, handles calling Meteor to update database.
    }
    renderDate1() {
        return(
            <div>
                {this.state.field == false &&
                    <p onClick={this.handleClick}>{formatDate(this.state.date1)}</p>}
                {this.state.field == true &&
                    <DatePicker
                        selected={this.state.date1}
                        startDate={this.state.date1}
                        onChange={this.handleDatePick}
                    />
                }
            </div>
        )
    }
    renderDate2() {
        return(
            <div>
                {this.state.field == false &&
                    <p onClick={this.handleClick}>{formatDate(this.state.date2)}</p>}
                {this.state.field == true &&
                    <DatePicker
                        selected={this.state.date2}
                        startDate={this.state.date2}
                        onChange={this.handleDatePick}
                    />
                }
            </div>
        )
    }
    render() {
        return(
            //conditionally calls renderDate1 OR renderDate2
        )
    }
}

(If this code/my explanation is rough, it's because I'm still a fairly beginner/low level developer. I don't have formal training, so I'm learning on the job while working on a pretty difficult app. As a solo developer. It's a long story. Please be gentle!) (如果这个代码/我的解释很粗糙,那是因为我还是一个相当初学者/低级别的开发人员。我没有接受过正式培训,所以我在学习一个非常困难的应用程序时正在学习这个工作。独唱开发者。这是一个很长的故事。请温柔!)

The React docs have a section on best practice usage of constructor() . React文档有一节关于constructor()最佳实践用法。 Reading that, paying close attention to the yellow-highlighted "Note" section, should illuminate the exact problem you're running into. 阅读,密切注意黄色突出显示的“注意”部分,应该说明你遇到的确切问题。

Essentially, constructor() is only run once, and is most commonly used to initialize internal/local state (or bind methods). 本质上, constructor()只运行一次,最常用于初始化内部/本地状态(或绑定方法)。 This means that your child component is setting date1 and date2 with the values from your selected prop once when constructor() is called for that child. 这意味着你的子组件设置date1date2从您的价值观selected道具时, 一次 constructor()被调用那个孩子。 Whatever the value of selected is at the time constructor() is called will be set to the child's state, and will remain the same even if the value continues to change. 无论selected的值是什么,当constructor()被调用时将被设置为子状态,并且即使值继续改变也将保持不变。

Thus, any successive updates to the selected prop passed to a child component will not be reflected in that component's internal state, meaning there will be no re-render of that component. 因此,传递给子组件的selected prop的任何连续更新都不会反映在该组件的内部状态中,这意味着不会重新呈现该组件。 You'll need to make use of React's setState() method elsewhere in your child component to properly update that child's state and trigger a re-render. 您需要在子组件的其他位置使用React的setState()方法来正确更新该子级的状态并触发重新呈现。

Using a combination of the React lifecycle methods to properly update your child components is the way to go. 使用React生命周期方法的组合来正确更新子组件是可行的方法。 The snippet below gives you the main idea of what to change about your implementation in componentDidMount() and componentDidUpdate() 下面的代码片段为您提供了有关在componentDidMount()componentDidUpdate()实现更改的主要想法

 class Child extends React.Component { constructor(props) { super(props); this.state = { date1: 0, date2: 0, }; } /* Any time the child mounts to the DOM, you can use the method below to set your state using the current value of your selected prop from the parent component... */ componentDidMount() { this.setState({ date1: this.props.selected[0], date2: this.props.selected[1] }); } /* When the child receives an update from its parent, in this case a new date selection, and should re-render based on that update, use the method below to make a comparison of your selected prop and then call setState again... */ componentDidUpdate(prevProps) { if (prevProps.selected !== this.props.selected) { this.setState({ date1: this.props.selected[0], date2: this.props.selected[1] }); } } render() { const { date1, date2 } = this.state; return ( <div style={{ border: `4px dotted red`, margin: 8, padding: 4 }}> <h1 style={{ fontWeight: 'bold' }}>Child</h1> <h2>The value of date1 is {date1}</h2> <h2>The value of date2 is {date2}</h2> </div> ); } } class Parent extends React.Component { constructor(props) { super(props); this.state = { valOne: 0, valTwo: 0 }; } incrementOne = () => { this.setState(prevState => ({ valOne: (prevState.valOne += 1) })); }; incrementTwo = () => { this.setState(prevState => ({ valTwo: (prevState.valTwo += 1) })); }; render() { const { valOne, valTwo } = this.state; return ( <div style={{ border: `4px solid blue`, margin: 8, padding: 4 }}> <h1 style={{ fontWeight: 'bold', fontSize: 18 }}>Parent</h1> <button onClick={() => this.incrementOne()}>Increment date1</button> <button onClick={() => this.incrementTwo()}>Increment date2</button> <Child selected={[valOne, valTwo]} /> </div> ); } } ReactDOM.render(<Parent />, document.querySelector('#app')); 
 <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id="app"></div> 

Since you're using the React Component pattern for your children, making good use of the React lifecyle methods will really help you out here. 由于您正在为您的孩子使用React Component模式,因此充分利用React生命周期方法将真正帮助您。 I can't suggest strongly enough that you learn the React component lifecyle . 我不能强烈建议你学习React组件的生命周期 As you continue to use React, it will become essential in cases such as this. 当你继续使用React时,在这种情况下它将变得至关重要。 componentDidMount() and componentDidUpdate() are good places to start. componentDidMount()componentDidUpdate()是很好的起点。

Hope that this helps. 希望这会有所帮助。 Let us know how it turns out. 让我们知道结果如何。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM