diff --git a/.changeset/afraid-boxes-grab.md b/.changeset/afraid-boxes-grab.md
new file mode 100644
index 0000000..818e3c2
--- /dev/null
+++ b/.changeset/afraid-boxes-grab.md
@@ -0,0 +1,7 @@
+---
+"@use-search-params-state/react": patch
+"@use-search-params-state/eslint-config": patch
+"@use-search-params-state/next": patch
+---
+
+Added array values feature
diff --git a/apps/playground/src/app/array-values/page.tsx b/apps/playground/src/app/array-values/page.tsx
new file mode 100644
index 0000000..4f7705e
--- /dev/null
+++ b/apps/playground/src/app/array-values/page.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { SetKeyValueArrayInputs } from "@/components/set-key-value-inputs";
+import { Alert } from "@/components/ui/alert";
+
+import { useSearchParamsState } from "@use-search-params-state/next";
+
+export default function Page() {
+ const [state, setState] = useSearchParamsState();
+
+ return (
+
+
+ {"This demonstration shows how to use the library with arrays."}
+
+
+
setState(k, v)} />
+
+ {JSON.stringify(state, null, 2)}
+
+ );
+}
diff --git a/apps/playground/src/app/sidebar.tsx b/apps/playground/src/app/sidebar.tsx
index d998de1..e4da3ca 100644
--- a/apps/playground/src/app/sidebar.tsx
+++ b/apps/playground/src/app/sidebar.tsx
@@ -8,6 +8,7 @@ export const Sidebar = () => {
Basic
+ Array Values
With default values
diff --git a/apps/playground/src/app/with-weak-typesafety/page.tsx b/apps/playground/src/app/with-weak-typesafety/page.tsx
index 5c791b3..e85db8b 100644
--- a/apps/playground/src/app/with-weak-typesafety/page.tsx
+++ b/apps/playground/src/app/with-weak-typesafety/page.tsx
@@ -5,10 +5,12 @@ import { Alert } from "@/components/ui/alert";
import { useSearchParamsState } from "@use-search-params-state/next";
-interface SearchParamsType {
+// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+type SearchParamsType = {
page: string;
search?: string;
-}
+ testArray?: string[];
+};
export default function Page() {
const [state, setState] = useSearchParamsState({
@@ -17,6 +19,8 @@ export default function Page() {
},
});
+ setState("page", "2");
+
return (
diff --git a/apps/playground/src/components/set-key-value-inputs.tsx b/apps/playground/src/components/set-key-value-inputs.tsx
index 2db5ffb..d90595b 100644
--- a/apps/playground/src/components/set-key-value-inputs.tsx
+++ b/apps/playground/src/components/set-key-value-inputs.tsx
@@ -1,5 +1,7 @@
import { useState } from "react";
+import { PlusIcon } from "lucide-react";
+import { Badge } from "./ui/badge";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
@@ -40,3 +42,58 @@ export const SetKeyValueInputs = ({
);
};
+
+export const SetKeyValueArrayInputs = ({
+ setState,
+}: {
+ setState: (key: string, value: string[]) => void;
+}) => {
+ const [key, setKey] = useState("");
+ const [v, sv] = useState("");
+ const [values, setValues] = useState([]);
+
+ return (
+
+
+
+
+ setKey(e.target.value)}
+ />
+
+
+
+
+
+
+ sv(e.target.value)}
+ />
+
+
+
+
+ {values.map((node, i) => {
+ return {node};
+ })}
+
+
+
+
+
+
+ );
+};
diff --git a/packages/next/src/example.tsx b/packages/next/src/example.tsx
index b1a1d30..7a1ea68 100644
--- a/packages/next/src/example.tsx
+++ b/packages/next/src/example.tsx
@@ -4,15 +4,35 @@ const Component = () => {
const [state, setState] = useSearchParamsState<{
greeting?: string;
hello: string;
+ testArray: string[];
}>();
const handleClick = () => {
state.greeting === "hello"
? setState("greeting", "world")
: setState("greeting", "hello");
+
+ setState("testArray", ["hello", "world"]);
};
return ;
};
;
+
+const Comp2 = () => {
+ const [state, setState] = useSearchParamsState();
+
+ const handleClick = () => {
+ state.greeting === "hello"
+ ? setState("greeting", "world")
+ : setState("greeting", "hello");
+
+ setState("fdsafdsa", ["fdsafdsa"]);
+ setState("fdsafdsa", "fdsafdsa");
+ };
+
+ return ;
+};
+
+;
diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts
index 0671f63..184afa5 100644
--- a/packages/next/src/index.ts
+++ b/packages/next/src/index.ts
@@ -4,11 +4,9 @@ import { useSearchParamsState as useSearchParamsStateReact } from "@use-search-p
import type { UseSearchParamsStateOptions } from "@use-search-params-state/react";
export const useSearchParamsState = <
- State extends
- | { [K in keyof State]: string }
- | Record = Record,
+ S extends Partial>,
>(
- opts?: UseSearchParamsStateOptions,
+ opts?: UseSearchParamsStateOptions,
) => {
const searchParams = useSearchParams();
const router = useRouter();
@@ -18,7 +16,7 @@ export const useSearchParamsState = <
router.push(pathname + "?" + newSearchParams.toString());
};
- return useSearchParamsStateReact({
+ return useSearchParamsStateReact({
searchParams,
setSearchParams,
...opts,
diff --git a/packages/react/src/example.tsx b/packages/react/src/example.tsx
index aba2606..71621cc 100644
--- a/packages/react/src/example.tsx
+++ b/packages/react/src/example.tsx
@@ -7,7 +7,7 @@ const Component1 = () => {
const [state, setState] = useSearchParamsState<{
greeting?: string;
- hello: string;
+ hello: string[];
}>({
searchParams,
setSearchParams: (newSearchParams) => {
@@ -19,7 +19,7 @@ const Component1 = () => {
const handleClick = () => {
state.greeting === "hello"
? setState("greeting", "world")
- : setState("greeting", "hello");
+ : setState("hello", ["hello"]);
};
return ;
@@ -33,6 +33,7 @@ const Comp2 = () => {
const [state, setState] = useSearchParamsState<{
page: string;
hello: string;
+ testArray: string[];
}>({
defaultValues: {
hello: "world",
@@ -46,6 +47,8 @@ const Comp2 = () => {
const handleClick = () => {
state.page === "1" ? setState("page", "2") : setState("page", "1");
+
+ setState("testArray", ["hello", "world"]);
};
return ;
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index e99cf81..a32c401 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -20,17 +20,14 @@ export * from "./types";
// };
export const useSearchParamsState = <
- State extends
- | { [K in keyof State]: string }
- | Record = Record,
+ S extends Partial>,
// ZodSchema extends ZodObject,
// ZodSchemaRaw extends ZodRawShape,
// ZodSchemaType extends ZodInfer,
- Params extends
- UseSearchParamsStateParams = UseSearchParamsStateParams,
+ Params extends UseSearchParamsStateParams = UseSearchParamsStateParams,
>(
p: Params,
-): [State, (key: keyof State, value: string) => void] => {
+): [S, (key: K, value: S[K]) => void] => {
// Configure default values.
const opts: Params = {
removeDefaultValues: true,
@@ -48,14 +45,14 @@ export const useSearchParamsState = <
const [initiallySetKeys] = useState(Array.from(p.searchParams.keys()));
- const setState = (key: keyof State, value: string) => {
+ const setState = (key: K, value: S[K]) => {
const newObject = {
...spObject,
[key]: value,
};
- const newSearchParams = getSearchParams({
- newObject: newObject as unknown as State,
+ const newSearchParams = getSearchParams({
+ newObject: newObject as unknown as S,
options: opts,
initiallySetKeys,
});
@@ -63,5 +60,5 @@ export const useSearchParamsState = <
p.setSearchParams(newSearchParams);
};
- return [spObject as unknown as State, setState];
+ return [spObject as unknown as S, setState];
};
diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts
index df81e77..2346a53 100644
--- a/packages/react/src/types.ts
+++ b/packages/react/src/types.ts
@@ -11,7 +11,7 @@ export interface UseSearchParamsStateOptions {
* you're accessing does not contain a value. We recommend always
* providing default values for your search params.
*/
- defaultValues?: Record;
+ defaultValues?: Record;
/**
* Zod schema
diff --git a/packages/react/src/utils.ts b/packages/react/src/utils.ts
index 8ae1c80..de42940 100644
--- a/packages/react/src/utils.ts
+++ b/packages/react/src/utils.ts
@@ -2,12 +2,14 @@ import type { ReadonlyURLSearchParams } from "next/navigation";
import type { UseSearchParamsStateParams } from "./types";
-export const getSearchParams = ({
+export const getSearchParams = <
+ S extends Partial>,
+>({
newObject,
options,
initiallySetKeys,
}: {
- newObject: Record;
+ newObject: S;
options: UseSearchParamsStateParams>;
initiallySetKeys: string[];
}) => {
@@ -19,7 +21,14 @@ export const getSearchParams = ({
// If the key was set initially, we want to set save it regardless of value.
if (options.preserveInitialKeys && initiallySetKeys.includes(k)) {
- sp.set(k, v);
+ if (Array.isArray(v)) {
+ v.forEach((value) => {
+ sp.append(k, value);
+ });
+ } else {
+ sp.set(k, v);
+ }
+
return;
}
@@ -29,7 +38,13 @@ export const getSearchParams = ({
// If the value is the default value and we want to remove default values, skip it.
if (options.removeDefaultValues && v === options.defaultValues?.[k]) return;
- sp.set(k, v);
+ if (Array.isArray(v)) {
+ v.forEach((value) => {
+ sp.append(k, value);
+ });
+ } else {
+ sp.set(k, v);
+ }
});
if (options.sortKeys) {
@@ -41,13 +56,19 @@ export const getSearchParams = ({
export const searchParamsToObject = (
sp: URLSearchParams | ReadonlyURLSearchParams,
-): Record => {
- const newObj: Record = {};
+): Record => {
+ const newObj: Record = {};
for (const key of Array.from(sp.keys())) {
- const value = sp.get(key);
- if (value === null) continue;
- newObj[key] = value;
+ const values = sp.getAll(key);
+
+ if (values.length > 1) {
+ newObj[key] = values;
+ } else {
+ const value = values.at(0);
+ if (typeof value === "undefined") continue;
+ newObj[key] = value;
+ }
}
return newObj;
diff --git a/tooling/eslint/base.js b/tooling/eslint/base.js
index bb3e729..fb04d2d 100644
--- a/tooling/eslint/base.js
+++ b/tooling/eslint/base.js
@@ -22,10 +22,6 @@ const config = {
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
- "@typescript-eslint/consistent-type-imports": [
- "warn",
- { prefer: "type-imports", fixStyle: "separate-type-imports" },
- ],
"@typescript-eslint/no-misused-promises": [
2,
{ checksVoidReturn: { attributes: false } },