The useRecoilState properly applies multiple state updates made in one batch, just like original useState. On the other hand useRecoilCallback with set does not:
const $test = atomFamily({
key: "test",
default: { a: 0, b: 0 },
});
// ...
const [x, setX] = useState({ a: 0, b: 0 });
const [y, setY] = useRecoilState($test("xyz"));
const setYCB = useRecoilCallback(({ set }, n) => {
set($test("xyz"), (x) => ({ ...x, c: n }));
}, []);
useEffect(() => {
// update state multiple times with respect to previous update
setX((x) => ({ ...x, a: 1 }));
setX((x) => ({ ...x, b: 1 }));
setY((x) => ({ ...x, a: 1 }));
setY((x) => ({ ...x, b: 1 }));
// this breaks the state, basically it ignores those 2 previous updates made
setYCB(1);
}, []);
useEffect(() => {
console.log("x", x);
// ->聽{ a: 1, b: 1 }
}, [x]);
useEffect(() => {
console.log("y", y);
// ->聽{ a: 0, b: 0, c: 1 }, but expected result should be { a: 1, b: 1, c: 1 }
}, [y]);
From my point of view it seems like a bug.
This was fixed with #260
useRecoilCallback() is now provided a snapshot of state which is consistent with the stable committed state at the start of the transaction. This would include the state before the effect started to execute. When it sets an atom with the updater form, the parameter providing the "previous value" is consistent with all previous state changes queued during the current transaction, this should include visibility on sets during the effect before the callback was executed.
Add unit test with #333
Most helpful comment
This was fixed with #260
useRecoilCallback()is now provided a snapshot of state which is consistent with the stable committed state at the start of the transaction. This would include the state before the effect started to execute. When it sets an atom with the updater form, the parameter providing the "previous value" is consistent with all previous state changes queued during the current transaction, this should include visibility on sets during the effect before the callback was executed.Add unit test with #333