Reputation: 3056
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:
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.getDerivedStateFromProps
during setState
?Maybe some other design pattern or code is more suitable for my ReactJS form?
I am using ReactJ 16.x
Upvotes: 0
Views: 1677
Reputation: 3056
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();
}
}
Upvotes: 0
Reputation: 15292
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.
Upvotes: 1