While prospecting the use of useState with the great Immer, I ended up finding the small use-immer package, which unifies both things. However, it doesn’t have proper TypeScript typings.
So I wrote my own hook to perform this task: just like useState, it returns a value and a setter, but the setter’s argument is a mutable state, automatically wired to produce, and all that using a generic argument for proper typing:
import {useCallback, useState} from 'react';
import {Draft, produce} from 'immer';
export function useStateImmer<T>(initialState: T | (() => T)): [
T,
(updater: (draft: Draft<T>) => void) => void,
] {
const [obj, setObj] = useState(initialState);
const setObjProduce = useCallback((updater: (draft: Draft<T>) => void): void => {
setObj(curState => {
return produce(curState, draft => {
updater(draft);
});
});
}, [setObj]);
return [obj, setObjProduce];
}
Note that the returned setter function is identity, since it’s guarded by an useCallback call.