|
| 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