tatethurston / nextjs-routes

Type safe routing for Next.js
MIT License
571 stars 23 forks source link

Change locale and stay on the same route #96

Closed groomain closed 1 year ago

groomain commented 1 year ago

Hi,

I tried to change the locale and stay on the same route by following nextjs exemple.

const router = useRouter()
const { pathname, asPath, query } = router
// change just the locale and maintain all other route information including href's query
router.push({ pathname, query }, asPath, { locale: nextLocale })

Unfortunately I have a typescript error with nextjs-routes.

I fixed it with an assertion any but maybe you have a better solution?

Thanks for your help!

tatethurston commented 1 year ago

@groomain could you share your next.config.js and the TypeScript error you're encountering?

groomain commented 1 year ago
{
     reactStrictMode: true,
     defaultLocale: "fr",
     locales: ["fr", "en"],
}
tatethurston commented 1 year ago

@groomain Thanks, and the TypeScript error?

groomain commented 1 year ago

in router.push({ pathname, query }, asPath, { locale: nextLocale }) pathname need to be one of the route. but pathname type in router is all the routes

tatethurston commented 1 year ago

Could you paste the TypeScript error exactly as output by the compiler?

groomain commented 1 year ago
No overload matches this call.
  Overload 1 of 3, '(url: Route, as?: string | undefined, options?: TransitionOptions | undefined): Promise<boolean>', gave the following error.
    Argument of type '{ pathname: "/route-a" | "route-b" | more ...>; }' is not assignable to parameter of type 'Route'.
      Type '{ pathname: "/route-a" | "route-b" | more ...>; }' is not assignable to type '{ pathname: "/route-a"; query?: { [key: string]: string | string[] | undefined; } | undefined; }'.
        Types of property 'pathname' are incompatible.
          Type '"/route-a" | "route-b"' is not assignable to type '"/route-a"'.
            Type '"/a-word"' is not assignable to type '"/route-a"'.
  Overload 2 of 3, '(url: "/route-a" | "route-b", as?: string | undefined, options?: TransitionOptions | undefined): Promise<...>', gave the following error.
    Argument of type '{ pathname: "/route-a" | "route-b" | more ...>; }' is not assignable to parameter of type '"/route-a" | "route-b"'.
  Overload 3 of 3, '(url: { query: { [key: string]: string | string[] | undefined; }; }, as?: string | undefined, options?: TransitionOptions | undefined): Promise<boolean>', gave the following error.
    Argument of type '{ pathname: "/route-a" | "route-b" | more ...>; }' is not assignable to parameter of type '{ query: { [key: string]: string | string[] | undefined; }; }'.
      Object literal may only specify known properties, and 'pathname' does not exist in type '{ query: { [key: string]: string | string[] | undefined; }; }'.
tatethurston commented 1 year ago

Thanks @groomain, this looks like a bug. Is this project publicly available in GitHub? If not, could you let me know:

Could you also paste your generated nextjs-routes.d.ts?

Update: I didn't realize that you renamed your pathnames, so I mistakenly thought nextjs-routes had produced a malformed pathname

groomain commented 1 year ago

if you're using the pages or app directory? the version of nextjs-routes you're using

no sorry it's a private project it's used with page folder and the last version of nextjs-routes

tatethurston commented 1 year ago

@groomain I'm not able to reproduce this issue. Here's a working example using the code you've shared: https://stackblitz.com/edit/nextjs-8akbqz?file=next.config.js,package.json,pages%2Froute-a.tsx,pages%2F_app.tsx

Did you define your locales correctly? From your shared code it looks like you're using locales as a top level key in your next.config.js, but next requires that it's nested under i18n: https://nextjs.org/docs/advanced-features/i18n-routing

https://stackblitz.com/edit/nextjs-8akbqz?file=next.config.js,package.json,pages%2Froute-a.tsx,pages%2F_app.tsx

groomain commented 1 year ago

Hi, I think it's because I have a "catching all" page. [...slug].tsx

Can you try this one? I've reproduced the error here. https://stackblitz.com/edit/nextjs-zmkzrf?file=pages/_app.ts

tatethurston commented 1 year ago

Ah good find. The issue here is that once you destructure pathname and query, there isn't a way to let TypeScript know that the two fit together: TS will treat them as two separate unions:

// given this router
{ pathname: '/foos/[foo]', query: { foo: string } } | { pathname: '/bars/[bar]', query: { bar: string } }

// pathname destructured becomes this:
'/foos/[foo]' |  '/bars/[bar]' 

// query destructured becomes this:
{ foo: string } | { bar: string }

// this correctly error, and TS can't currently track that pathname and query came from the same allowed type so it checks that all unions are allowed
{ pathname: '/foos/[foo]', query: { bar: string } }

If you want to supply the pathname and query, and you know the route you're calling useRouter on, you can supply a generic argument to useRouter to prevent this issue: https://github.com/tatethurston/nextjs-routes#query

Example: https://stackblitz.com/edit/nextjs-t5pjth?file=pages%2Froute-a.tsx,pages%2F[...slug].tsx

tatethurston commented 1 year ago

On v1.0.1 you can do the following, because pathname and query will be pulled off router correctly, without the TypeScript destructuring tracking issue discussed above:

router.push(router, undefined, { locale: "fr" })

or for more intent clarity:

// nextjs will load the current route if pathname is omitted
router.push({ query: router.query }, undefined, { locale: "fr" })
groomain commented 1 year ago

Thanks!

97

fixed!