ReactHustle

How to Persist NextJS Query Params using Link Component

Jasser Mark Arioste

Jasser Mark Arioste

How to Persist NextJS Query Params using Link Component

Hello! In this tutorial, you'll learn how to persist NextJS query parameters when navigating to another page. 

Introduction #

Sometimes we need to persist query params. For example, when navigating between signup and sign-in pages, we need to persist the redirectURL for a better user experience.

# if a user was redirected to signup page, but the user already has an existing account
http://localhost:3000/signup?next=http%3A%2F%2Flocalhost%3A3000%2Fsubscribe

# when the user navigates to sign-in page, the `next` query param should persist

http://localhost:3000/signin?next=http%3A%2F%2Flocalhost%3A3000%2Fsubscribe
123456

Another example would be when implementing pagination if your query params have other fields such as page_size, sort_order, sort_by, etc.


http://localhost/3000/users?page=1&page_size=10&sort_by=first_name&sort_order=asc
# navigating to page 2 should retain all the other params aside from `page`
http://localhost/3000/users?page=2&page_size=10&sort_by=first_name&sort_order=asc
1234

This is a very common pattern when developing web applications. Now, how should we implement this?

The Wrong Approach #

The wrong approach would be to build the query params using a string template or something. For example:

// pages/signup.tsx
import Link from "next/link";
import { useRouter } from "next/router";

const SignUpPage = () => {
  const router = useRouter();
  const next = router.query.next as string;
  return (
    <form>
      <h1>Sign up</h1>
      <input type="text" placeholder="username" name="username" />
      <input type="text" placeholder="password" name="password" />
      <button type="submit">Sign up</button>
      <Link href={`/signin?next=${next}`}>Already have an account</Link>
    </form>
  );
};
export default SignUpPage;
123456789101112131415161718

In line 14, we use string interpolation to navigate to the sign-in page while retaining the next query parameter. However, if there is no next query parameter, that link will have the following URL: http://localhost:3000/signin?next=undefined, and therefore you'll have to add more checks or conditions to build this URL. Imagine if you have multiple parameters like the second example above, it's going to be very hard to maintain.

The Solution #

I think the correct approach would be to create a new <Link/> component that retains the query parameters. First, create the file components/RetainQueryLink.tsx.

// components/RetainQueryLink.tsx
import Link, { LinkProps } from "next/link";
import { useRouter } from "next/router";
import { PropsWithChildren } from "react";

const RetainQueryLink = ({ href, ...props }: LinkProps & PropsWithChildren) => {
  // 1. use useRouter hook to get access to the current query params
  const router = useRouter();

  // 2. get the pathname
  const pathname = typeof href === "object" ? href.pathname : href;

  // 3. get the query from props
  const query =
    typeof href === "object" && typeof href.query === "object"
      ? href.query
      : {};

  return (
    <Link
      {...props}
      href={{
        pathname: pathname,
        // combine router.query and query props
        query: {
          ...router.query,
          ...query,
        },
      }}
    />
  );
};
export default RetainQueryLink;

123456789101112131415161718192021222324252627282930313233

Now to use this component, we can just do the following:

// pages/signup.tsx
import RetainQueryLink from "../components/RetainQueryLink";

const SignUpPage = () => {
  return (
    <form>
      <h1>Sign up</h1>
      <input type="text" placeholder="username" name="username" />
      <input type="text" placeholder="password" name="password" />
      <button type="submit">Sign up</button>
      <RetainQueryLink href={"/signup"}>
        Already have an account
      </RetainQueryLink>
    </form>
  );
};
export default SignUpPage;
1234567891011121314151617

For more complex scenarios such as tables, you can still pass query parameters as before. For example:

// pages/users.tsx
import RetainQueryLink from "../components/RetainQueryLink";

const UsersPage = () => {
  const pages = [1, 2, 3, 4, 5];

  return (
    <div>
      Users
      <div>
        <span>Pagination</span>
        {pages.map((page) => {
          return (
            <RetainQueryLink
              key={page}
              href={{
                query: { page },
              }}
            >
              {` ${page} `}
            </RetainQueryLink>
          );
        })}
      </div>
      <pre>Location:{window.location.href}</pre>
    </div>
  );
};
export default UsersPage;
1234567891011121314151617181920212223242526272829

In lines 16-18, we only override the query.page parameter, all other query parameters, as well as the existing route, are retained. 

Not only can you use this with the Link component but when using the router as well. For example:


const router = useRouter();
router.push({
  pathname: "/signin",
  query: router.query,
});
123456

Demo #

Here's a simple demo for the second example. Notice that only the page parameter changes. 

NextJS Retain query params demo

Full Code #

Check out the full code at my GitHub: jmarioste/next-js-persist-query-params.

Conclusion #

We were able to learn how to persist query parameters using the link component. This is an extremely useful pattern in many tricky scenarios.

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.

Credits: Image by Karl Egger from Pixabay

Share this post!

Related Posts

Disclaimer

This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle