diff --git a/.changeset/khaki-dingos-kiss.md b/.changeset/khaki-dingos-kiss.md new file mode 100644 index 0000000000..0890188dc8 --- /dev/null +++ b/.changeset/khaki-dingos-kiss.md @@ -0,0 +1,22 @@ +--- +"react-router": patch +--- + +useRoute: return type-safe `handle` + +For example: + +```ts +// app/routes/admin.tsx +const handle = { hello: "world" }; +``` + +```ts +// app/routes/some-other-route.tsx +export default function Component() { + const admin = useRoute("routes/admin"); + if (!admin) throw new Error("Not nested within 'routes/admin'"); + console.log(admin.handle); + // ^? { hello: string } +} +``` diff --git a/integration/use-route-test.ts b/integration/use-route-test.ts index 44b3bca2cb..14515bb1db 100644 --- a/integration/use-route-test.ts +++ b/integration/use-route-test.ts @@ -27,6 +27,7 @@ test.use({ "app/root.tsx": tsx` import { Outlet } from "react-router" + export const handle = { rootHandle: "root/handle" } export const loader = () => ({ rootLoader: "root/loader" }) export const action = () => ({ rootAction: "root/action" }) @@ -42,6 +43,7 @@ test.use({ "app/routes/parent.tsx": tsx` import { Outlet } from "react-router" + export const handle = { parentHandle: "parent/handle" } export const loader = () => ({ parentLoader: "parent/loader" }) export const action = () => ({ parentAction: "parent/action" }) @@ -59,21 +61,38 @@ test.use({ import type { Expect, Equal } from "../expect-type" + export const handle = { currentHandle: "current/handle" } export const loader = () => ({ currentLoader: "current/loader" }) export const action = () => ({ currentAction: "current/action" }) export default function Component() { const current = useRoute() - type Test1 = Expect> + type Test1 = Expect> const root = useRoute("root") - type Test2 = Expect> + type Test2 = Expect> const parent = useRoute("routes/parent") - type Test3 = Expect> + type Test3 = Expect> const other = useRoute("routes/other") - type Test4 = Expect> + type Test4 = Expect> return ( <> @@ -87,6 +106,7 @@ test.use({ } `, "app/routes/other.tsx": tsx` + export const handle = { otherHandle: "other/handle" } export const loader = () => ({ otherLoader: "other/loader" }) export const action = () => ({ otherAction: "other/action" }) diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index 259f75ace4..07035f3a5c 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -1855,6 +1855,9 @@ type UseRouteResult = never; type UseRoute = { + handle: RouteId extends keyof RouteModules + ? RouteModules[RouteId]["handle"] + : unknown; loaderData: RouteId extends keyof RouteModules ? GetLoaderData | undefined : unknown; @@ -1871,11 +1874,12 @@ export function useRoute( ); const id: keyof RouteModules = args[0] ?? currentRouteId; - const state = useDataRouterState(DataRouterStateHook.UseRouteLoaderData); + const state = useDataRouterState(DataRouterStateHook.UseRoute); const route = state.matches.find(({ route }) => route.id === id); if (route === undefined) return undefined as UseRouteResult; return { + handle: route.route.handle, loaderData: state.loaderData[id], actionData: state.actionData?.[id], } as UseRouteResult;