Skip to content

fix(dev): route arrow-function component exports typecheck correctly#15312

Open
ChihebBENCHEIKH1 wants to merge 1 commit into
remix-run:mainfrom
ChihebBENCHEIKH1:fix/rsc-server-component-arrow-typegen
Open

fix(dev): route arrow-function component exports typecheck correctly#15312
ChihebBENCHEIKH1 wants to merge 1 commit into
remix-run:mainfrom
ChihebBENCHEIKH1:fix/rsc-server-component-arrow-typegen

Conversation

@ChihebBENCHEIKH1

Copy link
Copy Markdown

Fixes #14676

Problem

Defining a route's ServerComponent (or default Component) as a const arrow function whose parameter is typed with Route.ComponentProps / Route.ServerComponentProps triggers a TypeScript circular type-alias error:

// TS2456 / TS7022
export const ServerComponent = ({}: Route.ComponentProps) => 

Named function declarations sidestep it, and adding an explicit variable type annotation is a workaround, but users reasonably expect the arrow form to Just Work.

Why it fails

Typegen emits each +types/*.ts with an intermediate named alias:

type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }>;
export type ComponentProps = Annotations["ComponentProps"];

Naming Annotations as an object-type alias forces TypeScript to eagerly compute the full annotations object when Route.ComponentProps is resolved. When that happens while inferring the type of a const arrow initializer, the evaluation transitively reaches back into typeof import("./self") and the alias-circularity detector fires.

Function declarations skip it because TypeScript reads their signature without eagerly evaluating parameter annotations.

Fix

Drop the intermediate Annotations alias in the emitted file and index into GetAnnotations<...> directly for every Route.* type:

type AnnotationsInput = Info & { module: Module, matches: Matches };
export type ComponentProps = GetAnnotations<AnnotationsInput>["ComponentProps"];
export type ServerComponentProps = GetAnnotations<AnnotationsInput>["ServerComponentProps"];
// …

TypeScript resolves indexed access on a generic-instantiated type lazily per key, so only the requested property is instantiated and the cycle never forms. The types are semantically identical to what was emitted before.

Tests

Added two cases under test.describe("server-first route component detection")test.describe("arrow function route components") in integration/typegen-test.ts:

  • ServerComponent as arrow function typechecks (RSC framework mode, export const ServerComponent = (…: Route.ServerComponentProps) => …)
  • default (Component) as arrow function typechecks (non-RSC, const Component = (…: Route.ComponentProps) => …; export default Component)

Both assert Expect<Equal<typeof loaderData, …>> / Expect<Equal<typeof actionData, …>> and run pnpm typecheck.

Emit `Route.ComponentProps` / `Route.ServerComponentProps` (and the
other `Route.*` annotation aliases) by indexing `GetAnnotations<...>`
directly instead of routing through an intermediate
`type Annotations = GetAnnotations<...>` alias. The intermediate alias
made TypeScript eagerly compute the full annotations object, tripping
its type-alias circularity detector (TS2456 / TS7022) when
`Route.ComponentProps` was used as the contextual parameter type of a
`const` arrow-function route component export (e.g.
`export const ServerComponent = ({}: Route.ComponentProps) => …`).

Fixes remix-run#14676
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.

[RSC] Type error when defining route's ServerComponent as arrow function

2 participants