Skip to content

Commit 1011e75

Browse files
committed
changeset
1 parent c19b79d commit 1011e75

File tree

1 file changed

+81
-0
lines changed

1 file changed

+81
-0
lines changed

.changeset/six-lobsters-think.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
"@react-router/dev": patch
3+
"react-router": patch
4+
---
5+
6+
New (unstable) `useRoute` hook for accessing data from specific routes
7+
8+
`useRouteLoaderData` has many shortcomings:
9+
10+
1. Its `routeId` arg is typed as `string`, so TS won't complain if you pass in a non-existent route ID
11+
2. Type-safety was limited to a `typeof loader` generic that required you to manually import and pass in the type for the corresponding `loader`
12+
3. Even with `typeof loader`, the types were not aware of `clientLoader`, `HydrateFallback`, and `clientLoader.hydrate = true`, which all affect the type for `loaderData`
13+
4. It is limited solely to `loader` data, but does not provide `action` data
14+
5. It introduced confusion about when to use `useLoaderData` and when to use `useRouteLoaderData`
15+
16+
```ts
17+
// app/routes/admin.tsx
18+
export const loader = () => ({ message: "Hello, loader!" });
19+
20+
export const action = () => ({ message: "Hello, action!" });
21+
```
22+
23+
```ts
24+
import { type loader } from "../routes/admin";
25+
26+
export function Widget() {
27+
const loaderData = useRouteLoaderData<typeof loader>("routes/admin");
28+
// ...
29+
}
30+
```
31+
32+
With `useRoute`, all of these concerns have been fixed:
33+
34+
```ts
35+
import { unstable_useRoute as useRoute } from "react-router";
36+
37+
export function Widget() {
38+
const admin = useRoute("routes/admin");
39+
console.log(admin?.loaderData?.message);
40+
console.log(admin?.actionData?.message);
41+
// ...
42+
}
43+
```
44+
45+
Note: `useRoute` returns `undefined` if the route is not part of the current page.
46+
47+
The `root` route is special because it is guaranteed to be part of the current page, so no need to use `?.` or any other `undefined` checks:
48+
49+
```ts
50+
export function Widget() {
51+
const root = useRoute("root");
52+
console.log(root.loaderData?.message, root.actionData?.message);
53+
// ...
54+
}
55+
```
56+
57+
You may have noticed that `loaderData` and `actionData` are marked as optional.
58+
This is intentional as there's no guarantee that `loaderData` nor `actionData` exists when called in certain contexts like within an `ErrorBoundary`:
59+
60+
```ts
61+
export function ErrorBoundary() {
62+
const admin = useRoute("routes/admin");
63+
console.log(admin?.loaderData?.message);
64+
// ^^
65+
// `loader` for itself could have thrown an error,
66+
// so you need to check if `loaderData` exists!
67+
}
68+
```
69+
70+
In an effort to consolidate on fewer, more intuitive hooks, `useRoute` can be called without arguments as a replacement for `useLoaderData` and `useActionData`:
71+
72+
```ts
73+
export function Widget() {
74+
const currentRoute = useRoute();
75+
currentRoute.loaderData;
76+
currentRoute.actionData;
77+
}
78+
```
79+
80+
Since `Widget` is a reusable component that could be within any route, we have no guarantees about the types for `loaderData` nor `actionData`.
81+
As a result, they are both typed as `unknown` and it is up to you to narrow the type to what your reusable component needs (for example, via `zod`).

0 commit comments

Comments
 (0)