function useRecoilURLSync()

in packages/recoil-sync/RecoilSync_URL.js [159:255]


function useRecoilURLSync({
  storeKey,
  location: loc,
  serialize,
  deserialize,
  browserInterface,
}: RecoilURLSyncOptions): void {
  const {getURL, replaceURL, pushURL, listenChangeURL} = {
    ...DEFAULT_BROWSER_INTERFACE,
    ...(browserInterface ?? {}),
  };

  // Parse and cache the current state from the URL
  // Update cached URL parsing if properties of location prop change, but not
  // based on just the object reference itself.
  const memoizedLoc = useMemo(
    () => loc,
    // Complications with disjoint uniont
    // $FlowIssue[prop-missing]
    [loc.part, loc.queryParam], // eslint-disable-line fb-www/react-hooks-deps
  );
  const updateCachedState: () => void = useCallback(() => {
    cachedState.current = parseURL(getURL(), memoizedLoc, deserialize);
  }, [getURL, memoizedLoc, deserialize]);
  const cachedState = useRef<?ItemSnapshot>(null);
  // Avoid executing updateCachedState() on each render
  const firstRender = useRef(true);
  firstRender.current && updateCachedState();
  firstRender.current = false;
  useEffect(updateCachedState, [updateCachedState]);

  const write = useCallback(
    ({diff, allItems}) => {
      updateCachedState(); // Just to be safe...

      // This could be optimized with an itemKey-based registery if necessary to avoid
      // atom traversal.
      const atomRegistry = registries.get(storeKey);
      const itemsToPush =
        atomRegistry != null
          ? new Set(
              Array.from(atomRegistry)
                .filter(
                  ([, {history, itemKeys}]) =>
                    history === 'push' &&
                    Array.from(itemKeys).some(key => diff.has(key)),
                )
                .map(([, {itemKeys}]) => itemKeys)
                .reduce(
                  (itemKeys, keys) => itemKeys.concat(Array.from(keys)),
                  [],
                ),
            )
          : null;

      if (itemsToPush?.size && cachedState.current != null) {
        const replaceItems: ItemSnapshot = cachedState.current;
        // First, repalce the URL with any atoms that replace the URL history
        for (const [key, value] of allItems) {
          if (!itemsToPush.has(key)) {
            replaceItems.set(key, value);
          }
        }
        replaceURL(encodeURL(getURL(), loc, replaceItems, serialize));

        // Next, push the URL with any atoms that caused a new URL history entry
        pushURL(encodeURL(getURL(), loc, allItems, serialize));
      } else {
        // Just replace the URL with the new state
        replaceURL(encodeURL(getURL(), loc, allItems, serialize));
      }
      cachedState.current = allItems;
    },
    [getURL, loc, pushURL, replaceURL, serialize, storeKey, updateCachedState],
  );

  const read: ReadItem = useCallback(itemKey => {
    return cachedState.current?.has(itemKey)
      ? cachedState.current?.get(itemKey)
      : new DefaultValue();
  }, []);

  const listen = useCallback(
    ({updateAllKnownItems}) => {
      function handleUpdate() {
        updateCachedState();
        if (cachedState.current != null) {
          updateAllKnownItems(cachedState.current);
        }
      }
      return listenChangeURL(handleUpdate);
    },
    [listenChangeURL, updateCachedState],
  );

  useRecoilSync({storeKey, read, write, listen});
}