0

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...

Fateh Khalsa
  • 1,326
  • 1
  • 15
  • 19

1 Answers1

0

You could implement a usePrevious() hook yourself to store any previous variable:

function usePrevious(value) {
  const ref = useRef();
  // Store current value in ref
  useEffect(() => {
    ref.current = value;
  }, [value]); // Only re-run if value changes
  // Return previous value (happens before update in useEffect above)
  return ref.current;
}
Ryan Le
  • 7,708
  • 1
  • 13
  • 23