function useRecoilSync()

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],
  );
}