diff --git a/README.md b/README.md index 96978ed..49e1997 100644 --- a/README.md +++ b/README.md @@ -1550,6 +1550,44 @@ export default function Component() { Now you can see in your DevTools that when the user hovers an anchor it will prefetch it, and when the user clicks it will do a client-side navigation. +### Generate URL strings for Link/Form + +If you use `` with search params, specially dynamic values like a pagination, you will have to create the URL with the search params manually. + +You may have something like this: + +```tsx +return Next; +``` + +Or maybe using URLSearchParams: + +```tsx +let searchParams = new URLSearchParams(); +searchParams.set("page", page + 1); +searchParams.set("search", search); +return Next; +``` + +Or even using the `useSearchParams` hook to edit the current ones: + +```tsx +let [searchParams] = useSearchParams(); +searchParams.set("page", page + 1); + +return Next; +``` + +All of these are valid solutions, but they can be a bit verbose and repetitive, and you may forget to add the `?` before the search params, the `to` function in Remix Utils lets you do this in a more concise way. + +```tsx +import { to } from "remix-utils"; + +return Next; +``` + +The `to` is going to generate a URL like `?page=2&search=hello` so you can pass it to ``, `
`, `useFetcher().load()`, `useFetcher().submit()`, or `useSubmit()()`. + ## Author - [Sergio Xalambrí](https://sergiodxa.com) diff --git a/src/common.ts b/src/common.ts index 6c9c9e9..5491ec7 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1 +1,2 @@ export * from "./common/promise"; +export * from "./common/to"; diff --git a/src/common/to.ts b/src/common/to.ts new file mode 100644 index 0000000..8ac3413 --- /dev/null +++ b/src/common/to.ts @@ -0,0 +1,23 @@ +import type { LinkProps } from "@remix-run/react"; + +type To = Partial> & { + searchParams?: ConstructorParameters["0"]; +}; + +export function to(partial: To): LinkProps["to"] { + let url = new URL("http://localhost"); + + if (partial.hostname) url.hostname = partial.hostname; + if (partial.pathname) url.pathname = partial.pathname; + if (partial.search) url.search = partial.search; + if (partial.searchParams) { + url.search = new URLSearchParams(partial.searchParams).toString(); + } + if (partial.hash) url.hash = partial.hash; + + let string = url.toString(); + + if (partial.hostname) return string; + if (partial.pathname) return string.slice("http://localhost".length); + return string.slice("http://localhost/".length); +} diff --git a/test/common/to.test.ts b/test/common/to.test.ts new file mode 100644 index 0000000..d3b46c9 --- /dev/null +++ b/test/common/to.test.ts @@ -0,0 +1,30 @@ +import { to } from "../../src"; + +describe(to.name, () => { + test("returns a string with the changed pathname", () => { + expect(to({ pathname: "/foo/bar" })).toBe("/foo/bar"); + }); + + test("returns a string with the changed search", () => { + expect(to({ search: "?foo=bar" })).toBe("?foo=bar"); + }); + + test("returns a string with the changed hash", () => { + expect(to({ hash: "#foo" })).toBe("#foo"); + }); + + test("returns a string with the changed search params", () => { + expect(to({ searchParams: { foo: "bar" } })).toBe("?foo=bar"); + }); + + test("returns a string with the changed search params (array)", () => { + expect( + to({ + searchParams: [ + ["foo", "bar"], + ["foo", "baz"], + ], + }) + ).toBe("?foo=bar&foo=baz"); + }); +});