If you’ve used Redux at some point, theres a good chance you’ve heard of or even used Immer. Immer was created as a way of making Redux reducers easier to deal with.
When React moved from classes to functions, the useState and useReducer were actually inspired by Redux and as a side effect, immer became something we could use with hooks.
Unfortunately, most developers had Redux-Burnout, so most people haven’t touched immer since then, preferring to “keep things simple” with useState. However, sometimes you still end up with complex objects/arrays inside of a useState and end up doing some things that would be considered “weird” if you weren’t using react.
Imagine you’re using a table library (e.g. @mui/x-data-grid-pro) that has you pass in an array of columns (headers) and rows (data). Now lets say you’re building a fintech app that has a toggle to display transactions in usd-$ and euros-€. To do this in react we’ve have to do something like this. Since when we call a useState setter, the array/object passed in needs to have a different reference, we need to use .map to create a copy of our array.
This feels a lot more natural. We’re just “mutating” the draft (proxy) object, which takes care of translating the “mutation” operations into immutable operations.
Not only is this more natural, Immer also makes the code you write simpler. In my DataGrid example, we can actually take it a step further.
// Outside our component, next to were we define the COLUMNSconst INDEX = draft.findIndex(column => COLUMNS.field.startsWith('amount'))// ...const [columns, setColumns] = useState(COLUMNS)useEffect(() => { setColumns(produce(draft => { draft[INDEX] = props.displayInUSD ? AMOUNT_USD : AMOUNT_EURO }))}, [props.displayInUSD])
In a more complex example, this might actually make some performance gains.
Immer is life-changing as a JS dev, and I’m not even exaggerating :) Like, it’s right up there with Prettier in terms of “wow this package is amazing, how did I ever live without it?”
—Mark Erikson, (the) Redux Maintainer
Fun Fact 2
Immer (German for: always) is a tiny package that allows you to work with immutable state in a more convenient way.
If you plan on using immer heavilty and want to avoid having to call produce all the time, theres actually a useImmer utility hook that works just like you’d expect:
Works like useState when setting a value e.g. setState(42)
Works like produce in the callback: setState(draft => { draft.value = 42 })