ReactJS setState conflicts with getDerivedStateFro

2019-08-19 03:24发布

问题:

I am trying to follow the design pattern in https://github.com/reactjs/rfcs/issues/26 (see replies by bvaughn) and create ReactJS editable form, that reads data from the server, show then in the form fields, allows the use to edit values and then save those data back in database.

I have some essential code:

const mapStateToProps = state => {
    return {
        invoice: state.invoice,
        invoiceReady: state.invoiceReady,
    };
};

 static getDerivedStateFromProps(nextProps, prevState) {
        if (nextProps.match.params.id !== prevState.id) {
            return update(
                prevState, {
                    ['id']: {$set: nextProps.match.params.id},
                    ['invoiceReady']: {$set: false}
                });
        } else {
            return update(
                prevState, {
                    ['invoice']: {$set: nextProps.invoice},
                    ['invoiceReady']: {$set: nextProps.invoiceReady}
                });
        }
    }

 handleChange(event) {
        const state = update(
            this.state, {
                invoice: {
                    [event.target.id]: {$set: event.target.value}
                }
            }
        );
        this.setState(state);
    }

componentDidMount() {
        this.requestInvoice();
    }

componentDidUpdate(prevProps, prevState) {
        if (!this.state.invoiceReady) {
            this.requestInvoice();
        }
    }

and my JSX code contains editable fields like:

                        <input
                            type="text"
                            className="form-control"
                            id="invoiceDate"
                            value={this.state.invoice.invoiceDate}
                            onChange={this.handleChange}
                        />

So - according to the mentioned pattern I am initiating the request to the server by requestInvoice, some other components handle this action and receives and saves the invoice into the Redux store and that is why those data are automatically written into the props.invoice variable and I am further writing props.invoice into this.state.invoice by getDerivedStateFromProps for further local processing of the invoice data. During the work with the form the handleChange events are raised and with the help of immutability-helper (update function) the updated fields are written into new state and setState is called.

My problem is that during the setState call the React is calling getDerivedStateFromProps and so the props.invoice overwrites any changes that are coming from the user and that ar processed into handleChange:setState function.

So - how to solve this problem: the conflict between setState and getDerivedStateFromProps?

Several options may be:

  • my design can be flawed. E.g. maybe I should not try to move props.invoice into this.state.invoice (why not?)? But from the other side it would be nice to save invoice into this.state and be sure that all the changes are applied to the this.state.invoice during the editing of the invoice.
  • mybe I should prevent execution of getDerivedStateFromProps during setState?

Maybe some other design pattern or code is more suitable for my ReactJS form?

I am using ReactJ 16.x

回答1:

If you are decided to use react-redux for component state management, then you should avoid to manage the that component internal state using setState as its controlled by redux store.

Otherwise it will causes such issue and lots of if-else you may need to handle it.

considering your scenario, During handleChange instead calling setState,better to dispatch an action to update the state in redux store and then via connect it will reflect to your component in the same way you calling API.

handleChange(event) {
        const stateData = invoice: {
                    [event.target.id]: {$set: event.target.value}
                }; 
        this.props.callDispatch({'UPDATE_SOME_DATA',stateData})
    }

Only thing need to take care, make sure reducers update only required passed properties data and leave other data intact updated by API dispatch action process.



回答2:

I have arrived at the following solution - with the help of this.state.stateUpdate variable - and I consider this as the answer to my question (please comment if this is not sound and neat solution, I can accept other answer as well if that works):

 const mapStateToProps = state => {
        return {
            invoice: state.invoice,
            invoiceReady: state.invoiceReady,
            stateUpdate: false 
        };
    };

     static getDerivedStateFromProps(nextProps, prevState) {
            if (prevState.stateUpdate) {
              return update(
                prevState, {
                    stateUpdate: {$set: false}
                }
              )
            }
            if (nextProps.match.params.id !== prevState.id) {
                return update(
                    prevState, {
                        ['id']: {$set: nextProps.match.params.id},
                        ['invoiceReady']: {$set: false}
                    });
            } else {
                return update(
                    prevState, {
                        ['invoice']: {$set: nextProps.invoice},
                        ['invoiceReady']: {$set: nextProps.invoiceReady}
                    });
            }
        }

     handleChange(event) {
            const state = update(
                this.state, {
                    invoice: {
                        [event.target.id]: {$set: event.target.value}
                    },
                    stateUpdate: {$set: true}
                }
            );
            this.setState(state);
        }

    componentDidMount() {
            this.requestInvoice();
        }

    componentDidUpdate(prevProps, prevState) {
            if (!this.state.invoiceReady) {
                this.requestInvoice();
            }
        }