Next.js Discord

Discord Forum

Forms with GET Query Parameters

Unanswered
Masai Lion posted this in #help-forum
Open in Discord
Masai LionOP
I've got a form with various fields used to build out the search parameters to query a DB. To preserve the state, I was imagining using query parameters like
mysite.com/?search=somestring&filter=xyz...


Using next.js with react-hook-form, I'm struggling with how to actually submit in a way that updates the URL. In old fashioned forms, the URL would automatically be updated with the fields names/values on submit, but I'm not clear how to do this in the "NEXT.js" way.

I've tried a few approaches so far:

1. in the form onSubmit, building the URL and then doing a router.push. Doing this seems to bypass the way next.js is supposed to work. For example, my loading.tsx file doesn't display while the page refreshes.

2. I tried creating a bunch of hidden inputs that store the various search parameters (e.g. search, filter, sort, etc). This way I can use fancy form elements that can handle arrays, etc, but the hidden inputs keep the URL friendly values. Then I just let a standard html <form method="get" /> do the default submit. This kind of works, but it means I'm basically fighting react-hook-form and it doesn't feel right.

3. A third option is to avoid using the URL to maintain the state and instead use session storage. There are some downsides to this I was hoping to avoid. One example is the simple ability to copy/paste a URL or bookmark it.

Am I thinking about this wrong?

10 Replies

@Masai Lion I've got a form with various fields used to build out the search parameters to query a DB. To preserve the state, I was imagining using query parameters like `mysite.com/?search=somestring&filter=xyz...` Using next.js with react-hook-form, I'm struggling with how to actually submit in a way that updates the URL. In old fashioned forms, the URL would automatically be updated with the fields names/values on submit, but I'm not clear how to do this in the "NEXT.js" way. I've tried a few approaches so far: 1. in the form `onSubmit`, building the URL and then doing a router.push. Doing this seems to bypass the way next.js is supposed to work. For example, my loading.tsx file doesn't display while the page refreshes. 2. I tried creating a bunch of hidden inputs that store the various search parameters (e.g. search, filter, sort, etc). This way I can use fancy form elements that can handle arrays, etc, but the hidden inputs keep the URL friendly values. Then I just let a standard html `<form method="get" />` do the default submit. This kind of works, but it means I'm basically fighting `react-hook-form` and it doesn't feel right. 3. A third option is to avoid using the URL to maintain the state and instead use session storage. There are some downsides to this I was hoping to avoid. One example is the simple ability to copy/paste a URL or bookmark it. Am I thinking about this wrong?
heya welcome, here's my take on it:

in my opinion, the old fashioned way that you mentioned was "the" way on MPA frameworks. but in NextJS, this is only partly true; it's a kinda weird mix of SPA and MPA that takes the good of both worlds.

i wouldn't recommend refreshing for every form submits, that sounds like trying to work around nextjs' SPA and treat it as an MPA framework, when it can actually do both. here's what i'd do in your situation:

- do the fetching on the client-side; highly recommend tRPC or react-query for doing this.
- use useSearchParams to treat the GET parameters as a "state manager". this is to make it as if the GET parameters are controlling the server; but in reality it does not, it's consumed by the client and fed into tRPC or react-query
- in order to update the search params, use router.push("/" = new URLSearchParams({ search: ..., ... })), and do that on the onSubmit of a form controlled by react-hook-form
with this, you are able to have:

- copy-able URL to be shared
- have the data be loaded on the client as if it's an SPA, making it look super quick
cool right, MPA and SPA combined lol
i'll try making an example, hold on a sec
Masai LionOP
Thanks, that makes sense. I was trying to do more on the server which seemed like the new and improved old way, but it gets complicated when transitioning from server to a client based SPA experience.
okay this took a bit more time than i thought :lolsob:
"use client";

import { useRouter, useSearchParams } from "next/navigation";
import { useForm } from "react-hook-form";
import { z } from "zod";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "~/components/ui/form";
import { Input } from "~/components/ui/input";
import { zodResolver } from "@hookform/resolvers/zod";
import { api } from "~/trpc/react";
import { useMemo } from "react";

const formSchema = z.object({
  search: z.string(),
});

export default function MyForm() {
  const searchParams = useSearchParams();
  const search = useMemo(
    () => searchParams.get("search"),
    [searchParams],
  );

  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
  });

  const { data: posts } = api.post.search.useQuery({
    query: search ?? undefined,
  });

  const router = useRouter();
  const onSubmit = async (data: z.infer<typeof formSchema>) => {
    router.push(`/?${new URLSearchParams({ search: data.search })}`);
  };

  return (
    <div className="flex flex-col gap-2">
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)}>
          <FormField
            control={form.control}
            name="search"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Search</FormLabel>
                <FormControl>
                  <Input placeholder="Name" className="w-full" {...field} />
                </FormControl>
                <FormDescription>Enter keywords to search</FormDescription>
                <FormMessage />
              </FormItem>
            )}
          />
        </form>
      </Form>

      <div className="flex flex-col rounded-md border border-black/10 p-2 gap-2">
        {posts?.map((post) => (
          <article className="px-3 py-1 bg-slate-200 rounded-md">
            <h2>{post}</h2>
          </article>
        ))}
      </div>
    </div>
  );
}
but hmm, i think using a debounced input would be better than using react-hook-form to do this, since you want that "instant refresh" when changing some parameters of the search, rather than clicking "search" or enter to submit
Masai LionOP
Thank you very much for putting the thought into this. I'll have to play with it a bit.
Yea, it gets a little tricky because some fields, like a checkbox make sense to trigger an update while others, like a search field, make more sense to use a submit event.

I'll have to play through this more, but I appreciate the insight regarding client vs server. It's one of the areas I've found hardest to reason through because there are tradeoffs for each approach, and for an app like mine, eventually it feels like a SPA. I wanted to be sure I wasn't missing something.
👍 good luck