in packages/recoil-sync/RecoilSync.js [319:479]
function useRecoilSync({
storeKey,
write,
read,
listen,
}: RecoilSyncOptions): void {
const recoilStoreID = useRecoilStoreID();
// Subscribe to Recoil state changes
const snapshot = useRecoilSnapshot();
const previousSnapshotRef = useRef(snapshot);
useEffect(() => {
if (write != null && snapshot !== previousSnapshotRef.current) {
previousSnapshotRef.current = snapshot;
const diff: ItemDiff = new Map();
const atomRegistry = registries.getAtomRegistry(recoilStoreID, storeKey);
const modifiedAtoms = snapshot.getNodes_UNSTABLE({isModified: true});
for (const atom of modifiedAtoms) {
const registration = atomRegistry.get(atom.key);
if (registration != null) {
const atomInfo = snapshot.getInfo_UNSTABLE(registration.atom);
// Avoid feedback loops:
// Don't write to storage updates that came from listening to storage
if (
(atomInfo.isSet &&
atomInfo.loadable?.contents !==
registration.pendingUpdate?.value) ||
(!atomInfo.isSet &&
!(registration.pendingUpdate?.value instanceof DefaultValue))
) {
for (const [, {options}] of registration.effects) {
writeAtomItemsToDiff(
diff,
options,
read,
atomInfo.isSet || options.syncDefault === true
? atomInfo.loadable
: null,
);
}
}
delete registration.pendingUpdate;
}
}
if (diff.size) {
write(
getWriteInterface(
recoilStoreID,
storeKey,
diff,
snapshot.getInfo_UNSTABLE,
),
);
}
}
}, [read, recoilStoreID, snapshot, storeKey, write]);
const updateItems = useRecoilTransaction_UNSTABLE(
({set, reset}) =>
(diff: ItemDiff) => {
const atomRegistry = registries.getAtomRegistry(
recoilStoreID,
storeKey,
);
// TODO iterating over all atoms registered with the store could be
// optimized if we maintain a reverse look-up map of subscriptions.
for (const [, atomRegistration] of atomRegistry) {
// Iterate through the effects for this storage in reverse order as
// the last effect takes priority.
for (const [, effectRegistration] of Array.from(
atomRegistration.effects,
).reverse()) {
const {options, subscribedItemKeys} = effectRegistration;
// Only consider updating this atom if it subscribes to any items
// specified in the diff.
if (setIntersectsMap(subscribedItemKeys, diff)) {
const loadable = readAtomItems(effectRegistration, read, diff);
if (loadable != null) {
switch (loadable.state) {
case 'hasValue':
if (loadable.contents instanceof DefaultValue) {
atomRegistration.pendingUpdate = {value: DEFAULT_VALUE};
reset(atomRegistration.atom);
} else {
atomRegistration.pendingUpdate = {
value: loadable.contents,
};
set(atomRegistration.atom, loadable.contents);
}
break;
case 'hasError':
if (options.actionOnFailure_UNSTABLE === 'errorState') {
// TODO Async atom support to allow setting atom to error state
// in the meantime we can just reset it to default value...
atomRegistration.pendingUpdate = {value: DEFAULT_VALUE};
reset(atomRegistration.atom);
}
break;
case 'loading':
// TODO Async atom support
throw err(
'Recoil does not yet support setting atoms to an asynchronous state',
);
}
// If this effect set the atom, don't bother with lower-priority
// effects. But, if the item didn't have a value then reset
// below but ontinue falling back on other effects for the same
// storage. This can happen if multiple effects are used to
// migrate to a new itemKey and we want to read from the
// older key as a fallback.
break;
} else {
atomRegistration.pendingUpdate = {value: DEFAULT_VALUE};
reset(atomRegistration.atom);
}
}
}
}
},
[recoilStoreID, storeKey, read],
);
const updateItem = useCallback(
<T>(itemKey: ItemKey, newValue: DefaultValue | T) => {
updateItems(new Map([[itemKey, newValue]]));
},
[updateItems],
);
const updateAllKnownItems = useCallback(
itemSnapshot => {
// Reset the value of any items that are registered and not included in
// the user-provided snapshot.
const atomRegistry = registries.getAtomRegistry(recoilStoreID, storeKey);
for (const [, registration] of atomRegistry) {
for (const [, {subscribedItemKeys}] of registration.effects) {
for (const itemKey of subscribedItemKeys) {
if (!itemSnapshot.has(itemKey)) {
itemSnapshot.set(itemKey, DEFAULT_VALUE);
}
}
}
}
updateItems(itemSnapshot);
},
[recoilStoreID, storeKey, updateItems],
);
useEffect(
() =>
// TODO try/catch errors and set atom to error state if actionOnFailure is errorState
listen?.({updateItem, updateAllKnownItems}),
[updateItem, updateAllKnownItems, listen],
);
// Register Storage
// Save before effects so that we can initialize atoms for initial render
registries.setStorage(recoilStoreID, storeKey, {write, read});
useEffect(
() => registries.setStorage(recoilStoreID, storeKey, {write, read}),
[recoilStoreID, storeKey, read, write],
);
}