pagesHooks.ts77 lines · main
1'use client'
2
3import { useLayoutEffect, useMemo } from 'react'
4import { useLatest } from 'react-use'
5import { useSnapshot } from 'valtio'
6
7import { useCommandContext } from '../../internal/Context'
8import { isComponentPage, type PageDefinition } from '../../internal/state/pagesState'
9import { useSetQuery } from './queryHooks'
10
11const useCurrentPage = () => {
12 const { pagesState } = useCommandContext()
13 const { commandPages, pageStack } = useSnapshot(pagesState)
14
15 const topOfStack = pageStack.at(-1)
16 const result = useMemo(
17 () =>
18 topOfStack && commandPages[topOfStack]
19 ? { ...commandPages[topOfStack], name: topOfStack }
20 : undefined,
21 [commandPages, topOfStack]
22 )
23
24 return result
25}
26
27const usePageComponent = () => {
28 const _currentPage = useCurrentPage()
29
30 const currentPage = _currentPage as PageDefinition
31 if (!currentPage || !isComponentPage(currentPage)) return undefined
32
33 return currentPage.component
34}
35
36const useSetPage = () => {
37 const { pagesState } = useCommandContext()
38 const setQuery = useSetQuery()
39
40 return (name: string, preserveQuery: boolean = false) => {
41 pagesState.appendPageStack(name)
42 if (!preserveQuery) setQuery('')
43 }
44}
45
46const usePopPage = () => {
47 const { pagesState } = useCommandContext()
48 const { popPageStack } = useSnapshot(pagesState)
49
50 return popPageStack
51}
52
53const EMPTY_ARRAY = [] as any[]
54
55const useRegisterPage = (
56 name: string,
57 definition: PageDefinition,
58 { deps = EMPTY_ARRAY, enabled = true }: { deps?: any[]; enabled?: boolean } = {}
59) => {
60 const { pagesState } = useCommandContext()
61 const { registerNewPage } = useSnapshot(pagesState)
62
63 const definitionRef = useLatest(definition)
64
65 // useLayoutEffect runs synchronously after DOM mutations but before paint.
66 // This ensures the page is registered before any subsequent code (e.g. in
67 // child components) tries to access it, avoiding a timing gap that would
68 // exist with useEffect.
69 useLayoutEffect(() => {
70 if (!enabled) return
71
72 const unsubscribe = registerNewPage(name, definitionRef.current)
73 return unsubscribe
74 }, [registerNewPage, name, enabled, ...deps])
75}
76
77export { useCurrentPage, usePageComponent, usePopPage, useRegisterPage, useSetPage }