diff --git a/README.md b/README.md index 8f5efd1..3d94fa5 100644 --- a/README.md +++ b/README.md @@ -136,13 +136,11 @@ Query parameter names can be customized. ```tsx const stateAndOnChanges = useTableSearchParams(router, { - globalFilter: { + paramNames: { // Customize query parameter name by passing a string - paramName: "userTable-globalFilter", - }, - sorting: { + globalFilter: "userTable-globalFilter", // Add prefix by passing a function - paramName: (defaultParamName) => `userTable-${defaultParamName}`, + sorting: (defaultParamName) => `userTable-${defaultParamName}`, }, }); ``` @@ -158,9 +156,9 @@ Default values can be customized. ```tsx const stateAndOnChanges = useTableSearchParams(router, { - sorting: { + defaultValues: { // Sort by name in descending order when query parameter is not present - defaultValue: [{ id: "name", desc: true }], + sorting: [{ id: "name", desc: true }], }, }); ``` @@ -189,41 +187,57 @@ Encoder and decoder can be customized. ```tsx const stateAndOnChanges = useTableSearchParams(router, { // Use JSON.stringify/JSON.parse for encoding/decoding - globalFilter: { + encoders: { // foo -> { "globalFilter": "foo" } - encoder: (globalFilter) => ({ + globalFilter: (globalFilter) => ({ globalFilter: JSON.stringify(globalFilter), }), + }, + decoders: { // { "globalFilter": "foo" } -> foo - decoder: (query) => + globalFilter: (query) => query["globalFilter"] ? JSON.parse(query["globalFilter"] as string) : (query["globalFilter"] ?? ""), }, +}); + +// ... + +const stateAndOnChanges = useTableSearchParams(router, { // Encoders/decoders with different query parameter names can also be used. - sorting: { + encoders: { // [{ id: "name", desc: true }] -> { "userTable-sorting": "[{ \"id\": \"name\", \"desc\": true }]" } - encoder: (sorting) => ({ + sorting: (sorting) => ({ "userTable-sorting": JSON.stringify(sorting), }), + }, + decoders: { // { "userTable-sorting": "[{ \"id\": \"name\", \"desc\": true }]" } -> [{ id: "name", desc: true }] - decoder: (query) => + sorting: (query) => query["userTable-sorting"] ? JSON.parse(query["userTable-sorting"] as string) : query["userTable-sorting"], }, +}); + +// ... + +const stateAndOnChanges = useTableSearchParams(router, { // Encoders/decoders with different numbers of query parameters can also be used. - columnFilters: { + encoders: { // [{ id: "name", value: "foo" }] -> { "columnFilters.name": "\"foo\"" } - encoder: (columnFilters) => + columnFilters: (columnFilters) => Object.fromEntries( columnFilters.map(({ id, value }) => [ `columnFilters.${id}`, JSON.stringify(value), ]), ), + }, + decoders: { // { "columnFilters.name": "\"foo\"" } -> [{ id: "name", value: "foo" }] - decoder: (query) => + columnFilters: (query) => Object.entries(query) .filter(([key]) => key.startsWith("columnFilters.")) .map(([key, value]) => ({ diff --git a/examples/next-app-router/src/app/custom-default-value/table.tsx b/examples/next-app-router/src/app/custom-default-value/table.tsx index 9293e99..73c1da6 100644 --- a/examples/next-app-router/src/app/custom-default-value/table.tsx +++ b/examples/next-app-router/src/app/custom-default-value/table.tsx @@ -25,10 +25,12 @@ export const Table = () => { const stateAndOnChanges = useTableSearchParams( { push, query, pathname }, { - globalFilter: { defaultValue: "a" }, - sorting: { defaultValue: [{ id: "name", desc: true }] }, - pagination: { defaultValue: { pageIndex: 2, pageSize: 20 } }, - columnFilters: { defaultValue: [{ id: "name", value: "b" }] }, + defaultValues: { + globalFilter: "a", + sorting: [{ id: "name", desc: true }], + pagination: { pageIndex: 2, pageSize: 20 }, + columnFilters: [{ id: "name", value: "b" }], + }, }, ); diff --git a/examples/next-app-router/src/app/custom-encoder-decoder/table.tsx b/examples/next-app-router/src/app/custom-encoder-decoder/table.tsx index bc152f6..b5ad99a 100644 --- a/examples/next-app-router/src/app/custom-encoder-decoder/table.tsx +++ b/examples/next-app-router/src/app/custom-encoder-decoder/table.tsx @@ -25,52 +25,48 @@ export const Table = () => { const stateAndOnChanges = useTableSearchParams( { push, query, pathname }, { - globalFilter: { - encoder: (globalFilter) => ({ + encoders: { + globalFilter: (globalFilter) => ({ globalFilter: JSON.stringify(globalFilter), }), - decoder: (query) => - query["globalFilter"] - ? JSON.parse(query["globalFilter"] as string) - : query["globalFilter"] ?? "", - }, - sorting: { - encoder: (sorting) => + sorting: (sorting) => Object.fromEntries( sorting.map(({ id, desc }) => [ `sorting.${id}`, desc ? "desc" : "asc", ]), ), - decoder: (query) => + pagination: (pagination) => ({ + pagination: JSON.stringify(pagination), + }), + columnFilters: (columnFilters) => + Object.fromEntries( + columnFilters.map(({ id, value }) => [ + `columnFilters.${id}`, + JSON.stringify(value), + ]), + ), + }, + decoders: { + globalFilter: (query) => + query["globalFilter"] + ? JSON.parse(query["globalFilter"] as string) + : query["globalFilter"] ?? "", + sorting: (query) => Object.entries(query) .filter(([key]) => key.startsWith("sorting.")) .map(([key, desc]) => ({ id: key.replace("sorting.", ""), desc: desc === "desc", })), - }, - pagination: { - encoder: (pagination) => ({ - pagination: JSON.stringify(pagination), - }), - decoder: (query) => + pagination: (query) => query["pagination"] ? JSON.parse(query["pagination"] as string) : { pageIndex: 0, pageSize: 10, }, - }, - columnFilters: { - encoder: (columnFilters) => - Object.fromEntries( - columnFilters.map(({ id, value }) => [ - `columnFilters.${id}`, - JSON.stringify(value), - ]), - ), - decoder: (query) => + columnFilters: (query) => Object.entries(query) .filter(([key]) => key.startsWith("columnFilters.")) .map(([key, value]) => ({ diff --git a/examples/next-app-router/src/app/custom-param-name/table.tsx b/examples/next-app-router/src/app/custom-param-name/table.tsx index 1cbc8ce..d29de44 100644 --- a/examples/next-app-router/src/app/custom-param-name/table.tsx +++ b/examples/next-app-router/src/app/custom-param-name/table.tsx @@ -25,20 +25,14 @@ export const Table = () => { const stateAndOnChanges = useTableSearchParams( { push, query, pathname }, { - globalFilter: { - paramName: "userTable-globalFilter", - }, - sorting: { - paramName: (defaultParamName) => `userTable-${defaultParamName}`, - }, - pagination: { - paramName: { + paramNames: { + globalFilter: "userTable-globalFilter", + sorting: (defaultParamName) => `userTable-${defaultParamName}`, + pagination: { pageIndex: "userTable-pageIndex", pageSize: "userTable-pageSize", }, - }, - columnFilters: { - paramName: (defaultParamName) => `userTable-${defaultParamName}`, + columnFilters: (defaultParamName) => `userTable-${defaultParamName}`, }, }, ); diff --git a/examples/next-pages-router/src/pages/custom-default-value.tsx b/examples/next-pages-router/src/pages/custom-default-value.tsx index 294d82c..57085c3 100644 --- a/examples/next-pages-router/src/pages/custom-default-value.tsx +++ b/examples/next-pages-router/src/pages/custom-default-value.tsx @@ -18,10 +18,12 @@ export default function CustomParamNames() { const router = useRouter(); const stateAndOnChanges = useTableSearchParams(router, { - globalFilter: { defaultValue: "a" }, - sorting: { defaultValue: [{ id: "name", desc: true }] }, - pagination: { defaultValue: { pageIndex: 2, pageSize: 20 } }, - columnFilters: { defaultValue: [{ id: "name", value: "b" }] }, + defaultValues: { + globalFilter: "a", + sorting: [{ id: "name", desc: true }], + pagination: { pageIndex: 2, pageSize: 20 }, + columnFilters: [{ id: "name", value: "b" }], + }, }); const table = useReactTable({ diff --git a/examples/next-pages-router/src/pages/custom-encoder-decoder.tsx b/examples/next-pages-router/src/pages/custom-encoder-decoder.tsx index 9f7906a..5babe39 100644 --- a/examples/next-pages-router/src/pages/custom-encoder-decoder.tsx +++ b/examples/next-pages-router/src/pages/custom-encoder-decoder.tsx @@ -18,52 +18,48 @@ export default function CustomEncoderDecoder() { const router = useRouter(); const stateAndOnChanges = useTableSearchParams(router, { - globalFilter: { - encoder: (globalFilter) => ({ + encoders: { + globalFilter: (globalFilter) => ({ globalFilter: JSON.stringify(globalFilter), }), - decoder: (query) => - query["globalFilter"] - ? JSON.parse(query["globalFilter"] as string) - : query["globalFilter"] ?? "", - }, - sorting: { - encoder: (sorting) => + sorting: (sorting) => Object.fromEntries( sorting.map(({ id, desc }) => [ `sorting.${id}`, desc ? "desc" : "asc", ]), ), - decoder: (query) => + pagination: (pagination) => ({ + pagination: JSON.stringify(pagination), + }), + columnFilters: (columnFilters) => + Object.fromEntries( + columnFilters.map(({ id, value }) => [ + `columnFilters.${id}`, + JSON.stringify(value), + ]), + ), + }, + decoders: { + globalFilter: (query) => + query["globalFilter"] + ? JSON.parse(query["globalFilter"] as string) + : query["globalFilter"] ?? "", + sorting: (query) => Object.entries(query) .filter(([key]) => key.startsWith("sorting.")) .map(([key, desc]) => ({ id: key.replace("sorting.", ""), desc: desc === "desc", })), - }, - pagination: { - encoder: (pagination) => ({ - pagination: JSON.stringify(pagination), - }), - decoder: (query) => + pagination: (query) => query["pagination"] ? JSON.parse(query["pagination"] as string) : { pageIndex: 0, pageSize: 10, }, - }, - columnFilters: { - encoder: (columnFilters) => - Object.fromEntries( - columnFilters.map(({ id, value }) => [ - `columnFilters.${id}`, - JSON.stringify(value), - ]), - ), - decoder: (query) => + columnFilters: (query) => Object.entries(query) .filter(([key]) => key.startsWith("columnFilters.")) .map(([key, value]) => ({ diff --git a/examples/next-pages-router/src/pages/custom-param-name.tsx b/examples/next-pages-router/src/pages/custom-param-name.tsx index be79f65..f7f08f7 100644 --- a/examples/next-pages-router/src/pages/custom-param-name.tsx +++ b/examples/next-pages-router/src/pages/custom-param-name.tsx @@ -18,20 +18,14 @@ export default function CustomParamNames() { const router = useRouter(); const stateAndOnChanges = useTableSearchParams(router, { - globalFilter: { - paramName: "userTable-globalFilter", - }, - sorting: { - paramName: (defaultParamName) => `userTable-${defaultParamName}`, - }, - pagination: { - paramName: { + paramNames: { + globalFilter: "userTable-globalFilter", + sorting: (defaultParamName) => `userTable-${defaultParamName}`, + pagination: { pageIndex: "userTable-pageIndex", pageSize: "userTable-pageSize", }, - }, - columnFilters: { - paramName: (defaultParamName) => `userTable-${defaultParamName}`, + columnFilters: (defaultParamName) => `userTable-${defaultParamName}`, }, }); diff --git a/examples/tanstack-router/src/routes/custom-default-value.tsx b/examples/tanstack-router/src/routes/custom-default-value.tsx index b51236e..39b06ec 100644 --- a/examples/tanstack-router/src/routes/custom-default-value.tsx +++ b/examples/tanstack-router/src/routes/custom-default-value.tsx @@ -33,10 +33,12 @@ function Page() { pathname: Route.path, }, { - globalFilter: { defaultValue: "a" }, - sorting: { defaultValue: [{ id: "name", desc: true }] }, - pagination: { defaultValue: { pageIndex: 2, pageSize: 20 } }, - columnFilters: { defaultValue: [{ id: "name", value: "b" }] }, + defaultValues: { + globalFilter: "a", + sorting: [{ id: "name", desc: true }], + pagination: { pageIndex: 2, pageSize: 20 }, + columnFilters: [{ id: "name", value: "b" }], + }, }, ); diff --git a/examples/tanstack-router/src/routes/custom-encoder-decoder.tsx b/examples/tanstack-router/src/routes/custom-encoder-decoder.tsx index fac7f10..fb71b23 100644 --- a/examples/tanstack-router/src/routes/custom-encoder-decoder.tsx +++ b/examples/tanstack-router/src/routes/custom-encoder-decoder.tsx @@ -33,52 +33,48 @@ function Page() { pathname: Route.path, }, { - globalFilter: { - encoder: (globalFilter) => ({ + encoders: { + globalFilter: (globalFilter) => ({ globalFilter: JSON.stringify(globalFilter), }), - decoder: (query) => - query["globalFilter"] - ? JSON.parse(query["globalFilter"] as string) - : query["globalFilter"] ?? "", - }, - sorting: { - encoder: (sorting) => + sorting: (sorting) => Object.fromEntries( sorting.map(({ id, desc }) => [ `sorting.${id}`, desc ? "desc" : "asc", ]), ), - decoder: (query) => + pagination: (pagination) => ({ + pagination: JSON.stringify(pagination), + }), + columnFilters: (columnFilters) => + Object.fromEntries( + columnFilters.map(({ id, value }) => [ + `columnFilters.${id}`, + JSON.stringify(value), + ]), + ), + }, + decoders: { + globalFilter: (query) => + query["globalFilter"] + ? JSON.parse(query["globalFilter"] as string) + : query["globalFilter"] ?? "", + sorting: (query) => Object.entries(query) .filter(([key]) => key.startsWith("sorting.")) .map(([key, desc]) => ({ id: key.replace("sorting.", ""), desc: desc === "desc", })), - }, - pagination: { - encoder: (pagination) => ({ - pagination: JSON.stringify(pagination), - }), - decoder: (query) => + pagination: (query) => query["pagination"] ? JSON.parse(query["pagination"] as string) : { pageIndex: 0, pageSize: 10, }, - }, - columnFilters: { - encoder: (columnFilters) => - Object.fromEntries( - columnFilters.map(({ id, value }) => [ - `columnFilters.${id}`, - JSON.stringify(value), - ]), - ), - decoder: (query) => + columnFilters: (query) => Object.entries(query) .filter(([key]) => key.startsWith("columnFilters.")) .map(([key, value]) => ({ diff --git a/examples/tanstack-router/src/routes/custom-param-name.tsx b/examples/tanstack-router/src/routes/custom-param-name.tsx index a30f1f9..6f48d1b 100644 --- a/examples/tanstack-router/src/routes/custom-param-name.tsx +++ b/examples/tanstack-router/src/routes/custom-param-name.tsx @@ -33,20 +33,14 @@ function Page() { pathname: Route.path, }, { - globalFilter: { - paramName: "userTable-globalFilter", - }, - sorting: { - paramName: (defaultParamName) => `userTable-${defaultParamName}`, - }, - pagination: { - paramName: { + paramNames: { + globalFilter: "userTable-globalFilter", + sorting: (defaultParamName) => `userTable-${defaultParamName}`, + pagination: { pageIndex: "userTable-pageIndex", pageSize: "userTable-pageSize", }, - }, - columnFilters: { - paramName: (defaultParamName) => `userTable-${defaultParamName}`, + columnFilters: (defaultParamName) => `userTable-${defaultParamName}`, }, }, ); diff --git a/packages/tanstack-table-search-params/README.md b/packages/tanstack-table-search-params/README.md index 8f5efd1..3d94fa5 100644 --- a/packages/tanstack-table-search-params/README.md +++ b/packages/tanstack-table-search-params/README.md @@ -136,13 +136,11 @@ Query parameter names can be customized. ```tsx const stateAndOnChanges = useTableSearchParams(router, { - globalFilter: { + paramNames: { // Customize query parameter name by passing a string - paramName: "userTable-globalFilter", - }, - sorting: { + globalFilter: "userTable-globalFilter", // Add prefix by passing a function - paramName: (defaultParamName) => `userTable-${defaultParamName}`, + sorting: (defaultParamName) => `userTable-${defaultParamName}`, }, }); ``` @@ -158,9 +156,9 @@ Default values can be customized. ```tsx const stateAndOnChanges = useTableSearchParams(router, { - sorting: { + defaultValues: { // Sort by name in descending order when query parameter is not present - defaultValue: [{ id: "name", desc: true }], + sorting: [{ id: "name", desc: true }], }, }); ``` @@ -189,41 +187,57 @@ Encoder and decoder can be customized. ```tsx const stateAndOnChanges = useTableSearchParams(router, { // Use JSON.stringify/JSON.parse for encoding/decoding - globalFilter: { + encoders: { // foo -> { "globalFilter": "foo" } - encoder: (globalFilter) => ({ + globalFilter: (globalFilter) => ({ globalFilter: JSON.stringify(globalFilter), }), + }, + decoders: { // { "globalFilter": "foo" } -> foo - decoder: (query) => + globalFilter: (query) => query["globalFilter"] ? JSON.parse(query["globalFilter"] as string) : (query["globalFilter"] ?? ""), }, +}); + +// ... + +const stateAndOnChanges = useTableSearchParams(router, { // Encoders/decoders with different query parameter names can also be used. - sorting: { + encoders: { // [{ id: "name", desc: true }] -> { "userTable-sorting": "[{ \"id\": \"name\", \"desc\": true }]" } - encoder: (sorting) => ({ + sorting: (sorting) => ({ "userTable-sorting": JSON.stringify(sorting), }), + }, + decoders: { // { "userTable-sorting": "[{ \"id\": \"name\", \"desc\": true }]" } -> [{ id: "name", desc: true }] - decoder: (query) => + sorting: (query) => query["userTable-sorting"] ? JSON.parse(query["userTable-sorting"] as string) : query["userTable-sorting"], }, +}); + +// ... + +const stateAndOnChanges = useTableSearchParams(router, { // Encoders/decoders with different numbers of query parameters can also be used. - columnFilters: { + encoders: { // [{ id: "name", value: "foo" }] -> { "columnFilters.name": "\"foo\"" } - encoder: (columnFilters) => + columnFilters: (columnFilters) => Object.fromEntries( columnFilters.map(({ id, value }) => [ `columnFilters.${id}`, JSON.stringify(value), ]), ), + }, + decoders: { // { "columnFilters.name": "\"foo\"" } -> [{ id: "name", value: "foo" }] - decoder: (query) => + columnFilters: (query) => Object.entries(query) .filter(([key]) => key.startsWith("columnFilters.")) .map(([key, value]) => ({ diff --git a/packages/tanstack-table-search-params/src/index.ts b/packages/tanstack-table-search-params/src/index.ts index 5575187..d2134b1 100644 --- a/packages/tanstack-table-search-params/src/index.ts +++ b/packages/tanstack-table-search-params/src/index.ts @@ -5,6 +5,7 @@ import { useColumnFilters } from "./useColumnFilters"; import { useGlobalFilter } from "./useGlobalFilter"; import { usePagination } from "./usePagination"; import { useSorting } from "./useSorting"; +import type { ExtractSpecificStateOptions } from "./utils"; export type State = Pick< TableState, @@ -28,8 +29,9 @@ export type Returns = { }; export type Options = { - [KEY in keyof State]?: { - paramName?: KEY extends "pagination" + defaultValues?: Partial; + paramNames?: { + [KEY in keyof State]?: KEY extends "pagination" ? | { pageIndex: string; @@ -40,12 +42,29 @@ export type Options = { pageSize: string; }) => { pageIndex: string; pageSize: string }) : string | ((key: KEY) => string); - encoder?: (value: State[KEY]) => Query; - decoder?: (query: Query) => State[KEY]; - defaultValue?: State[KEY]; + }; + encoders?: { + [KEY in keyof State]?: (value: State[KEY]) => Query; + }; + decoders?: { + [KEY in keyof State]?: (query: Query) => State[KEY]; }; }; +const extractSpecificStateOptions = ({ + options, + key, +}: { + options: Options | undefined; + key: KEY; +}): ExtractSpecificStateOptions => + Object.fromEntries( + Object.entries(options ?? {}).map(([k, v]) => [ + k.replace(/s$/, ""), + v?.[key], + ]), + ); + export const useTableSearchParams = ( { push, @@ -70,19 +89,19 @@ export const useTableSearchParams = ( const { globalFilter, onGlobalFilterChange } = useGlobalFilter({ router, - options: options?.globalFilter, + options: extractSpecificStateOptions({ options, key: "globalFilter" }), }); const { sorting, onSortingChange } = useSorting({ router, - options: options?.sorting, + options: extractSpecificStateOptions({ options, key: "sorting" }), }); const { pagination, onPaginationChange } = usePagination({ router, - options: options?.pagination, + options: extractSpecificStateOptions({ options, key: "pagination" }), }); const { columnFilters, onColumnFiltersChange } = useColumnFilters({ router, - options: options?.columnFilters, + options: extractSpecificStateOptions({ options, key: "columnFilters" }), }); const state = useMemo( diff --git a/packages/tanstack-table-search-params/src/tests/columnFilters.test.ts b/packages/tanstack-table-search-params/src/tests/columnFilters.test.ts index ea86c31..2a26985 100644 --- a/packages/tanstack-table-search-params/src/tests/columnFilters.test.ts +++ b/packages/tanstack-table-search-params/src/tests/columnFilters.test.ts @@ -20,27 +20,29 @@ describe("columnFilters", () => { { name: "with options: string param name", options: { - columnFilters: { - paramName: "COLUMN_FILTERS", + paramNames: { + columnFilters: "COLUMN_FILTERS", }, }, }, { name: "with options: function param name", options: { - columnFilters: { - paramName: (key) => `userTable-${key}`, + paramNames: { + columnFilters: (key) => `userTable-${key}`, }, }, }, { name: "with options: default param name encoder/decoder", options: { - columnFilters: { - encoder: (columnFilters) => ({ + encoders: { + columnFilters: (columnFilters) => ({ columnFilters: JSON.stringify(columnFilters), }), - decoder: (query) => + }, + decoders: { + columnFilters: (query) => query["columnFilters"] ? JSON.parse(query["columnFilters"] as string) : query["columnFilters"], @@ -50,11 +52,13 @@ describe("columnFilters", () => { { name: "with options: custom param name encoder/decoder", options: { - columnFilters: { - encoder: (columnFilters) => ({ + encoders: { + columnFilters: (columnFilters) => ({ "userTable-columnFilters": JSON.stringify(columnFilters), }), - decoder: (query) => + }, + decoders: { + columnFilters: (query) => query["userTable-columnFilters"] ? JSON.parse(query["userTable-columnFilters"] as string) : query["userTable-columnFilters"], @@ -64,15 +68,17 @@ describe("columnFilters", () => { { name: "with options: custom number of params encoder/decoder", options: { - columnFilters: { - encoder: (columnFilters) => + encoders: { + columnFilters: (columnFilters) => Object.fromEntries( columnFilters.map(({ id, value }) => [ `columnFilters.${id}`, JSON.stringify(value), ]), ), - decoder: (query) => + }, + decoders: { + columnFilters: (query) => Object.entries(query) .filter(([key]) => key.startsWith("columnFilters.")) .map(([key, value]) => ({ @@ -85,19 +91,19 @@ describe("columnFilters", () => { { name: "with options: custom default value", options: { - columnFilters: { - defaultValue: [{ id: "custom", value: "default" }], + defaultValues: { + columnFilters: [{ id: "custom", value: "default" }], }, }, }, ])("%s", ({ options }) => { const paramName = - typeof options?.columnFilters?.paramName === "function" - ? options.columnFilters.paramName("columnFilters") - : options?.columnFilters?.paramName || "columnFilters"; + typeof options?.paramNames?.columnFilters === "function" + ? options?.paramNames?.columnFilters("columnFilters") + : options?.paramNames?.columnFilters || "columnFilters"; const defaultColumnFilters = - options?.columnFilters?.defaultValue ?? defaultDefaultColumnFilters; + options?.defaultValues?.columnFilters ?? defaultDefaultColumnFilters; test("single column: string value", () => { const { result: routerResult, rerender: routerRerender } = renderHook( @@ -137,7 +143,7 @@ describe("columnFilters", () => { { id: "name", value: "J" }, ]); expect(routerResult.current.query).toEqual( - options?.columnFilters?.encoder?.([{ id: "name", value: "J" }]) ?? { + options?.encoders?.columnFilters?.([{ id: "name", value: "J" }]) ?? { [paramName]: defaultColumnFilters.length === 0 ? "name.%22J%22" @@ -151,7 +157,7 @@ describe("columnFilters", () => { }); rerender(); expect(routerResult.current.query).toEqual( - options?.columnFilters?.encoder?.([]) ?? {}, + options?.encoders?.columnFilters?.([]) ?? {}, ); expect(result.current.getState().columnFilters).toEqual( defaultColumnFilters, @@ -204,7 +210,9 @@ describe("columnFilters", () => { { id: "age", value: [30, 40] }, ]); expect(routerResult.current.query).toEqual( - options?.columnFilters?.encoder?.([{ id: "age", value: [30, 40] }]) ?? { + options?.encoders?.columnFilters?.([ + { id: "age", value: [30, 40] }, + ]) ?? { [paramName]: defaultColumnFilters.length === 0 ? "age.%5B30%2C40%5D" @@ -221,7 +229,7 @@ describe("columnFilters", () => { defaultColumnFilters, ); expect(routerResult.current.query).toEqual( - options?.columnFilters?.encoder?.([]) ?? {}, + options?.encoders?.columnFilters?.([]) ?? {}, ); }); @@ -272,7 +280,7 @@ describe("columnFilters", () => { { id: "name", value: "J" }, ]); expect(routerResult.current.query).toEqual( - options?.columnFilters?.encoder?.([{ id: "name", value: "J" }]) ?? { + options?.encoders?.columnFilters?.([{ id: "name", value: "J" }]) ?? { [paramName]: defaultColumnFilters.length === 0 ? "name.%22J%22" @@ -301,7 +309,9 @@ describe("columnFilters", () => { { id: "age", value: [30, 40] }, ]); expect(routerResult.current.query).toEqual( - options?.columnFilters?.encoder?.([{ id: "age", value: [30, 40] }]) ?? { + options?.encoders?.columnFilters?.([ + { id: "age", value: [30, 40] }, + ]) ?? { [paramName]: defaultColumnFilters.length === 0 ? "age.%5B30%2C40%5D" @@ -317,7 +327,7 @@ describe("columnFilters", () => { defaultColumnFilters, ); expect(routerResult.current.query).toEqual( - options?.columnFilters?.encoder?.([]) ?? {}, + options?.encoders?.columnFilters?.([]) ?? {}, ); }); }); diff --git a/packages/tanstack-table-search-params/src/tests/globalFilter.test.ts b/packages/tanstack-table-search-params/src/tests/globalFilter.test.ts index 928f607..52b8074 100644 --- a/packages/tanstack-table-search-params/src/tests/globalFilter.test.ts +++ b/packages/tanstack-table-search-params/src/tests/globalFilter.test.ts @@ -21,27 +21,29 @@ describe("globalFilter", () => { { name: "with options: string param name", options: { - globalFilter: { - paramName: "GLOBAL_FILTER", + paramNames: { + globalFilter: "GLOBAL_FILTER", }, }, }, { name: "with options: function param name", options: { - globalFilter: { - paramName: (key) => `userTable-${key}`, + paramNames: { + globalFilter: (key) => `userTable-${key}`, }, }, }, { name: "with options: default param name encoder/decoder", options: { - globalFilter: { - encoder: (globalFilter) => ({ + encoders: { + globalFilter: (globalFilter) => ({ globalFilter: JSON.stringify(globalFilter), }), - decoder: (query) => + }, + decoders: { + globalFilter: (query) => query["globalFilter"] ? JSON.parse(query["globalFilter"] as string) : query["globalFilter"] ?? "", @@ -51,11 +53,13 @@ describe("globalFilter", () => { { name: "with options: custom param name encoder/decoder", options: { - globalFilter: { - encoder: (globalFilter) => ({ + encoders: { + globalFilter: (globalFilter) => ({ "userTable-globalFilter": JSON.stringify(globalFilter), }), - decoder: (query) => + }, + decoders: { + globalFilter: (query) => query["userTable-globalFilter"] ? JSON.parse(query["userTable-globalFilter"] as string) : query["userTable-globalFilter"] ?? "", @@ -65,16 +69,16 @@ describe("globalFilter", () => { { name: "with options: custom default value", options: { - globalFilter: { - defaultValue: "default", + defaultValues: { + globalFilter: "default", }, }, }, ])("%s", ({ options }) => { const paramName = - typeof options?.globalFilter?.paramName === "function" - ? options.globalFilter.paramName("globalFilter") - : options?.globalFilter?.paramName || "globalFilter"; + typeof options?.paramNames?.globalFilter === "function" + ? options?.paramNames?.globalFilter("globalFilter") + : options?.paramNames?.globalFilter || "globalFilter"; test("basic", () => { const { result: routerResult, rerender: routerRerender } = renderHook( @@ -98,7 +102,7 @@ describe("globalFilter", () => { }; const defaultGlobalFilter = - options?.globalFilter?.defaultValue ?? defaultDefaultGlobalFilter; + options?.defaultValues?.globalFilter ?? defaultDefaultGlobalFilter; // initial state expect(result.current.getState().globalFilter).toBe(defaultGlobalFilter); @@ -109,7 +113,7 @@ describe("globalFilter", () => { rerender(); expect(result.current.getState().globalFilter).toBe("John"); expect(routerResult.current.query).toEqual( - options?.globalFilter?.encoder?.("John") ?? { + options?.encoders?.globalFilter?.("John") ?? { [paramName]: "John", }, ); @@ -120,7 +124,7 @@ describe("globalFilter", () => { expect(result.current.getState().globalFilter).toBe(""); expect(routerResult.current.query).toEqual( - options?.globalFilter?.encoder?.("") ?? { + options?.encoders?.globalFilter?.("") ?? { [paramName]: defaultGlobalFilter ? encodedEmptyStringForCustomDefaultValue : undefined, diff --git a/packages/tanstack-table-search-params/src/tests/next-pages-router/columnFilters.test.ts b/packages/tanstack-table-search-params/src/tests/next-pages-router/columnFilters.test.ts index f4941da..22f0c4d 100644 --- a/packages/tanstack-table-search-params/src/tests/next-pages-router/columnFilters.test.ts +++ b/packages/tanstack-table-search-params/src/tests/next-pages-router/columnFilters.test.ts @@ -20,27 +20,29 @@ describe("columnFilters", () => { { name: "with options: string param name", options: { - columnFilters: { - paramName: "COLUMN_FILTERS", + paramNames: { + columnFilters: "COLUMN_FILTERS", }, }, }, { name: "with options: function param name", options: { - columnFilters: { - paramName: (key) => `userTable-${key}`, + paramNames: { + columnFilters: (key) => `userTable-${key}`, }, }, }, { name: "with options: default param name encoder/decoder", options: { - columnFilters: { - encoder: (columnFilters) => ({ + encoders: { + columnFilters: (columnFilters) => ({ columnFilters: JSON.stringify(columnFilters), }), - decoder: (query) => + }, + decoders: { + columnFilters: (query) => query["columnFilters"] ? JSON.parse(query["columnFilters"] as string) : query["columnFilters"], @@ -50,11 +52,13 @@ describe("columnFilters", () => { { name: "with options: custom param name encoder/decoder", options: { - columnFilters: { - encoder: (columnFilters) => ({ + encoders: { + columnFilters: (columnFilters) => ({ "userTable-columnFilters": JSON.stringify(columnFilters), }), - decoder: (query) => + }, + decoders: { + columnFilters: (query) => query["userTable-columnFilters"] ? JSON.parse(query["userTable-columnFilters"] as string) : query["userTable-columnFilters"], @@ -64,15 +68,17 @@ describe("columnFilters", () => { { name: "with options: custom number of params encoder/decoder", options: { - columnFilters: { - encoder: (columnFilters) => + encoders: { + columnFilters: (columnFilters) => Object.fromEntries( columnFilters.map(({ id, value }) => [ `columnFilters.${id}`, JSON.stringify(value), ]), ), - decoder: (query) => + }, + decoders: { + columnFilters: (query) => Object.entries(query) .filter(([key]) => key.startsWith("columnFilters.")) .map(([key, value]) => ({ @@ -85,19 +91,19 @@ describe("columnFilters", () => { { name: "with options: custom default value", options: { - columnFilters: { - defaultValue: [{ id: "custom", value: "default" }], + defaultValues: { + columnFilters: [{ id: "custom", value: "default" }], }, }, }, ])("%s", ({ options }) => { const paramName = - typeof options?.columnFilters?.paramName === "function" - ? options.columnFilters.paramName("columnFilters") - : options?.columnFilters?.paramName || "columnFilters"; + typeof options?.paramNames?.columnFilters === "function" + ? options?.paramNames?.columnFilters("columnFilters") + : options?.paramNames?.columnFilters || "columnFilters"; const defaultColumnFilters = - options?.columnFilters?.defaultValue ?? defaultDefaultColumnFilters; + options?.defaultValues?.columnFilters ?? defaultDefaultColumnFilters; test("single column: string value", () => { const { result, rerender } = renderHook(() => { @@ -127,7 +133,7 @@ describe("columnFilters", () => { { id: "name", value: "J" }, ]); expect(mockRouter.query).toEqual( - options?.columnFilters?.encoder?.([{ id: "name", value: "J" }]) ?? { + options?.encoders?.columnFilters?.([{ id: "name", value: "J" }]) ?? { [paramName]: defaultColumnFilters.length === 0 ? "name.%22J%22" @@ -141,7 +147,7 @@ describe("columnFilters", () => { }); rerender(); expect(mockRouter.query).toEqual( - options?.columnFilters?.encoder?.([]) ?? {}, + options?.encoders?.columnFilters?.([]) ?? {}, ); expect(result.current.getState().columnFilters).toEqual( defaultColumnFilters, @@ -184,7 +190,9 @@ describe("columnFilters", () => { { id: "age", value: [30, 40] }, ]); expect(mockRouter.query).toEqual( - options?.columnFilters?.encoder?.([{ id: "age", value: [30, 40] }]) ?? { + options?.encoders?.columnFilters?.([ + { id: "age", value: [30, 40] }, + ]) ?? { [paramName]: defaultColumnFilters.length === 0 ? "age.%5B30%2C40%5D" @@ -201,7 +209,7 @@ describe("columnFilters", () => { defaultColumnFilters, ); expect(mockRouter.query).toEqual( - options?.columnFilters?.encoder?.([]) ?? {}, + options?.encoders?.columnFilters?.([]) ?? {}, ); }); @@ -242,7 +250,7 @@ describe("columnFilters", () => { { id: "name", value: "J" }, ]); expect(mockRouter.query).toEqual( - options?.columnFilters?.encoder?.([{ id: "name", value: "J" }]) ?? { + options?.encoders?.columnFilters?.([{ id: "name", value: "J" }]) ?? { [paramName]: defaultColumnFilters.length === 0 ? "name.%22J%22" @@ -271,7 +279,9 @@ describe("columnFilters", () => { { id: "age", value: [30, 40] }, ]); expect(mockRouter.query).toEqual( - options?.columnFilters?.encoder?.([{ id: "age", value: [30, 40] }]) ?? { + options?.encoders?.columnFilters?.([ + { id: "age", value: [30, 40] }, + ]) ?? { [paramName]: defaultColumnFilters.length === 0 ? "age.%5B30%2C40%5D" @@ -287,7 +297,7 @@ describe("columnFilters", () => { defaultColumnFilters, ); expect(mockRouter.query).toEqual( - options?.columnFilters?.encoder?.([]) ?? {}, + options?.encoders?.columnFilters?.([]) ?? {}, ); }); }); diff --git a/packages/tanstack-table-search-params/src/tests/next-pages-router/globalFilter.test.ts b/packages/tanstack-table-search-params/src/tests/next-pages-router/globalFilter.test.ts index a66b679..2e7c8f2 100644 --- a/packages/tanstack-table-search-params/src/tests/next-pages-router/globalFilter.test.ts +++ b/packages/tanstack-table-search-params/src/tests/next-pages-router/globalFilter.test.ts @@ -21,27 +21,29 @@ describe("globalFilter", () => { { name: "with options: string param name", options: { - globalFilter: { - paramName: "GLOBAL_FILTER", + paramNames: { + globalFilter: "GLOBAL_FILTER", }, }, }, { name: "with options: function param name", options: { - globalFilter: { - paramName: (key) => `userTable-${key}`, + paramNames: { + globalFilter: (key) => `userTable-${key}`, }, }, }, { name: "with options: default param name encoder/decoder", options: { - globalFilter: { - encoder: (globalFilter) => ({ + encoders: { + globalFilter: (globalFilter) => ({ globalFilter: JSON.stringify(globalFilter), }), - decoder: (query) => + }, + decoders: { + globalFilter: (query) => query["globalFilter"] ? JSON.parse(query["globalFilter"] as string) : query["globalFilter"] ?? "", @@ -51,11 +53,13 @@ describe("globalFilter", () => { { name: "with options: custom param name encoder/decoder", options: { - globalFilter: { - encoder: (globalFilter) => ({ + encoders: { + globalFilter: (globalFilter) => ({ "userTable-globalFilter": JSON.stringify(globalFilter), }), - decoder: (query) => + }, + decoders: { + globalFilter: (query) => query["userTable-globalFilter"] ? JSON.parse(query["userTable-globalFilter"] as string) : query["userTable-globalFilter"] ?? "", @@ -65,16 +69,16 @@ describe("globalFilter", () => { { name: "with options: custom default value", options: { - globalFilter: { - defaultValue: "default", + defaultValues: { + globalFilter: "default", }, }, }, ])("%s", ({ options }) => { const paramName = - typeof options?.globalFilter?.paramName === "function" - ? options.globalFilter.paramName("globalFilter") - : options?.globalFilter?.paramName || "globalFilter"; + typeof options?.paramNames?.globalFilter === "function" + ? options?.paramNames?.globalFilter("globalFilter") + : options?.paramNames?.globalFilter || "globalFilter"; test("basic", () => { const { result, rerender } = renderHook(() => { @@ -88,7 +92,7 @@ describe("globalFilter", () => { }); const defaultGlobalFilter = - options?.globalFilter?.defaultValue ?? defaultDefaultGlobalFilter; + options?.defaultValues?.globalFilter ?? defaultDefaultGlobalFilter; // initial state expect(result.current.getState().globalFilter).toBe(defaultGlobalFilter); @@ -99,7 +103,7 @@ describe("globalFilter", () => { rerender(); expect(result.current.getState().globalFilter).toBe("John"); expect(mockRouter.query).toEqual( - options?.globalFilter?.encoder?.("John") ?? { + options?.encoders?.globalFilter?.("John") ?? { [paramName]: "John", }, ); @@ -109,7 +113,7 @@ describe("globalFilter", () => { rerender(); expect(result.current.getState().globalFilter).toBe(""); expect(mockRouter.query).toEqual( - options?.globalFilter?.encoder?.("") ?? { + options?.encoders?.globalFilter?.("") ?? { [paramName]: defaultGlobalFilter ? encodedEmptyStringForCustomDefaultValue : undefined, diff --git a/packages/tanstack-table-search-params/src/tests/next-pages-router/pagination.test.ts b/packages/tanstack-table-search-params/src/tests/next-pages-router/pagination.test.ts index 4a7d2d3..f6e7e7a 100644 --- a/packages/tanstack-table-search-params/src/tests/next-pages-router/pagination.test.ts +++ b/packages/tanstack-table-search-params/src/tests/next-pages-router/pagination.test.ts @@ -21,8 +21,8 @@ describe("pagination", () => { { name: "with options: string param name", options: { - pagination: { - paramName: { + paramNames: { + pagination: { pageIndex: "PAGE_INDEX", pageSize: "PAGE_SIZE", }, @@ -32,8 +32,8 @@ describe("pagination", () => { { name: "with options: function param name", options: { - pagination: { - paramName: ({ pageIndex, pageSize }) => ({ + paramNames: { + pagination: ({ pageIndex, pageSize }) => ({ pageIndex: `userTable-${pageIndex}`, pageSize: `userTable-${pageSize}`, }), @@ -43,12 +43,14 @@ describe("pagination", () => { { name: "with options: default param name encoder/decoder", options: { - pagination: { - encoder: (pagination) => ({ + encoders: { + pagination: (pagination) => ({ pageIndex: JSON.stringify(pagination.pageIndex), pageSize: JSON.stringify(pagination.pageSize), }), - decoder: (query) => ({ + }, + decoders: { + pagination: (query) => ({ pageIndex: query["pageIndex"] ? JSON.parse(query["pageIndex"] as string) : defaultDefaultPagination.pageIndex, @@ -62,12 +64,14 @@ describe("pagination", () => { { name: "with options: custom param name encoder/decoder", options: { - pagination: { - encoder: (pagination) => ({ + encoders: { + pagination: (pagination) => ({ "userTable-pageIndex": JSON.stringify(pagination.pageIndex), "userTable-pageSize": JSON.stringify(pagination.pageSize), }), - decoder: (query) => ({ + }, + decoders: { + pagination: (query) => ({ pageIndex: query["userTable-pageIndex"] ? JSON.parse(query["userTable-pageIndex"] as string) : defaultDefaultPagination.pageIndex, @@ -81,11 +85,13 @@ describe("pagination", () => { { name: "with options: custom number of params encoder/decoder", options: { - pagination: { - encoder: (pagination) => ({ + encoders: { + pagination: (pagination) => ({ pagination: JSON.stringify(pagination), }), - decoder: (query) => + }, + decoders: { + pagination: (query) => query["pagination"] ? JSON.parse(query["pagination"] as string) : defaultDefaultPagination, @@ -95,8 +101,8 @@ describe("pagination", () => { { name: "with options: custom default value", options: { - pagination: { - defaultValue: { + defaultValues: { + pagination: { pageIndex: 3, pageSize: 25, }, @@ -105,12 +111,12 @@ describe("pagination", () => { }, ])("%s", ({ options }) => { const paramName = - typeof options?.pagination?.paramName === "function" - ? options.pagination.paramName({ + typeof options?.paramNames?.pagination === "function" + ? options.paramNames.pagination({ pageIndex: "pageIndex", pageSize: "pageSize", }) - : options?.pagination?.paramName ?? { + : options?.paramNames?.pagination ?? { pageIndex: "pageIndex", pageSize: "pageSize", }; @@ -127,7 +133,7 @@ describe("pagination", () => { }); const defaultPagination = - options?.pagination?.defaultValue ?? defaultDefaultPagination; + options?.defaultValues?.pagination ?? defaultDefaultPagination; // initial state expect(result.current.getState().pagination).toEqual(defaultPagination); @@ -143,7 +149,7 @@ describe("pagination", () => { pageSize: updatedPageSize, }); expect(mockRouter.query).toEqual( - options?.pagination?.encoder?.({ + options?.encoders?.pagination?.({ pageIndex: defaultPagination.pageIndex, pageSize: updatedPageSize, }) ?? { @@ -159,7 +165,7 @@ describe("pagination", () => { pageSize: updatedPageSize, }); expect(mockRouter.query).toEqual( - options?.pagination?.encoder?.({ + options?.encoders?.pagination?.({ pageIndex: defaultPagination.pageIndex + 1, pageSize: updatedPageSize, }) ?? { @@ -176,11 +182,11 @@ describe("pagination", () => { pageSize: updatedPageSize, }); expect(mockRouter.query).toEqual( - options?.pagination?.encoder?.({ + options?.encoders?.pagination?.({ pageIndex: 0, pageSize: updatedPageSize, }) ?? { - [paramName.pageIndex]: options?.pagination?.defaultValue + [paramName.pageIndex]: options?.defaultValues?.pagination ? "1" : undefined, [paramName.pageSize]: `${updatedPageSize}`, @@ -195,11 +201,11 @@ describe("pagination", () => { pageSize: 10, }); expect(mockRouter.query).toEqual( - options?.pagination?.encoder?.({ + options?.encoders?.pagination?.({ pageIndex: 0, pageSize: 10, }) ?? - (options?.pagination?.defaultValue + (options?.defaultValues?.pagination ? { [paramName.pageIndex]: "1", [paramName.pageSize]: "10", diff --git a/packages/tanstack-table-search-params/src/tests/next-pages-router/sorting.test.ts b/packages/tanstack-table-search-params/src/tests/next-pages-router/sorting.test.ts index 0db704d..6d1d752 100644 --- a/packages/tanstack-table-search-params/src/tests/next-pages-router/sorting.test.ts +++ b/packages/tanstack-table-search-params/src/tests/next-pages-router/sorting.test.ts @@ -21,27 +21,29 @@ describe("sorting", () => { { name: "with options: string param name", options: { - sorting: { - paramName: "SORTING", + paramNames: { + sorting: "SORTING", }, }, }, { name: "with options: function param name", options: { - sorting: { - paramName: (key) => `userTable-${key}`, + paramNames: { + sorting: (key) => `userTable-${key}`, }, }, }, { name: "with options: default param name encoder/decoder", options: { - sorting: { - encoder: (sorting) => ({ + encoders: { + sorting: (sorting) => ({ sorting: JSON.stringify(sorting), }), - decoder: (query) => + }, + decoders: { + sorting: (query) => query["sorting"] ? JSON.parse(query["sorting"] as string) : query["sorting"], @@ -51,11 +53,13 @@ describe("sorting", () => { { name: "with options: custom param name encoder/decoder", options: { - sorting: { - encoder: (sorting) => ({ + encoders: { + sorting: (sorting) => ({ "userTable-sorting": JSON.stringify(sorting), }), - decoder: (query) => + }, + decoders: { + sorting: (query) => query["userTable-sorting"] ? JSON.parse(query["userTable-sorting"] as string) : query["userTable-sorting"], @@ -65,15 +69,17 @@ describe("sorting", () => { { name: "with options: custom number of params encoder/decoder", options: { - sorting: { - encoder: (sorting) => + encoders: { + sorting: (sorting) => Object.fromEntries( sorting.map(({ id, desc }) => [ `sorting.${id}`, desc ? "desc" : "asc", ]), ), - decoder: (query) => + }, + decoders: { + sorting: (query) => Object.entries(query) .filter(([key]) => key.startsWith("sorting.")) .map(([key, desc]) => ({ @@ -86,19 +92,19 @@ describe("sorting", () => { { name: "with options: custom default value", options: { - sorting: { - defaultValue: [{ id: "custom", desc: true }], + defaultValues: { + sorting: [{ id: "custom", desc: true }], }, }, }, ])("%s", ({ options }) => { const paramName = - typeof options?.sorting?.paramName === "function" - ? options.sorting.paramName("sorting") - : options?.sorting?.paramName || "sorting"; + typeof options?.paramNames?.sorting === "function" + ? options.paramNames.sorting("sorting") + : options?.paramNames?.sorting || "sorting"; const defaultSorting = - options?.sorting?.defaultValue ?? defaultDefaultSorting; + options?.defaultValues?.sorting ?? defaultDefaultSorting; test("single column", () => { const { result, rerender } = renderHook(() => { @@ -129,7 +135,7 @@ describe("sorting", () => { { id: "id", desc: true }, ]); expect(mockRouter.query).toEqual( - options?.sorting?.encoder?.([{ id: "id", desc: true }]) ?? { + options?.encoders?.sorting?.([{ id: "id", desc: true }]) ?? { [paramName]: "id.desc", }, ); @@ -145,7 +151,7 @@ describe("sorting", () => { { id: "id", desc: false }, ]); expect(mockRouter.query).toEqual( - options?.sorting?.encoder?.([{ id: "id", desc: false }]) ?? { + options?.encoders?.sorting?.([{ id: "id", desc: false }]) ?? { [paramName]: "id.asc", }, ); @@ -161,7 +167,7 @@ describe("sorting", () => { { id: "name", desc: false }, ]); expect(mockRouter.query).toEqual( - options?.sorting?.encoder?.([{ id: "name", desc: false }]) ?? { + options?.encoders?.sorting?.([{ id: "name", desc: false }]) ?? { [paramName]: "name.asc", }, ); @@ -181,7 +187,7 @@ describe("sorting", () => { rerender(); expect(result.current.getState().sorting).toEqual([]); expect(mockRouter.query).toEqual( - options?.sorting?.encoder?.([]) ?? { + options?.encoders?.sorting?.([]) ?? { [paramName]: defaultSorting.length > 0 ? encodedEmptyStringForCustomDefaultValue @@ -226,7 +232,7 @@ describe("sorting", () => { { id: "name", desc: false }, ]); expect(mockRouter.query).toEqual( - options?.sorting?.encoder?.([ + options?.encoders?.sorting?.([ { id: "id", desc: true }, { id: "name", desc: false }, ]) ?? { [paramName]: "id.desc,name.asc" }, @@ -244,7 +250,7 @@ describe("sorting", () => { { id: "name", desc: false }, ]); expect(mockRouter.query).toEqual( - options?.sorting?.encoder?.([ + options?.encoders?.sorting?.([ { id: "id", desc: false }, { id: "name", desc: false }, ]) ?? { [paramName]: "id.asc,name.asc" }, @@ -261,7 +267,7 @@ describe("sorting", () => { { id: "id", desc: false }, ]); expect(mockRouter.query).toEqual( - options?.sorting?.encoder?.([{ id: "id", desc: false }]) ?? { + options?.encoders?.sorting?.([{ id: "id", desc: false }]) ?? { [paramName]: "id.asc", }, ); diff --git a/packages/tanstack-table-search-params/src/tests/pagination.test.ts b/packages/tanstack-table-search-params/src/tests/pagination.test.ts index 1f054cb..10661c5 100644 --- a/packages/tanstack-table-search-params/src/tests/pagination.test.ts +++ b/packages/tanstack-table-search-params/src/tests/pagination.test.ts @@ -21,8 +21,8 @@ describe("pagination", () => { { name: "with options: string param name", options: { - pagination: { - paramName: { + paramNames: { + pagination: { pageIndex: "PAGE_INDEX", pageSize: "PAGE_SIZE", }, @@ -32,8 +32,8 @@ describe("pagination", () => { { name: "with options: function param name", options: { - pagination: { - paramName: ({ pageIndex, pageSize }) => ({ + paramNames: { + pagination: ({ pageIndex, pageSize }) => ({ pageIndex: `userTable-${pageIndex}`, pageSize: `userTable-${pageSize}`, }), @@ -43,12 +43,14 @@ describe("pagination", () => { { name: "with options: default param name encoder/decoder", options: { - pagination: { - encoder: (pagination) => ({ + encoders: { + pagination: (pagination) => ({ pageIndex: JSON.stringify(pagination.pageIndex), pageSize: JSON.stringify(pagination.pageSize), }), - decoder: (query) => ({ + }, + decoders: { + pagination: (query) => ({ pageIndex: query["pageIndex"] ? JSON.parse(query["pageIndex"] as string) : defaultDefaultPagination.pageIndex, @@ -62,12 +64,14 @@ describe("pagination", () => { { name: "with options: custom param name encoder/decoder", options: { - pagination: { - encoder: (pagination) => ({ + encoders: { + pagination: (pagination) => ({ "userTable-pageIndex": JSON.stringify(pagination.pageIndex), "userTable-pageSize": JSON.stringify(pagination.pageSize), }), - decoder: (query) => ({ + }, + decoders: { + pagination: (query) => ({ pageIndex: query["userTable-pageIndex"] ? JSON.parse(query["userTable-pageIndex"] as string) : defaultDefaultPagination.pageIndex, @@ -81,11 +85,13 @@ describe("pagination", () => { { name: "with options: custom number of params encoder/decoder", options: { - pagination: { - encoder: (pagination) => ({ + encoders: { + pagination: (pagination) => ({ pagination: JSON.stringify(pagination), }), - decoder: (query) => + }, + decoders: { + pagination: (query) => query["pagination"] ? JSON.parse(query["pagination"] as string) : defaultDefaultPagination, @@ -95,8 +101,8 @@ describe("pagination", () => { { name: "with options: custom default value", options: { - pagination: { - defaultValue: { + defaultValues: { + pagination: { pageIndex: 3, pageSize: 25, }, @@ -105,12 +111,12 @@ describe("pagination", () => { }, ])("%s", ({ options }) => { const paramName = - typeof options?.pagination?.paramName === "function" - ? options.pagination.paramName({ + typeof options?.paramNames?.pagination === "function" + ? options.paramNames.pagination({ pageIndex: "pageIndex", pageSize: "pageSize", }) - : options?.pagination?.paramName ?? { + : options?.paramNames?.pagination ?? { pageIndex: "pageIndex", pageSize: "pageSize", }; @@ -137,7 +143,7 @@ describe("pagination", () => { }; const defaultPagination = - options?.pagination?.defaultValue ?? defaultDefaultPagination; + options?.defaultValues?.pagination ?? defaultDefaultPagination; // initial state expect(result.current.getState().pagination).toEqual(defaultPagination); @@ -153,7 +159,7 @@ describe("pagination", () => { pageSize: updatedPageSize, }); expect(routerResult.current.query).toEqual( - options?.pagination?.encoder?.({ + options?.encoders?.pagination?.({ pageIndex: defaultPagination.pageIndex, pageSize: updatedPageSize, }) ?? { @@ -169,7 +175,7 @@ describe("pagination", () => { pageSize: updatedPageSize, }); expect(routerResult.current.query).toEqual( - options?.pagination?.encoder?.({ + options?.encoders?.pagination?.({ pageIndex: defaultPagination.pageIndex + 1, pageSize: updatedPageSize, }) ?? { @@ -186,11 +192,11 @@ describe("pagination", () => { pageSize: updatedPageSize, }); expect(routerResult.current.query).toEqual( - options?.pagination?.encoder?.({ + options?.encoders?.pagination?.({ pageIndex: 0, pageSize: updatedPageSize, }) ?? { - [paramName.pageIndex]: options?.pagination?.defaultValue + [paramName.pageIndex]: options?.defaultValues?.pagination ? "1" : undefined, [paramName.pageSize]: `${updatedPageSize}`, @@ -205,11 +211,11 @@ describe("pagination", () => { pageSize: 10, }); expect(routerResult.current.query).toEqual( - options?.pagination?.encoder?.({ + options?.encoders?.pagination?.({ pageIndex: 0, pageSize: 10, }) ?? - (options?.pagination?.defaultValue + (options?.defaultValues?.pagination ? { [paramName.pageIndex]: "1", [paramName.pageSize]: "10", diff --git a/packages/tanstack-table-search-params/src/tests/sorting.test.ts b/packages/tanstack-table-search-params/src/tests/sorting.test.ts index 655f5c0..cd96803 100644 --- a/packages/tanstack-table-search-params/src/tests/sorting.test.ts +++ b/packages/tanstack-table-search-params/src/tests/sorting.test.ts @@ -21,27 +21,29 @@ describe("sorting", () => { { name: "with options: string param name", options: { - sorting: { - paramName: "SORTING", + paramNames: { + sorting: "SORTING", }, }, }, { name: "with options: function param name", options: { - sorting: { - paramName: (key) => `userTable-${key}`, + paramNames: { + sorting: (key) => `userTable-${key}`, }, }, }, { name: "with options: default param name encoder/decoder", options: { - sorting: { - encoder: (sorting) => ({ + encoders: { + sorting: (sorting) => ({ sorting: JSON.stringify(sorting), }), - decoder: (query) => + }, + decoders: { + sorting: (query) => query["sorting"] ? JSON.parse(query["sorting"] as string) : query["sorting"], @@ -51,11 +53,13 @@ describe("sorting", () => { { name: "with options: custom param name encoder/decoder", options: { - sorting: { - encoder: (sorting) => ({ + encoders: { + sorting: (sorting) => ({ "userTable-sorting": JSON.stringify(sorting), }), - decoder: (query) => + }, + decoders: { + sorting: (query) => query["userTable-sorting"] ? JSON.parse(query["userTable-sorting"] as string) : query["userTable-sorting"], @@ -65,15 +69,17 @@ describe("sorting", () => { { name: "with options: custom number of params encoder/decoder", options: { - sorting: { - encoder: (sorting) => + encoders: { + sorting: (sorting) => Object.fromEntries( sorting.map(({ id, desc }) => [ `sorting.${id}`, desc ? "desc" : "asc", ]), ), - decoder: (query) => + }, + decoders: { + sorting: (query) => Object.entries(query) .filter(([key]) => key.startsWith("sorting.")) .map(([key, desc]) => ({ @@ -86,19 +92,19 @@ describe("sorting", () => { { name: "with options: custom default value", options: { - sorting: { - defaultValue: [{ id: "custom", desc: true }], + defaultValues: { + sorting: [{ id: "custom", desc: true }], }, }, }, ])("%s", ({ options }) => { const paramName = - typeof options?.sorting?.paramName === "function" - ? options.sorting.paramName("sorting") - : options?.sorting?.paramName || "sorting"; + typeof options?.paramNames?.sorting === "function" + ? options.paramNames.sorting("sorting") + : options?.paramNames?.sorting || "sorting"; const defaultSorting = - options?.sorting?.defaultValue ?? defaultDefaultSorting; + options?.defaultValues?.sorting ?? defaultDefaultSorting; test("single column", () => { const { result: routerResult, rerender: routerRerender } = renderHook( @@ -139,7 +145,7 @@ describe("sorting", () => { { id: "id", desc: true }, ]); expect(routerResult.current.query).toEqual( - options?.sorting?.encoder?.([{ id: "id", desc: true }]) ?? { + options?.encoders?.sorting?.([{ id: "id", desc: true }]) ?? { [paramName]: "id.desc", }, ); @@ -155,7 +161,7 @@ describe("sorting", () => { { id: "id", desc: false }, ]); expect(routerResult.current.query).toEqual( - options?.sorting?.encoder?.([{ id: "id", desc: false }]) ?? { + options?.encoders?.sorting?.([{ id: "id", desc: false }]) ?? { [paramName]: "id.asc", }, ); @@ -171,7 +177,7 @@ describe("sorting", () => { { id: "name", desc: false }, ]); expect(routerResult.current.query).toEqual( - options?.sorting?.encoder?.([{ id: "name", desc: false }]) ?? { + options?.encoders?.sorting?.([{ id: "name", desc: false }]) ?? { [paramName]: "name.asc", }, ); @@ -191,7 +197,7 @@ describe("sorting", () => { rerender(); expect(result.current.getState().sorting).toEqual([]); expect(routerResult.current.query).toEqual( - options?.sorting?.encoder?.([]) ?? { + options?.encoders?.sorting?.([]) ?? { [paramName]: defaultSorting.length > 0 ? encodedEmptyStringForCustomDefaultValue @@ -246,7 +252,7 @@ describe("sorting", () => { { id: "name", desc: false }, ]); expect(routerResult.current.query).toEqual( - options?.sorting?.encoder?.([ + options?.encoders?.sorting?.([ { id: "id", desc: true }, { id: "name", desc: false }, ]) ?? { [paramName]: "id.desc,name.asc" }, @@ -264,7 +270,7 @@ describe("sorting", () => { { id: "name", desc: false }, ]); expect(routerResult.current.query).toEqual( - options?.sorting?.encoder?.([ + options?.encoders?.sorting?.([ { id: "id", desc: false }, { id: "name", desc: false }, ]) ?? { [paramName]: "id.asc,name.asc" }, @@ -281,7 +287,7 @@ describe("sorting", () => { { id: "id", desc: false }, ]); expect(routerResult.current.query).toEqual( - options?.sorting?.encoder?.([{ id: "id", desc: false }]) ?? { + options?.encoders?.sorting?.([{ id: "id", desc: false }]) ?? { [paramName]: "id.asc", }, ); diff --git a/packages/tanstack-table-search-params/src/useColumnFilters.ts b/packages/tanstack-table-search-params/src/useColumnFilters.ts index 78c5a5c..a4834d9 100644 --- a/packages/tanstack-table-search-params/src/useColumnFilters.ts +++ b/packages/tanstack-table-search-params/src/useColumnFilters.ts @@ -1,19 +1,20 @@ import { type OnChangeFn, functionalUpdate } from "@tanstack/react-table"; import { useCallback, useMemo } from "react"; -import { type Options, PARAM_NAMES, type State } from "."; +import { PARAM_NAMES, type State } from "."; import { decodeColumnFilters, encodeColumnFilters, } from "./encoder-decoder/columnFilters"; import type { Router } from "./types"; import { updateQuery } from "./updateQuery"; +import type { ExtractSpecificStateOptions } from "./utils"; export const defaultDefaultColumnFilters = [] as const satisfies State["sorting"]; type Props = { router: Router; - options?: Options["columnFilters"]; + options?: ExtractSpecificStateOptions<"columnFilters">; }; type Returns = { diff --git a/packages/tanstack-table-search-params/src/useGlobalFilter.ts b/packages/tanstack-table-search-params/src/useGlobalFilter.ts index 8e6dae5..0ab2669 100644 --- a/packages/tanstack-table-search-params/src/useGlobalFilter.ts +++ b/packages/tanstack-table-search-params/src/useGlobalFilter.ts @@ -1,19 +1,20 @@ import { type OnChangeFn, functionalUpdate } from "@tanstack/react-table"; import { useCallback } from "react"; -import { type Options, PARAM_NAMES, type State } from "."; +import { PARAM_NAMES, type State } from "."; import { decodeGlobalFilter, encodeGlobalFilter, } from "./encoder-decoder/globalFilter"; import type { Router } from "./types"; import { updateQuery } from "./updateQuery"; +import type { ExtractSpecificStateOptions } from "./utils"; export const defaultDefaultGlobalFilter = "" as const satisfies State["globalFilter"]; type Props = { router: Router; - options?: Options["globalFilter"]; + options?: ExtractSpecificStateOptions<"globalFilter"> | undefined; }; type Returns = { @@ -22,7 +23,7 @@ type Returns = { }; export const useGlobalFilter = ({ router, options }: Props): Returns => { - const paramName = + const paramNames = (typeof options?.paramName === "function" ? options?.paramName(PARAM_NAMES.GLOBAL_FILTER) : options?.paramName) || PARAM_NAMES.GLOBAL_FILTER; @@ -32,7 +33,7 @@ export const useGlobalFilter = ({ router, options }: Props): Returns => { const globalFilter = options?.decoder ? options?.decoder?.(router.query) - : decodeGlobalFilter(router.query[paramName], defaultGlobalFilter); + : decodeGlobalFilter(router.query[paramNames], defaultGlobalFilter); return { globalFilter, @@ -43,7 +44,7 @@ export const useGlobalFilter = ({ router, options }: Props): Returns => { options?.encoder ? options.encoder(globalFilter) : { - [paramName]: encodeGlobalFilter( + [paramNames]: encodeGlobalFilter( globalFilter, defaultGlobalFilter, ), @@ -54,7 +55,7 @@ export const useGlobalFilter = ({ router, options }: Props): Returns => { router, }); }, - [router, globalFilter, paramName, options?.encoder, defaultGlobalFilter], + [router, globalFilter, paramNames, options?.encoder, defaultGlobalFilter], ), }; }; diff --git a/packages/tanstack-table-search-params/src/usePagination.ts b/packages/tanstack-table-search-params/src/usePagination.ts index 1cedd90..a21d268 100644 --- a/packages/tanstack-table-search-params/src/usePagination.ts +++ b/packages/tanstack-table-search-params/src/usePagination.ts @@ -1,12 +1,13 @@ import { type OnChangeFn, functionalUpdate } from "@tanstack/react-table"; import { useCallback, useMemo } from "react"; -import { type Options, PARAM_NAMES, type State } from "."; +import { PARAM_NAMES, type State } from "."; import { decodePagination, encodePagination, } from "./encoder-decoder/pagination"; import type { Router } from "./types"; import { updateQuery } from "./updateQuery"; +import type { ExtractSpecificStateOptions } from "./utils"; export const defaultDefaultPagination = { pageIndex: 0, @@ -15,7 +16,7 @@ export const defaultDefaultPagination = { type Props = { router: Router; - options?: Options["pagination"]; + options?: ExtractSpecificStateOptions<"pagination">; }; type Returns = { diff --git a/packages/tanstack-table-search-params/src/useSorting.ts b/packages/tanstack-table-search-params/src/useSorting.ts index afebfb2..494ec67 100644 --- a/packages/tanstack-table-search-params/src/useSorting.ts +++ b/packages/tanstack-table-search-params/src/useSorting.ts @@ -1,15 +1,16 @@ import { type OnChangeFn, functionalUpdate } from "@tanstack/react-table"; import { useCallback, useMemo } from "react"; -import { type Options, PARAM_NAMES, type State } from "."; +import { PARAM_NAMES, type State } from "."; import { decodeSorting, encodeSorting } from "./encoder-decoder/sorting"; import type { Router } from "./types"; import { updateQuery } from "./updateQuery"; +import type { ExtractSpecificStateOptions } from "./utils"; export const defaultDefaultSorting = [] as const satisfies State["sorting"]; type Props = { router: Router; - options?: Options["sorting"]; + options?: ExtractSpecificStateOptions<"sorting">; }; type Returns = { diff --git a/packages/tanstack-table-search-params/src/utils.ts b/packages/tanstack-table-search-params/src/utils.ts new file mode 100644 index 0000000..3cad680 --- /dev/null +++ b/packages/tanstack-table-search-params/src/utils.ts @@ -0,0 +1,7 @@ +import type { Options, State } from "."; + +export type ExtractSpecificStateOptions = { + [OPTION_NAME in keyof Required as OPTION_NAME extends `${infer T}s` + ? T + : OPTION_NAME]?: Required[OPTION_NAME][STATE_NAME] | undefined; +};