ARTICLE AD BOX
Illustrative example:
export const App = () => { const [things, setThings] = useStoredState(window.localStorage, 'things', [ { id: 0, name: 'New Thing' }, ]); const [selectedThingId, setSelectedThingId] = useStoredState(window.sessionStorage, 'selectedThingId', 0); const newThing = useCallback(() => { setThings(things => { const newId = 1 + Math.max(...things.map(thing => thing.id)); setSelectedThingId(newId); return [ ...things, { id: newId, name: 'New Thing' }, ]; }, [setThings, setSelectedThingId]); const deleteThing = useCallback(() => { setThings(things => { const remainingThings = things.filter(thing => thing.id !== selectedThingId); setSelectedThingId(remainingThings[0].id); return remainingThings; }, [setThings, setSelectedThingId, selectedThingId]); // remainder omitted }The problem I'm solving is updating two states at once, where the new value of the second should depend on the new value of the first.
The obvious solution is to use a single state so that a single setState with a callback can do everything at once, but there are cases where using a single state would be a hassle. In the above example (based on real code), the two states use a useStoredState hook that gives them different persistence behavior. Combining them would make the persistence logic a lot more complicated. Another place where I'm running into this is when a component has some state passed from a parent component, and additional local state.
In principle this sort of thing could also be done with effects, but in practice that would be a hassle even for simple cases like the above (you'd have to do something like keep around a separate list of known ids, and check it whenever things changed).
Is the above code safe? The callbacks have side effects, but they are idempotent. It seems dubious, but I can't think of any situation where they would actually go wrong.
