UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

60 lines (55 loc) 2.81 kB
import {useMemo, useState} from 'react' import {useSyncExternalStoreWithSelector} from 'use-sync-external-store/with-selector' import {type RouterHistory} from '../router' import {type WorkspacesContextValue} from '../workspaces' import {createCommonBasePathRegex} from './createCommonBasePathRegex' import {matchWorkspace, type MatchWorkspaceResult} from './matchWorkspace' import {useNormalizedWorkspaces} from './useNormalizedWorkspaces' /** * Reads the `history` pathname and responds to changes, returns matching workspace * @internal */ export function useSyncPathnameWithWorkspace( history: RouterHistory, _workspaces: WorkspacesContextValue, ): MatchWorkspaceResult { // Workspaces changes infrequently, but router matching can fire a lot. And so there's value in memoizing the normalized // to avoid creating new arrays on every render. const workspaces = useNormalizedWorkspaces(_workspaces) // As with `workspaces` there's value in only create the recursive basePath regex if there's `workspaces` have at all changed const basePathRegex = useMemo(() => createCommonBasePathRegex(workspaces), [workspaces]) // history.location is mutable, so we snapshot it with useState to preserve the original pathname const [serverSnapshot] = useState(() => history.location.pathname) // React will only re-subscribe if store.subscribe changes identity, so by memoizing the whole store // we ensure that if any of the dependencies used by store.selector changes, we'll re-subscribe. // If we don't, we risk hot reload seeing stale workspace configs as the user is editing them. const store = useMemo(() => { return { subscribe: (onStoreChange: () => void) => history.listen(onStoreChange), getSnapshot: () => history.location.pathname, getServerSnapshot: () => serverSnapshot, selector: (pathname: string) => matchWorkspace({basePathRegex, pathname, workspaces}), isEqual: (a: MatchWorkspaceResult, b: MatchWorkspaceResult) => { if (a.type !== b.type) return false switch (a.type) { case 'match': return a.workspace === (b as typeof a).workspace case 'redirect': return a.pathname === (b as typeof a).pathname case 'not-found': return true default: // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TS thinks this will never happen, but the point of the error is if it somehow did throw new Error(`Unknown type: ${(a as any).type}`) } }, } }, [basePathRegex, history, serverSnapshot, workspaces]) return useSyncExternalStoreWithSelector<string, MatchWorkspaceResult>( store.subscribe, store.getSnapshot, store.getServerSnapshot, store.selector, store.isEqual, ) }