Skip to content

Commit 1a6f7d1

Browse files
committed
updated changeset
1 parent 1011e75 commit 1a6f7d1

File tree

1 file changed

+66
-43
lines changed

1 file changed

+66
-43
lines changed

.changeset/six-lobsters-think.md

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,77 +5,100 @@
55

66
New (unstable) `useRoute` hook for accessing data from specific routes
77

8-
`useRouteLoaderData` has many shortcomings:
8+
For example, let's say you have an `admin` route somewhere in your app and you want any child routes of `admin` to all have access to the `loaderData` and `actionData` from `admin.`
99

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
10+
```tsx
1711
// app/routes/admin.tsx
18-
export const loader = () => ({ message: "Hello, loader!" });
12+
import { Outlet } from "react-router";
1913

20-
export const action = () => ({ message: "Hello, action!" });
21-
```
14+
export const loader = () => ({ message: "Hello, loader!" });
2215

23-
```ts
24-
import { type loader } from "../routes/admin";
16+
export const action = () => ({ count: 1 });
2517

26-
export function Widget() {
27-
const loaderData = useRouteLoaderData<typeof loader>("routes/admin");
28-
// ...
18+
export default function Component() {
19+
return (
20+
<div>
21+
{/* ... */}
22+
<Outlet />
23+
{/* ... */}
24+
</div>
25+
);
2926
}
3027
```
3128

32-
With `useRoute`, all of these concerns have been fixed:
29+
You might even want to create a reusable widget that all of the routes nested under `admin` could use:
3330

34-
```ts
31+
```tsx
3532
import { unstable_useRoute as useRoute } from "react-router";
3633

37-
export function Widget() {
38-
const admin = useRoute("routes/admin");
39-
console.log(admin?.loaderData?.message);
40-
console.log(admin?.actionData?.message);
41-
// ...
34+
export function AdminWidget() {
35+
// How to get `message` and `count` from `admin` route?
4236
}
4337
```
4438

45-
Note: `useRoute` returns `undefined` if the route is not part of the current page.
39+
In framework mode, `useRoute` knows all your app's routes and gives you TS errors when invalid route IDs are passed in:
40+
41+
```tsx
42+
export function AdminWidget() {
43+
const admin = useRoute("routes/dmin");
44+
// ^^^^^^^^^^^
45+
}
46+
```
4647

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+
`useRoute` returns `undefined` if the route is not part of the current page:
4849

49-
```ts
50-
export function Widget() {
51-
const root = useRoute("root");
52-
console.log(root.loaderData?.message, root.actionData?.message);
53-
// ...
50+
```tsx
51+
export function AdminWidget() {
52+
const admin = useRoute("routes/admin");
53+
if (!admin) {
54+
throw new Error(`AdminWidget used outside of "routes/admin"`);
55+
}
5456
}
5557
```
5658

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+
Note: the `root` route is the exception since it is guaranteed to be part of the current page.
60+
As a result, `useRoute` never returns `undefined` for `root`.
5961

60-
```ts
61-
export function ErrorBoundary() {
62+
`loaderData` and `actionData` are marked as optional since they could be accessed before the `action` is triggered or after the `loader` threw an error:
63+
64+
```tsx
65+
export function AdminWidget() {
6266
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+
if (!admin) {
68+
throw new Error(`AdminWidget used outside of "routes/admin"`);
69+
}
70+
const { loaderData, actionData } = admin;
71+
console.log(loaderData);
72+
// ^? { message: string } | undefined
73+
console.log(actionData);
74+
// ^? { count: number } | undefined
6775
}
6876
```
6977

70-
In an effort to consolidate on fewer, more intuitive hooks, `useRoute` can be called without arguments as a replacement for `useLoaderData` and `useActionData`:
78+
If instead of a specific route, you wanted access to the _current_ route's `loaderData` and `actionData`, you can call `useRoute` without arguments:
7179

72-
```ts
73-
export function Widget() {
80+
```tsx
81+
export function AdminWidget() {
7482
const currentRoute = useRoute();
7583
currentRoute.loaderData;
7684
currentRoute.actionData;
7785
}
7886
```
7987

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`).
88+
This usage is equivalent to calling `useLoaderData` and `useActionData`, but consolidates all route data access into one hook: `useRoute`.
89+
90+
Note: when calling `useRoute()` (without a route ID), TS has no way to know which route is the current route.
91+
As a result, `loaderData` and `actionData` are typed as `unknown`.
92+
If you want more type-safety, you can either narrow the type yourself with something like `zod` or you can refactor your app to pass down typed props to your `AdminWidget`:
93+
94+
```tsx
95+
export function AdminWidget({
96+
message,
97+
count,
98+
}: {
99+
message: string;
100+
count: number;
101+
}) {
102+
/* ... */
103+
}
104+
```

0 commit comments

Comments
 (0)