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
text/typescript
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,
)
}