I'm trying to figure out how to set my React component's state based on the previous state, while also using the Immer library to allow for easier immutable state changes.
Normally, without Immer, I'd safely access the component's previous state using the updater
function version of setState()
:
this.setState(prevState => {
prevState.counter = prevState.counter + 1;
});
However, when using the Immer library to allow for easier immutable state changes, you no longer have access to the updater function, or the prevState variable. This is fine for most operations, as the Immer draft
proxy can be used for simple changes - but it doesn't work for more complex changes such as comparisons, as the proxy doesn't reflect the original state object:
this.setState(produce(draft => {
const index = draft.list.indexOf(item); // doesn't work - always returns -1
if (index > -1) {
draftState.list.splice(index, 1);
}
});
The problem is that since the draft
state is a proxy, comparisons such as indexOf
always fail, since the two objects are inherently different. But I don't want to just use this.state
in my produce function, as the React docs are very clear that you shouldn't rely on its value when calculating the new state, and I don't want to do the state update without Immer, as complex state object changes are significantly simpler when working with a mutable proxy object.
Is there any way to access an up to date version of the component's previous state, while still using the Immer produce
function in my setState
calls?
Edit: Updating to add a (simplified) example of my actual code:
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
fooBar: {
selectedOptions: []
}
};
}
handleClick(clickedOption) {
/*
if clickedOption is already selected
remove option from selectedOptions
else
add option to selectedOptions
*/
this.setState(produce(draft => {
const index = draft.fooBar.selectedOptions.indexOf(clickedOption); // The problem is here
if (index > -1) {
draft.fooBar.selectedOptions.splice(index, 1);
}
else {
draft.fooBar.selectedOptions.push(clickedOption);
}
}));
}
The problem is at the const index = ...
line. If I use draft
, then the indexOf
function always returns -1
, and if I use this.state
, then I'm not guaranteed an up-to-date version of selectedOptions
to compare against...