Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/thick-snails-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
"react-router": patch
---

Fix types for `UIMatch` to reflect that the `loaderData`/`data` properties may be `undefined`

- When an `ErrorBoundary` is being rendered, not all active matches will have loader data available, since it may have been their `loader` that threw to trigger the boundary
- The `UIMatch.data` type was not correctly handing this and would always reflect the presence of data, leading to the unexpected runtime errors when an `ErrorBoundary` was rendered
- ⚠️ This may cause some type errors to show up in your code for unguarded `match.data` accesses - you should properly guard for `undefined` values in those scenarios.

```tsx
// app/root.tsx
export function loader() {
someFunctionThatThrows(); // ❌ Throws an Error
return { title: "My Title" };
}

export function Layout({ children }: { children: React.ReactNode }) {
let matches = useMatches();
let rootMatch = matches[0] as UIMatch<Awaited<ReturnType<typeof loader>>>;
// ^ rootMatch.data is incorrectly typed here, so TypeScript does not
// complain if you do the following which throws an error at runtime:
let { title } = rootMatch.data; // 💥

return <html>...</html>;
}
```
19 changes: 14 additions & 5 deletions packages/react-router/lib/router/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -923,14 +923,23 @@ export interface UIMatch<Data = unknown, Handle = unknown> {
*/
params: AgnosticRouteMatch["params"];
/**
* The return value from the matched route's loader or clientLoader
* The return value from the matched route's loader or clientLoader. This might
* be `undefined` if this route's `loader` (or a deeper route's `loader`) threw
* an error and we're currently displaying an `ErrorBoundary`.
*
* @deprecated Use `UIMatch.loaderData` instead
*/
data: Data;
/** The return value from the matched route's loader or clientLoader */
loaderData: Data;
/** The {@link https://reactrouter.com/start/framework/route-module#handle handle object} exported from the matched route module */
data: Data | undefined;
/**
* The return value from the matched route's loader or clientLoader. This might
* be `undefined` if this route's `loader` (or a deeper route's `loader`) threw
* an error and we're currently displaying an `ErrorBoundary`.
*/
loaderData: Data | undefined;
/**
* The {@link https://reactrouter.com/start/framework/route-module#handle handle object}
* exported from the matched route module
*/
handle: Handle;
}

Expand Down