Jean-Francois Gagnon
Jean-Francois Gagnon

Reputation: 3290

NextJS - Appending a query param to a dynamic route

In my NextJS app, I have a language selector that's visible on every page. When I select a new language, I just want to replace the current URL by appending a query param lang=en to it.

Here's the function that replaces the URL:

const changeLanguage = (lang: LanguageID) => {
    replace({
      pathname,
      query: { ...query, lang },
    });
  };

In this example, replace, query and pathname are coming from the next router.

Now, everything works for static routes, but I'm unable to make it work for dynamic routes. For example, I have the following folder structure:

pages
|_customers
|__index.tsx
|__[customerId].tsx

If I'm on http://localhost/customers and I change my language to English, the URL changes to http://localhost/customers?lang=en which is what I want. However, if I'm on http://localhost/customer/1 and I change my language to English, the URL changes to http://localhost/customers/[customerId]?customerId=1&lang=en, instead of the URL I'm expecting http://localhost/customers/1?lang=en.

Now, I know that I could use asPath on the router, and reconstruct the query string object by appending lang to it, but I feel that it's something that should be build into Next. Also, I know it could be easily done with vanilla JS, but it's not the point here.

Am I missing something? Is there an easier way to append query params to a dynamic route without doing a server-side re-rendering?

Thanks

Upvotes: 60

Views: 164617

Answers (13)

FDisk
FDisk

Reputation: 9426

If we want to have this as a link - use it like so:

const router = useRouter();


<Link
    href={{
        pathname: router.pathname,
        query: { ...router.query, lang },
    }}
    passHref
    shallow
    replace
></Link>

Upvotes: 22

Zlatko Iliev
Zlatko Iliev

Reputation: 47

I see the top answer already resolved this, just wanted to add, if you have for example table filters or page result filters, do not do ‘router.replace()’. Always push to preserve the browser history. This way the user can go back with browser arrows and query params will still work.

Upvotes: 0

Renny Ren
Renny Ren

Reputation: 107

If you are using App Router, The query object has been removed and is replaced by useSearchParams(), you need to update searchParams:

export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()
 
  // Get a new searchParams string by merging the current
  // searchParams with a provided key/value pair
  const createQueryString = useCallback(
    (name: string, value: string) => {
      const params = new URLSearchParams(searchParams.toString())
      params.set(name, value)
 
      return params.toString()
    },
    [searchParams]
  )
 
  return (
    <>
      {/* using useRouter */}
      <button
        onClick={() => {
          // <pathname>?sort=asc
          router.push(pathname + '?' + createQueryString('sort', 'asc'))
        }}
      >
        ASC
      </button>
 
      {/* using <Link> */}
      <Link
        href={
          // <pathname>?sort=desc
          pathname + '?' + createQueryString('sort', 'desc')
        }
      >
        DESC
      </Link>
    </>
  )
} 

Upvotes: 2

mohammadreza hayati
mohammadreza hayati

Reputation: 83

for nextjs 13 I just found this,

import { useRouter } from 'next/navigation';
router.push(`?page=${page}`)

you just need to handle every query you want

Upvotes: 4

NeoPrint3D
NeoPrint3D

Reputation: 1

The best solution I could come up with is that doesn't cause the Unknown key passed via urlObject into url.format is to do this pattern router.query.plan = plan.title; router.push({ query: router.query });

Upvotes: 0

Iman
Iman

Reputation: 469

In latest version, Next 13, some of the functionality moved to other hooks, which query and path are some of them. You can use useSearchParams to get query and usePathname instead of pathname. By the time I am writing this answer, it does not have a stable version and you can find the beta documents here: https://beta.nextjs.org/docs/api-reference/use-router

Upvotes: 2

Yilmaz
Yilmaz

Reputation: 49351

  let queryParams;
  if (typeof window !== "undefined") {  
    // The search property returns the querystring part of a URL, including the question mark (?).
    queryParams = new URLSearchParams(window.location.search);
    // quaeryParams object has nice methods
    // console.log("window.location.search", queryParams);
    // console.log("query get", queryParams.get("location"));
  }

inside changeLanguage,

const changeLanguage = (lang: LanguageID) => {
    if (queryParams.has("lang")) {
      queryParams.set("lang", lang);
    } else {
      // otherwise append
      queryParams.append("lang", lang);
    }
    router.replace({
      search: queryParams.toString(),
    });
  };

Upvotes: -1

Armin Sadeghi
Armin Sadeghi

Reputation: 845

An alternative approach when you have dynamic routing in Next.js, and want to do a shallow adjustment of the route to reflect updated query params, is to try:

const router = useRouter()
const url = {
      pathname: router.pathname,
      query: { ...router.query, page: 2 }
    }
router.push(url, undefined, { shallow: true })

This will retreive the current path (router.pathname) and query (router.query) details, and merge them in along with your new page query param. If your forget to merge in the existing query params you might see an error like:

The provided href value is missing query values to be interpolated properly

Upvotes: 10

Elvis Ohemeng Gyaase
Elvis Ohemeng Gyaase

Reputation: 99

If anyone is still looking the answer ,for Next,js ^11.1.2.I hope this helps you out.Use

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

Upvotes: 2

Maciej Sikora
Maciej Sikora

Reputation: 20152

The solution which doesn't need to send the whole previous route, as replace just replaces what we need to replace, so query params:

const router = useRouter();
router.replace({
   query: { ...router.query, key: value },
});

Upvotes: 36

albert
albert

Reputation: 192

I tried adding my param to the route query and pushing the router itself, as mentioned here, it works, but I got a lot of warnings: enter image description here

So, I then pushed to / and passed my query params as following:

const router = useRouter();
router.push({ href: '/', query: { myQueryParam: value  } });

I hope that works for you too.

Upvotes: 10

Cong Nguyen
Cong Nguyen

Reputation: 3445

Just add more param to current router then push itself

const router = useRouter();
router.query.NEWPARAMS = "VALUE"
router.push(router)

Upvotes: 72

Jean-Francois Gagnon
Jean-Francois Gagnon

Reputation: 3290

I ended up using the solution that I wanted to avoid in the first place, which was to play with the asPath value. Atleast, there's no server-side re-rendering being done since the path is the same.

Here's my updated changeLanguage function (stringifyUrl is coming from the query-string package)

  const changeLanguage = (lang: LanguageID) => {
    const newPathname = stringifyUrl({ url: pathname, query: { ...query, lang } });
    const newAsPath = stringifyUrl({ url: asPath, query: { lang } });
    replace(newPathname, newAsPath);
  };

Upvotes: 2

Related Questions