Skip to content

Fix types for UIMatch to reflect data may be undefined #12206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 5, 2025
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