Skip to content

Commit 0516c38

Browse files
authored
add useRouter from next/navigation (#201)
1 parent 0956f4f commit 0516c38

15 files changed

Lines changed: 393 additions & 51 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,21 @@
1717
</Link>;
1818
```
1919

20-
- Add `RouteLiteral` type. This type represents a string that confirmed to be a validated application route and can be passed to `Link` or `useRouter`. This is a TypeScript branded type.
20+
- Add `RouteLiteral` type. This type represents a string that has been confirmed to be a validated application route and can be passed to `Link` or `useRouter`. This is a TypeScript branded type.
2121

2222
```ts
2323
import { RouteLiteral } from "nextjs-routes";
2424
```
2525

26-
- Refine types for `usePathname` and `useParams` from `"next/navigation"` to use `nextjs-routes` generated types.
26+
`route` returns a `RouteLiteral`. If you construct a route string you can cast it to a `RouteLiteral` so that `Link` and `useRouter` will accept it:
27+
28+
```
29+
const myRoute = `/foos/${foo}` as RouteLiteral
30+
```
31+
32+
In general, prefer using the `route` helper to generate routes.
33+
34+
- Refine types for `usePathname`, `useRouter` and `useParams` from `"next/navigation"` to use `nextjs-routes` generated types.
2735

2836
- Fix generated routes when using [parallel-routes](https://nextjs.org/docs/app/building-your-application/routing/parallel-routes) and [intercepting-routes](https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes).
2937

examples/app/@types/nextjs-routes.d.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ declare module "nextjs-routes" {
3030
[key: string]: string | string[] | undefined;
3131
};
3232

33-
export type RoutedQuery<P extends Route["pathname"]> = Extract<
33+
export type RoutedQuery<P extends Route["pathname"] = Route["pathname"]> = Extract<
3434
Route,
3535
{ pathname: P }
3636
>["query"];
@@ -170,7 +170,10 @@ declare module "next/router" {
170170
// prettier-ignore
171171
declare module "next/navigation" {
172172
export * from "next/dist/client/components/navigation";
173-
import type { RoutedQuery, RouteLiteral } from "nextjs-routes";
173+
import type { Route, RouteLiteral, RoutedQuery } from "nextjs-routes";
174+
import type { AppRouterInstance as NextAppRouterInstance, NavigateOptions, PrefetchOptions } from "next/dist/shared/lib/app-router-context.shared-runtime";
175+
176+
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
174177

175178
/**
176179
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
@@ -189,7 +192,33 @@ declare module "next/navigation" {
189192
*
190193
* Read more: [Next.js Docs: `usePathname`](https://nextjs.org/docs/app/api-reference/functions/use-pathname)
191194
*/
192-
export function usePathname(): RouteLiteral;
195+
export const usePathname = () => RouteLiteral;
196+
197+
type AppRouterInstance = Omit<NextAppRouterInstance, 'push' | 'replace' | 'href'> & {
198+
push(href: StaticRoute | RouteLiteral, options?: NavigateOptions): void;
199+
replace(href: StaticRoute | RouteLiteral, options?: NavigateOptions): void;
200+
prefetch(href: StaticRoute | RouteLiteral, options?: PrefetchOptions): void;
201+
}
202+
203+
/**
204+
*
205+
* This hook allows you to programmatically change routes inside [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components).
206+
*
207+
* @example
208+
* ```ts
209+
* "use client"
210+
* import { useRouter } from 'next/navigation'
211+
*
212+
* export default function Page() {
213+
* const router = useRouter()
214+
* // ...
215+
* router.push('/dashboard') // Navigate to /dashboard
216+
* }
217+
* ```
218+
*
219+
* Read more: [Next.js Docs: `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router)
220+
*/
221+
export function useRouter(): AppRouterInstance;
193222

194223
/**
195224
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
@@ -208,5 +237,5 @@ declare module "next/navigation" {
208237
*
209238
* Read more: [Next.js Docs: `useParams`](https://nextjs.org/docs/app/api-reference/functions/use-params)
210239
*/
211-
export function useParams<Pathname extends Route["pathname"] = Route["pathname"]>(): RoutedQuery<Pathname>;
240+
export const useParams = <Pathname extends Route["pathname"] = Route["pathname"]>() => RoutedQuery<Pathname>;
212241
}

examples/app/src/app/client.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default function Client() {
99
const router = useRouter();
1010

1111
return (
12-
<button type="button" onClick={() => router.push("/tate")}>
12+
<button type="button" onClick={() => router.push("/")}>
1313
Dashboard
1414
</button>
1515
);

examples/cjs/types/nextjs-routes.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ declare module "nextjs-routes" {
3030
[key: string]: string | string[] | undefined;
3131
};
3232

33-
export type RoutedQuery<P extends Route["pathname"]> = Extract<
33+
export type RoutedQuery<P extends Route["pathname"] = Route["pathname"]> = Extract<
3434
Route,
3535
{ pathname: P }
3636
>["query"];

examples/intl/@types/nextjs-routes.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ declare module "nextjs-routes" {
3030
[key: string]: string | string[] | undefined;
3131
};
3232

33-
export type RoutedQuery<P extends Route["pathname"]> = Extract<
33+
export type RoutedQuery<P extends Route["pathname"] = Route["pathname"]> = Extract<
3434
Route,
3535
{ pathname: P }
3636
>["query"];

examples/typescript/types/nextjs-routes.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ declare module "nextjs-routes" {
3232
[key: string]: string | string[] | undefined;
3333
};
3434

35-
export type RoutedQuery<P extends Route["pathname"]> = Extract<
35+
export type RoutedQuery<P extends Route["pathname"] = Route["pathname"]> = Extract<
3636
Route,
3737
{ pathname: P }
3838
>["query"];

packages/e2e/@types/nextjs-routes.d.ts

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ declare module "nextjs-routes" {
1313
export type Route =
1414
| StaticRoute<"/">
1515
| DynamicRoute<"/[...slug]", { "slug": string[] }>
16+
| DynamicRoute<"/bars/[bar]", { "bar": string }>
1617
| DynamicRoute<"/foos/[foo]", { "foo": string }>;
1718

1819
interface StaticRoute<Pathname> {
@@ -31,7 +32,7 @@ declare module "nextjs-routes" {
3132
[key: string]: string | string[] | undefined;
3233
};
3334

34-
export type RoutedQuery<P extends Route["pathname"]> = Extract<
35+
export type RoutedQuery<P extends Route["pathname"] = Route["pathname"]> = Extract<
3536
Route,
3637
{ pathname: P }
3738
>["query"];
@@ -82,14 +83,14 @@ declare module "nextjs-routes" {
8283

8384
// prettier-ignore
8485
declare module "next/link" {
85-
import type { Route } from "nextjs-routes";;
86+
import type { Route, RouteLiteral } from "nextjs-routes";;
8687
import type { LinkProps as NextLinkProps } from "next/dist/client/link";
8788
import type React from "react";
8889

8990
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
9091

9192
export type LinkProps = Omit<NextLinkProps, "href" | "locale"> & {
92-
href: Route | StaticRoute | Omit<Route, "pathname">;
93+
href: Route | StaticRoute | Omit<Route, "pathname"> | RouteLiteral;
9394
locale?: false;
9495
}
9596

@@ -167,3 +168,76 @@ declare module "next/router" {
167168

168169
export function useRouter<P extends Route["pathname"]>(): NextRouter<P>;
169170
}
171+
172+
// prettier-ignore
173+
declare module "next/navigation" {
174+
export * from "next/dist/client/components/navigation";
175+
import type { Route, RouteLiteral, RoutedQuery } from "nextjs-routes";
176+
import type { AppRouterInstance as NextAppRouterInstance, NavigateOptions, PrefetchOptions } from "next/dist/shared/lib/app-router-context.shared-runtime";
177+
178+
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
179+
180+
/**
181+
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
182+
* that lets you read the current URL's pathname.
183+
*
184+
* @example
185+
* ```ts
186+
* "use client"
187+
* import { usePathname } from 'next/navigation'
188+
*
189+
* export default function Page() {
190+
* const pathname = usePathname() // returns "/dashboard" on /dashboard?foo=bar
191+
* // ...
192+
* }
193+
* ```
194+
*
195+
* Read more: [Next.js Docs: `usePathname`](https://nextjs.org/docs/app/api-reference/functions/use-pathname)
196+
*/
197+
export const usePathname = () => RouteLiteral;
198+
199+
type AppRouterInstance = Omit<NextAppRouterInstance, 'push' | 'replace' | 'href'> & {
200+
push(href: StaticRoute | RouteLiteral, options?: NavigateOptions): void;
201+
replace(href: StaticRoute | RouteLiteral, options?: NavigateOptions): void;
202+
prefetch(href: StaticRoute | RouteLiteral, options?: PrefetchOptions): void;
203+
}
204+
205+
/**
206+
*
207+
* This hook allows you to programmatically change routes inside [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components).
208+
*
209+
* @example
210+
* ```ts
211+
* "use client"
212+
* import { useRouter } from 'next/navigation'
213+
*
214+
* export default function Page() {
215+
* const router = useRouter()
216+
* // ...
217+
* router.push('/dashboard') // Navigate to /dashboard
218+
* }
219+
* ```
220+
*
221+
* Read more: [Next.js Docs: `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router)
222+
*/
223+
export function useRouter(): AppRouterInstance;
224+
225+
/**
226+
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
227+
* that lets you read a route's dynamic params filled in by the current URL.
228+
*
229+
* @example
230+
* ```ts
231+
* "use client"
232+
* import { useParams } from 'next/navigation'
233+
*
234+
* export default function Page() {
235+
* // on /dashboard/[team] where pathname is /dashboard/nextjs
236+
* const { team } = useParams() // team === "nextjs"
237+
* }
238+
* ```
239+
*
240+
* Read more: [Next.js Docs: `useParams`](https://nextjs.org/docs/app/api-reference/functions/use-params)
241+
*/
242+
export const useParams = <Pathname extends Route["pathname"] = Route["pathname"]>() => RoutedQuery<Pathname>;
243+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default () => <div>Bar</div>;

packages/e2e/app/layout.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default function RootLayout({
2+
children,
3+
}: {
4+
children: React.ReactNode;
5+
}) {
6+
return (
7+
<html>
8+
<head />
9+
<body>{children}</body>
10+
</html>
11+
);
12+
}

packages/e2e/next-env.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3+
/// <reference types="next/navigation-types/compat/navigation" />
34

45
// NOTE: This file should not be edited
5-
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
6+
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

0 commit comments

Comments
 (0)