diff --git a/readme.md b/readme.md
index 596f32f..a6ea959 100644
--- a/readme.md
+++ b/readme.md
@@ -58,7 +58,7 @@ const parseSearch = (query: Query) => {
export function UserList() {
const { page, size, query, setQuery, setPage, resetQuery } =
- useQueryAndPagination({ search: '' }, parseSearch)
+ useQueryAndPagination(parseSearch, { search: '' })
return (
@@ -116,10 +116,14 @@ const users = [
export function UserList() {
const { page, size, query, setQuery, setPage, resetQuery } =
- useQueryAndPagination({ name: '', age: 0 }, userSchema, {
- page: 0,
- size: 4,
- })
+ useQueryAndPagination:
+ userSchema,
+ { name: '', age: 0 },
+ {
+ page: 0,
+ size: 4,
+ },
+ )
return (
diff --git a/src/engine/pagination.ts b/src/engine/pagination.ts
index c462789..4ba0a3e 100644
--- a/src/engine/pagination.ts
+++ b/src/engine/pagination.ts
@@ -46,11 +46,14 @@ const parsePagination = (query: Query): Partial
=> {
}
}
-export const useAbstractQueryAndPagination = (
- defaultQuery: T,
- parseQuery: ParseQuery,
+export const useAbstractQueryAndPagination = <
+ TQuery extends AbstractQuery,
+ TDefaultQuery extends Partial,
+>(
router: Router,
- defaultPagination?: PaginationQuery,
+ parseQuery: ParseQuery,
+ defaultQuery: TDefaultQuery,
+ defaultPagination?: Partial,
options?: Partial,
) => {
const mergedDefaultPagination = {
@@ -63,9 +66,9 @@ export const useAbstractQueryAndPagination = (
}
const { query, setQuery } = useAbstractQuery(
- mergedDefaultQueryAndPagination,
- parseQuery,
router,
+ parseQuery,
+ mergedDefaultQueryAndPagination,
options,
)
@@ -74,15 +77,18 @@ export const useAbstractQueryAndPagination = (
setQuery: setPagination,
resetQuery: resetPagination,
} = useAbstractQuery(
- mergedDefaultPagination,
- parsePagination,
router,
+ parsePagination,
+ mergedDefaultPagination,
options,
)
return {
query,
- setQuery: (query: Partial, options?: Partial) => {
+ setQuery: (
+ query: Partial,
+ options?: Partial,
+ ) => {
const mergedOptions = { ...DEFAULT_CHANGE_QUERY_OPTIONS, ...options }
setQuery({
...query,
diff --git a/src/engine/query.ts b/src/engine/query.ts
index e5f73f4..31b7bf3 100644
--- a/src/engine/query.ts
+++ b/src/engine/query.ts
@@ -24,17 +24,17 @@ export type ParseQuery = (query: Query) => Partial
export type Router = {
/**
- * @returns The current query.
+ * @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.
+ * 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, defaultQuery: Query) => void
}
-export const useQuery = (defaultQuery: T, router: Router) => {
+export const useQuery = (router: Router, defaultQuery: T) => {
const resetQuery = useCallback(() => {
router.setQuery(defaultQuery, defaultQuery)
}, [router, defaultQuery])
@@ -71,10 +71,13 @@ const DEFAULT_ABSTRACT_QUERY_OPTIONS: AbstractQueryOptions = {
},
}
-export const useAbstractQuery = (
- defaultQuery: T,
- parseQuery: ParseQuery,
+export const useAbstractQuery = <
+ TQuery extends AbstractQuery,
+ TDefaultQuery extends Partial,
+>(
router: Router,
+ parseQuery: ParseQuery,
+ defaultQuery: TDefaultQuery,
options?: Partial,
) => {
const mergedOptions = useMemo(
@@ -88,12 +91,12 @@ export const useAbstractQuery = (
)
const { query, setQuery, resetQuery } = useQuery(
- convertedDefaultQuery,
router,
+ convertedDefaultQuery,
)
- const parsedQuery: T = useMemo(() => {
- let parsed: Partial
+ const parsedQuery = useMemo(() => {
+ let parsed: Partial
try {
parsed = parseQuery(query)
} catch (e) {
@@ -106,7 +109,7 @@ export const useAbstractQuery = (
}, [defaultQuery, parseQuery, query])
const setAbstractQuery = useCallback(
- (query: Partial) => {
+ (query: Partial) => {
setQuery(mergedOptions.convertToQuery(query))
},
[setQuery, mergedOptions],
diff --git a/src/routers/__test__/inMemory.test.ts b/src/routers/__test__/inMemory.test.ts
index eae893e..df0ccb6 100644
--- a/src/routers/__test__/inMemory.test.ts
+++ b/src/routers/__test__/inMemory.test.ts
@@ -1,6 +1,5 @@
import { act, renderHook } from '@testing-library/react'
import { z } from 'zod'
-import { NonNullableRecord } from '../../utils'
import { useQuery, useQueryAndPagination } from '../../zod/routers/inMemory'
import { usePagination } from '../inMemory'
@@ -10,8 +9,8 @@ describe('InMemory', () => {
})
const useInMemoryQueryWithSearch = (
- defaultQuery: NonNullableRecord>,
- ) => useQuery(defaultQuery, searchSchema)
+ defaultQuery: Partial> = {},
+ ) => useQuery(searchSchema, defaultQuery)
test('should set default page and size', () => {
const page = 0
@@ -53,7 +52,7 @@ describe('InMemory', () => {
const page = 2
const { result } = renderHook(() =>
- useQueryAndPagination({ search: '' }, searchSchema),
+ useQueryAndPagination(searchSchema, { search: '' }),
)
act(() => {
@@ -90,7 +89,7 @@ describe('InMemory', () => {
const page = 2
const { result } = renderHook(() =>
- useQueryAndPagination({ search: defaultSearch }, searchSchema),
+ useQueryAndPagination(searchSchema, { search: defaultSearch }),
)
act(() => {
@@ -125,7 +124,7 @@ describe('InMemory', () => {
const page = 2
const { result } = renderHook(() =>
- useQueryAndPagination({ search: defaultSearch }, searchSchema),
+ useQueryAndPagination(searchSchema, { search: defaultSearch }),
)
act(() => {
@@ -159,7 +158,7 @@ describe('InMemory', () => {
})
const { result } = renderHook(() =>
- useQuery({ search: '', department: '' }, schema),
+ useQuery(schema, { search: '', department: '' }),
)
act(() => {
@@ -178,7 +177,7 @@ describe('InMemory', () => {
const search = ''
const { result } = renderHook(() =>
- useQuery({ search: 'Default search' }, searchSchema),
+ useQuery(searchSchema, { search: 'Default search' }),
)
act(() => {
diff --git a/src/routers/__test__/nextRouter.test.ts b/src/routers/__test__/nextRouter.test.ts
index 6fb5b19..2a435e4 100644
--- a/src/routers/__test__/nextRouter.test.ts
+++ b/src/routers/__test__/nextRouter.test.ts
@@ -2,7 +2,6 @@ import { act, renderHook } from '@testing-library/react'
import router from 'next/router'
import { z } from 'zod'
import { vi } from 'vitest'
-import { NonNullableRecord } from '../../utils'
import { useQuery, useQueryAndPagination } from '../../zod/routers/nextRouter'
import { usePagination } from '../nextRouter'
@@ -19,8 +18,8 @@ describe('NextRouter', () => {
})
const useNextRouterQueryWithSearch = (
- defaultQuery: NonNullableRecord>,
- ) => useQuery(defaultQuery, searchSchema)
+ defaultQuery: Partial> = {},
+ ) => useQuery(searchSchema, defaultQuery)
test('should set default page and size', () => {
const page = 0
@@ -64,7 +63,7 @@ describe('NextRouter', () => {
const page = 2
const { result } = renderHook(() =>
- useQueryAndPagination({ search: '' }, searchSchema),
+ useQueryAndPagination(searchSchema, { search: '' }),
)
act(() => {
@@ -104,7 +103,7 @@ describe('NextRouter', () => {
const page = 2
const { result } = renderHook(() =>
- useQueryAndPagination({ search: defaultSearch }, searchSchema),
+ useQueryAndPagination(searchSchema, { search: defaultSearch }),
)
act(() => {
@@ -146,7 +145,7 @@ describe('NextRouter', () => {
const page = 2
const { result } = renderHook(() =>
- useQueryAndPagination({ search: defaultSearch }, searchSchema),
+ useQueryAndPagination(searchSchema, { search: defaultSearch }),
)
act(() => {
@@ -194,17 +193,14 @@ describe('NextRouter', () => {
})
const { result } = renderHook(() =>
- useQuery(
- {
- search: '',
- department: '',
- age: defaultAge,
- birthDate: defaultBirthDate,
- netWorth: defaultNetWorth,
- darkMode: defaultDarkMode,
- },
- schema,
- ),
+ useQuery(schema, {
+ search: '',
+ department: '',
+ age: defaultAge,
+ birthDate: defaultBirthDate,
+ netWorth: defaultNetWorth,
+ darkMode: defaultDarkMode,
+ }),
)
act(() => {
@@ -226,7 +222,7 @@ describe('NextRouter', () => {
const greeting = 'hello'
router.query = { greeting }
- const { result } = renderHook(() => useQuery({ search: '' }, searchSchema))
+ const { result } = renderHook(() => useQuery(searchSchema, { search: '' }))
act(() => {
result.current.setQuery({ search: 'Max' })
@@ -242,7 +238,7 @@ describe('NextRouter', () => {
router.query = { search: 'Max' }
const { result } = renderHook(() =>
- useQuery({ search: defaultSearch }, searchSchema),
+ useQuery(searchSchema, { search: defaultSearch }),
)
act(() => {
@@ -257,7 +253,7 @@ describe('NextRouter', () => {
const search = ''
const { result } = renderHook(() =>
- useQuery({ search: 'Default search' }, searchSchema),
+ useQuery(searchSchema, { search: 'Default search' }),
)
act(() => {
@@ -267,4 +263,25 @@ describe('NextRouter', () => {
expect(result.current.query.search).toBe(search)
expect(router.query.search).toBe(search)
})
+
+ test('passing no default query should return undefined for a property that is not in the query', () => {
+ const { result } = renderHook(() =>
+ useQuery(z.object({ department: z.string() })),
+ )
+
+ expect(result.current.query.department).toBeUndefined()
+ })
+
+ test('the query should be a merge of the default query and the current query', () => {
+ const defaultRole = 'ADMIN'
+
+ const { result } = renderHook(() =>
+ useQuery(z.object({ department: z.string(), role: z.string() }), {
+ role: defaultRole,
+ }),
+ )
+
+ expect(result.current.query.department).toBeUndefined()
+ expect(result.current.query.role).toBe(defaultRole)
+ })
})
diff --git a/src/routers/__test__/reactRouter.test.tsx b/src/routers/__test__/reactRouter.test.tsx
index 803ff28..a10f039 100644
--- a/src/routers/__test__/reactRouter.test.tsx
+++ b/src/routers/__test__/reactRouter.test.tsx
@@ -1,7 +1,6 @@
import { act, renderHook } from '@testing-library/react'
import { z } from 'zod'
import { BrowserRouter } from 'react-router-dom'
-import { NonNullableRecord } from '../../utils'
import { useQuery, useQueryAndPagination } from '../../zod/routers/reactRouter'
import { usePagination } from '../reactRouter'
@@ -24,8 +23,8 @@ describe('ReactRouter', () => {
})
const useReactRouterQueryWithSearch = (
- defaultQuery: NonNullableRecord>,
- ) => useQuery(defaultQuery, searchSchema)
+ defaultQuery: Partial> = {},
+ ) => useQuery(searchSchema, defaultQuery)
test('should set default page and size', () => {
const page = 0
@@ -71,7 +70,7 @@ describe('ReactRouter', () => {
const page = 2
const { result } = renderHookWithContext(() =>
- useQueryAndPagination({ search: '' }, searchSchema),
+ useQueryAndPagination(searchSchema, { search: '' }),
)
act(() => {
@@ -111,7 +110,7 @@ describe('ReactRouter', () => {
const page = 2
const { result } = renderHookWithContext(() =>
- useQueryAndPagination({ search: defaultSearch }, searchSchema),
+ useQueryAndPagination(searchSchema, { search: defaultSearch }),
)
act(() => {
@@ -149,7 +148,7 @@ describe('ReactRouter', () => {
const page = 2
const { result } = renderHookWithContext(() =>
- useQueryAndPagination({ search: defaultSearch }, searchSchema),
+ useQueryAndPagination(searchSchema, { search: defaultSearch }),
)
act(() => {
@@ -186,7 +185,7 @@ describe('ReactRouter', () => {
})
const { result } = renderHookWithContext(() =>
- useQuery({ search: '', department: '' }, schema),
+ useQuery(schema, { search: '', department: '' }),
)
act(() => {
@@ -207,7 +206,7 @@ describe('ReactRouter', () => {
window.history.pushState({}, '', `/?greeting=${greeting}`)
const { result } = renderHookWithContext(() =>
- useQuery({ search: '' }, searchSchema),
+ useQuery(searchSchema, { search: '' }),
)
act(() => {
@@ -226,7 +225,7 @@ describe('ReactRouter', () => {
window.history.pushState({}, '', `/?search=Max`)
const { result } = renderHookWithContext(() =>
- useQuery({ search: defaultSearch }, searchSchema),
+ useQuery(searchSchema, { search: defaultSearch }),
)
act(() => {
@@ -241,7 +240,7 @@ describe('ReactRouter', () => {
const search = ''
const { result } = renderHookWithContext(() =>
- useQuery({ search: 'Default search' }, searchSchema),
+ useQuery(searchSchema, { search: 'Default search' }),
)
act(() => {
diff --git a/src/routers/inMemory.ts b/src/routers/inMemory.ts
index c8e8f54..9384900 100644
--- a/src/routers/inMemory.ts
+++ b/src/routers/inMemory.ts
@@ -29,36 +29,36 @@ const useInMemoryRouter = (): Router => {
}
}
-export const useQuery = (
- defaultQuery: T,
- parseQuery: ParseQuery,
+export const useQuery = (
+ parseQuery: ParseQuery,
+ defaultQuery: Partial = {},
options?: Partial,
) => {
const router = useInMemoryRouter()
- return useAbstractQuery(defaultQuery, parseQuery, router, options)
+ return useAbstractQuery(router, parseQuery, defaultQuery, options)
}
-export const useQueryAndPagination = (
- defaultQuery: T,
- parseQuery: ParseQuery,
- defaultPagination?: PaginationQuery,
+export const useQueryAndPagination = (
+ parseQuery: ParseQuery,
+ defaultQuery: Partial = {},
+ defaultPagination?: Partial,
options?: Partial,
) => {
const router = useInMemoryRouter()
return useAbstractQueryAndPagination(
- defaultQuery,
- parseQuery,
router,
+ parseQuery,
+ defaultQuery,
defaultPagination,
options,
)
}
export const usePagination = (
- defaultPagination?: PaginationQuery,
+ defaultPagination?: Partial,
options?: Partial,
) => {
const { page, size, setPage, setSize, setPagination, resetPagination } =
- useQueryAndPagination({}, () => ({}), defaultPagination, options)
+ useQueryAndPagination(() => ({}), {}, defaultPagination, options)
return { page, size, setPage, setSize, setPagination, resetPagination }
}
diff --git a/src/routers/nextRouter.ts b/src/routers/nextRouter.ts
index 85b3dcd..23143e5 100644
--- a/src/routers/nextRouter.ts
+++ b/src/routers/nextRouter.ts
@@ -1,5 +1,5 @@
import { useRouter } from 'next/router'
-import { useMemo } from 'react'
+import { useEffect, useMemo, useState } from 'react'
import {
Query,
ParseQuery,
@@ -22,16 +22,25 @@ const useNextRouter = (
options: undefined | Partial,
): Router => {
const nextRouter = useRouter()
+ const [nextRouterQuery, setNextRouterQuery] = useState<
+ typeof nextRouter.query
+ >({})
const mergedOptions = useMemo(
() => ({ ...DEFAULT_NEXT_ROUTER_OPTIONS, ...options }),
[options],
)
+ useEffect(() => {
+ if (nextRouter.isReady) {
+ setNextRouterQuery(nextRouter.query)
+ }
+ }, [nextRouter.isReady, nextRouter.query])
+
return {
getQuery: (defaultQuery) => {
const query: Query = {}
- for (const [key, value] of Object.entries(nextRouter.query)) {
+ for (const [key, value] of Object.entries(nextRouterQuery)) {
if (value !== undefined) {
query[key] = value
}
@@ -39,7 +48,7 @@ const useNextRouter = (
return { ...defaultQuery, ...query }
},
setQuery: (query, defaultQuery) => {
- const newQuery = { ...nextRouter.query, ...query }
+ const newQuery = { ...nextRouterQuery, ...query }
const newQueryWithoutDefaults = Object.fromEntries(
Object.entries(newQuery).filter(
([key, value]) => value !== defaultQuery[key],
@@ -58,36 +67,36 @@ const useNextRouter = (
}
}
-export const useQuery = (
- defaultQuery: T,
- parseQuery: ParseQuery,
+export const useQuery = (
+ parseQuery: ParseQuery,
+ defaultQuery: Partial = {},
options?: Partial,
) => {
const router = useNextRouter(options)
- return useAbstractQuery(defaultQuery, parseQuery, router, options)
+ return useAbstractQuery(router, parseQuery, defaultQuery, options)
}
-export const useQueryAndPagination = (
- defaultQuery: T,
- parseQuery: ParseQuery,
- defaultPagination?: PaginationQuery,
+export const useQueryAndPagination = (
+ parseQuery: ParseQuery,
+ defaultQuery: Partial = {},
+ defaultPagination?: Partial,
options?: Partial,
) => {
const router = useNextRouter(options)
return useAbstractQueryAndPagination(
- defaultQuery,
- parseQuery,
router,
+ parseQuery,
+ defaultQuery,
defaultPagination,
options,
)
}
export const usePagination = (
- defaultPagination?: PaginationQuery,
+ defaultPagination?: Partial,
options?: Partial,
) => {
const { page, size, setPage, setSize, setPagination, resetPagination } =
- useQueryAndPagination({}, () => ({}), defaultPagination, options)
+ useQueryAndPagination(() => ({}), {}, defaultPagination, options)
return { page, size, setPage, setSize, setPagination, resetPagination }
}
diff --git a/src/routers/reactRouter.ts b/src/routers/reactRouter.ts
index 5aaeb0f..17b8b25 100644
--- a/src/routers/reactRouter.ts
+++ b/src/routers/reactRouter.ts
@@ -67,36 +67,36 @@ const useReactRouter = (
}
}
-export const useQuery = (
- defaultQuery: T,
- parseQuery: ParseQuery,
+export const useQuery = (
+ parseQuery: ParseQuery,
+ defaultQuery: Partial = {},
options?: Partial,
) => {
const router = useReactRouter(options)
- return useAbstractQuery(defaultQuery, parseQuery, router, options)
+ return useAbstractQuery(router, parseQuery, defaultQuery, options)
}
-export const useQueryAndPagination = (
- defaultQuery: T,
- parseQuery: ParseQuery,
- defaultPagination?: PaginationQuery,
+export const useQueryAndPagination = (
+ parseQuery: ParseQuery,
+ defaultQuery: Partial = {},
+ defaultPagination?: Partial,
options?: Partial,
) => {
const router = useReactRouter(options)
return useAbstractQueryAndPagination(
- defaultQuery,
- parseQuery,
router,
+ parseQuery,
+ defaultQuery,
defaultPagination,
options,
)
}
export const usePagination = (
- defaultPagination?: PaginationQuery,
+ defaultPagination?: Partial,
options?: Partial,
) => {
const { page, size, setPage, setSize, setPagination, resetPagination } =
- useQueryAndPagination({}, () => ({}), defaultPagination, options)
+ useQueryAndPagination(() => ({}), {}, defaultPagination, options)
return { page, size, setPage, setSize, setPagination, resetPagination }
}
diff --git a/src/zod/routers/inMemory.ts b/src/zod/routers/inMemory.ts
index 289b34f..117cb5c 100644
--- a/src/zod/routers/inMemory.ts
+++ b/src/zod/routers/inMemory.ts
@@ -4,24 +4,29 @@ import {
useQuery as useQueryVanilla,
useQueryAndPagination as useQueryAndPaginationVanilla,
} from '../../routers/inMemory'
-import { NonNullableRecord } from '../../utils'
import { zodParser } from '../util'
-export const useQuery = (
- defaultQuery: NonNullableRecord>,
+export const useQuery = <
+ TSchema extends z.ZodTypeAny,
+ TQuery extends z.output,
+>(
schemaQuery: TSchema,
+ defaultQuery: Partial = {},
options?: Partial,
-) => useQueryVanilla(defaultQuery, zodParser(schemaQuery), options)
+) => useQueryVanilla(zodParser(schemaQuery), defaultQuery, options)
-export const useQueryAndPagination = (
- defaultQuery: NonNullableRecord>,
+export const useQueryAndPagination = <
+ TSchema extends z.ZodTypeAny,
+ TQuery extends z.output,
+>(
schemaQuery: TSchema,
- defaultPagination?: PaginationQuery,
+ defaultQuery: Partial = {},
+ defaultPagination?: Partial,
options?: Partial,
) =>
useQueryAndPaginationVanilla(
- defaultQuery,
zodParser(schemaQuery),
+ defaultQuery,
defaultPagination,
options,
)
diff --git a/src/zod/routers/nextRouter.ts b/src/zod/routers/nextRouter.ts
index 3f26e28..46f3b46 100644
--- a/src/zod/routers/nextRouter.ts
+++ b/src/zod/routers/nextRouter.ts
@@ -6,23 +6,28 @@ import {
} from '../../routers/nextRouter'
import { zodParser } from '../util'
import { RouterWithHistoryOptions } from '../../routers/shared'
-import { NonNullableRecord } from '../../utils'
-export const useQuery = (
- defaultQuery: NonNullableRecord>,
+export const useQuery = <
+ TSchema extends z.ZodTypeAny,
+ TQuery extends z.output,
+>(
schemaQuery: TSchema,
+ defaultQuery: Partial = {},
options?: Partial,
-) => useQueryVanilla(defaultQuery, zodParser(schemaQuery), options)
+) => useQueryVanilla(zodParser(schemaQuery), defaultQuery, options)
-export const useQueryAndPagination = (
- defaultQuery: NonNullableRecord>,
+export const useQueryAndPagination = <
+ TSchema extends z.ZodTypeAny,
+ TQuery extends z.output,
+>(
schemaQuery: TSchema,
- defaultPagination?: PaginationQuery,
+ defaultQuery: Partial = {},
+ defaultPagination?: Partial,
options?: Partial,
) =>
useQueryAndPaginationVanilla(
- defaultQuery,
zodParser(schemaQuery),
+ defaultQuery,
defaultPagination,
options,
)
diff --git a/src/zod/routers/reactRouter.ts b/src/zod/routers/reactRouter.ts
index 0bbabe2..634b947 100644
--- a/src/zod/routers/reactRouter.ts
+++ b/src/zod/routers/reactRouter.ts
@@ -6,23 +6,28 @@ import {
} from '../../routers/reactRouter'
import { zodParser } from '../util'
import { RouterWithHistoryOptions } from '../../routers/shared'
-import { NonNullableRecord } from '../../utils'
-export const useQuery = (
- defaultQuery: NonNullableRecord>,
+export const useQuery = <
+ TSchema extends z.ZodTypeAny,
+ TQuery extends z.output,
+>(
schemaQuery: TSchema,
+ defaultQuery: Partial = {},
options?: Partial,
-) => useQueryVanilla(defaultQuery, zodParser(schemaQuery), options)
+) => useQueryVanilla(zodParser(schemaQuery), defaultQuery, options)
-export const useQueryAndPagination = (
- defaultQuery: NonNullableRecord>,
+export const useQueryAndPagination = <
+ TSchema extends z.ZodTypeAny,
+ TQuery extends z.output,
+>(
schemaQuery: TSchema,
- defaultPagination?: PaginationQuery,
+ defaultQuery: Partial = {},
+ defaultPagination?: Partial,
options?: Partial,
) =>
useQueryAndPaginationVanilla(
- defaultQuery,
zodParser(schemaQuery),
+ defaultQuery,
defaultPagination,
options,
)