-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathquery.ts
More file actions
131 lines (114 loc) · 3.47 KB
/
Copy pathquery.ts
File metadata and controls
131 lines (114 loc) · 3.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { useCallback, useMemo } from 'react'
export type Query = Record<string, string | string[]>
export type AbstractQuery = Record<string, unknown>
/**
* Parses the query.
*
* @returns The parameters that could be parsed. An empty record if no parameter could be parsed.
* @throws If the parsing failed.
*/
export type ParseQuery<T> = (query: Query) => Partial<T>
export type Router = {
/**
* @returns The current query. If a property is undefined by the current query, the corresponding property of the default query is taken, which might be undefined too.
*/
getQuery: (defaultQuery: Query) => Query
/**
* Updates the query by merging the given query with the current query. If a property is undefined by the given query and the current query, the corresponding default query is taken, which might be undefined too.
*/
setQuery: (query: Partial<Query>, defaultQuery: Query) => void
}
export const useQuery = (router: Router, defaultQuery: Query) => {
const query = router.getQuery(defaultQuery)
const setQuery = useCallback(
(query: Query) => {
router.setQuery(query, defaultQuery)
},
[router, defaultQuery],
)
const resetQuery = useCallback(() => {
router.setQuery(defaultQuery, defaultQuery)
}, [router, defaultQuery])
return {
query,
setQuery,
resetQuery,
}
}
export type AbstractQueryOptions = {
/**
* How the abstract query is converted to an actual query.
*
* @default Each value that is not undefined is converted to a string by calling `.toString()` or removed if not stringifyable.
*/
convertToQuery: (abstractQuery: Partial<AbstractQuery>) => Query
}
const DEFAULT_ABSTRACT_QUERY_OPTIONS: AbstractQueryOptions = {
convertToQuery: (abstractQuery) => {
const query: Query = {}
for (const [key, value] of Object.entries(abstractQuery)) {
if (Array.isArray(value)) {
query[key] = value.map((v: unknown) =>
typeof v?.toString === 'function'
? // eslint-disable-next-line @typescript-eslint/no-base-to-string
v.toString()
: '',
)
} else {
if (value !== undefined) {
query[key] =
typeof value?.toString === 'function'
? // eslint-disable-next-line @typescript-eslint/no-base-to-string
value.toString()
: ''
}
}
}
return query
},
}
export const useAbstractQuery = <
TQuery extends AbstractQuery,
TDefaultQuery extends Partial<TQuery>,
>(
router: Router,
parseQuery: ParseQuery<TQuery>,
defaultQuery: TDefaultQuery = {} as TDefaultQuery,
options?: Partial<AbstractQueryOptions>,
) => {
const mergedOptions = useMemo(
() => ({ ...DEFAULT_ABSTRACT_QUERY_OPTIONS, ...options }),
[options],
)
const convertedDefaultQuery = useMemo(
() => mergedOptions.convertToQuery(defaultQuery),
[mergedOptions, defaultQuery],
)
const { query, setQuery, resetQuery } = useQuery(
router,
convertedDefaultQuery,
)
const parsedQuery = useMemo(() => {
let parsed: Partial<TQuery>
try {
parsed = parseQuery(query)
} catch (e) {
parsed = {}
}
return {
...defaultQuery,
...parsed,
}
}, [defaultQuery, parseQuery, query])
const setAbstractQuery = useCallback(
(query: Partial<TQuery>) => {
setQuery(mergedOptions.convertToQuery(query))
},
[setQuery, mergedOptions],
)
return {
query: parsedQuery,
setQuery: setAbstractQuery,
resetQuery,
}
}