Skip to content

Fix circular TypeScript type error when arrow function route components access matches#15163

Open
Zelys-DFKH wants to merge 1 commit into
remix-run:mainfrom
Zelys-DFKH:fix/12499-typegen-arrow-function-matches-circular
Open

Fix circular TypeScript type error when arrow function route components access matches#15163
Zelys-DFKH wants to merge 1 commit into
remix-run:mainfrom
Zelys-DFKH:fix/12499-typegen-arrow-function-matches-circular

Conversation

@Zelys-DFKH
Copy link
Copy Markdown
Contributor

Fixes #12499 — TypeScript error when accessing matches in an arrow function route component.

What

Arrow function route components that destructure matches from Route.ComponentProps get a TypeScript circular type error. The same annotation on a function declaration works fine.

Why it fails

The generated +types/*.ts builds a Matches tuple where the current route's entry is { id, module: typeof import("./self") }. When TypeScript resolves ComponentProps["matches"], it verifies the module field is assignable to RouteModule. That check evaluates ["default"] — the route component itself. For arrow functions, TypeScript needs to evaluate the parameter type annotation to determine the function's type, which requires resolving Route.ComponentProps, forming a cycle.

Function declarations skip it because TypeScript reads their signature from the declaration without evaluating parameter types.

Fix

For the current route's entry in the Matches tuple, typegen now emits { id, loaderData: Info["loaderData"] } instead of { id, module: typeof import("./self") }. Info["loaderData"] is computed via GetLoaderData, which only accesses ["loader"] and ["clientLoader"], not ["default"], so the cycle never forms.

This required adding MatchInfoWithLoaderData (with module?: never to keep the MatchData<T> discriminant disjoint from MatchInfo) and corresponding updates to Match<T>, Matches<T>, MetaMatch<T>, and MetaMatches<T> to accept AnyMatchInfo = MatchInfo | MatchInfoWithLoaderData. The loaderData type is identical to what the module path produced before; only the resolution path changes.

Closes #12499

…ts access matches

Arrow function route components that destructure `matches` from
`Route.ComponentProps` get a circular type error. The generated
`Matches` tuple contained `{ module: typeof import('./self') }` for the
current route's entry. TypeScript's `RouteModule` assignability check
evaluates `["default"]`, which for arrow functions requires resolving the
parameter type annotation — looping back to `Route.ComponentProps`.

Fix: typegen now emits `{ id, loaderData: Info["loaderData"] }` for the
self-entry instead of `{ id, module: typeof import('./self') }`.
`Info["loaderData"]` is computed via `GetLoaderData`, which only accesses
`["loader"]` and `["clientLoader"]`, not `["default"]`, so the cycle
never forms. This required adding `MatchInfoWithLoaderData` (with
`module?: never` for a disjoint discriminant) alongside `MatchInfo`, plus
corresponding updates to `Match<T>`, `Matches<T>`, `MetaMatch<T>`, and
`MetaMatches<T>` to accept `AnyMatchInfo`.

Fixes remix-run#12499
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

✅ Change File Found

A change file file exists in this PR. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Accessing matches causes TS error on route component declared as an arrow function which uses typegen Route type

1 participant