import * as React from "react"; import { useRouteLoaderData } from "react-router"; type LoaderData = Record & { tags?: string[] }; export function searchAndPatch( data: Record, type: string, id: string, set: Record, ) { if (!data) { return false; } if (Array.isArray(data)) { let didUpdate = false; data.forEach((item) => { const du = searchAndPatch(item, type, id, set); if (du) { didUpdate = true; } }); return didUpdate; } else if (typeof data === "object") { if (data.id === id && data.__typename === type) { Object.assign(data, set); return true; } else { let didUpdate = false; Object.values(data).forEach((value) => { const du = searchAndPatch(value, type, id, set); if (du) { didUpdate = true; } }); return didUpdate; } } return false; } export function useOptimisticData(id?: string) { const routeId = id || React.useId(); const _loaderData = useRouteLoaderData(routeId) as LoaderData; const [loaderData, setLoaderData] = React.useState(_loaderData); React.useEffect(() => { setLoaderData(_loaderData); }, [_loaderData]); React.useEffect(() => { const listener = (e: CustomEvent) => { const { type, id, set } = e.detail as { type: string; id: string; set: Record; }; const currentTags = loaderData?.tags || []; const possibleTags = [`type:${type}`, `type:${type}:id:${id}`]; if ( type && id && Object.keys(set ?? {}).length && possibleTags.some((tag) => currentTags.includes(tag)) ) { const didUpdate = searchAndPatch(loaderData, type, id, set); if (didUpdate) { React.startTransition(() => { setLoaderData({ ...loaderData }); }); } } }; document.addEventListener("DB:UPDATE", listener, false); return () => { document.removeEventListener("DB:UPDATE", listener, false); }; }, [routeId]); return loaderData; }