diff --git a/contributors.yml b/contributors.yml index b0d86fab06..e49192f3cc 100644 --- a/contributors.yml +++ b/contributors.yml @@ -87,6 +87,7 @@ - chanmilee-fe - chasinhues - chensokheng +- ChihebBENCHEIKH1 - chr33s - chrille0313 - chrisngobanh diff --git a/integration/typegen-test.ts b/integration/typegen-test.ts index 10bdf3cc62..463cd61321 100644 --- a/integration/typegen-test.ts +++ b/integration/typegen-test.ts @@ -867,6 +867,101 @@ test.describe("typegen", () => { await $("pnpm typecheck"); }); }); + + // https://github.com/remix-run/react-router/issues/14676 + test.describe("arrow function route components", () => { + test("ServerComponent as arrow function typechecks", async ({ + edit, + $, + }) => { + await edit({ + "vite.config.ts": viteConfig({ rsc: true }), + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("server-component/:id", "routes/server-component.tsx") + ] satisfies RouteConfig; + `, + "app/routes/server-component.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/server-component" + + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { server: "server" } + } + + export function action() { + return { server: "server" } + } + + export const ServerComponent = ({ + loaderData, + actionData, + }: Route.ServerComponentProps) => { + type TestLoaderData = Expect> + type TestActionData = Expect> + return ( + <> +

ServerComponent

+

Loader data: {loaderData.server}

+

Action data: {actionData?.server}

+ + ) + } + `, + }); + await $("pnpm typecheck"); + }); + + test("default (Component) as arrow function typechecks", async ({ + edit, + $, + }) => { + await edit({ + "vite.config.ts": viteConfig({ rsc: false }), + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("client-component/:id", "routes/client-component.tsx") + ] satisfies RouteConfig; + `, + "app/routes/client-component.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/client-component" + + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { server: "server" } + } + + export function action() { + return { server: "server" } + } + + const Component = ({ + loaderData, + actionData, + }: Route.ComponentProps) => { + type TestLoaderData = Expect> + type TestActionData = Expect> + return ( + <> +

default (Component)

+

Loader data: {loaderData.server}

+ {actionData &&

Action data: {actionData.server}

} + + ) + } + + export default Component + `, + }); + await $("pnpm typecheck"); + }); + }); }); test("layout without pages", async ({ edit, $ }) => { diff --git a/packages/react-router-dev/.changes/patch.fix-arrow-function-server-component-typegen.md b/packages/react-router-dev/.changes/patch.fix-arrow-function-server-component-typegen.md new file mode 100644 index 0000000000..636b4aaae6 --- /dev/null +++ b/packages/react-router-dev/.changes/patch.fix-arrow-function-server-component-typegen.md @@ -0,0 +1 @@ +Fix generated `Route.ComponentProps` / `Route.ServerComponentProps` triggering a `TS2456` / `TS7022` circular type-alias error when used as the parameter type of an arrow-function route component export — typegen now indexes into `GetAnnotations<...>` directly instead of routing through an intermediate `type Annotations = GetAnnotations<...>` alias diff --git a/packages/react-router-dev/typegen/generate.ts b/packages/react-router-dev/typegen/generate.ts index d89c25e94a..849c486702 100644 --- a/packages/react-router-dev/typegen/generate.ts +++ b/packages/react-router-dev/typegen/generate.ts @@ -287,57 +287,65 @@ function getRouteAnnotations({ Babel.generate(matchesType).code + "\n\n" + ts` - type Annotations = GetAnnotations; + // The Route type aliases below index directly into + // \`GetAnnotations<...>\` instead of going through an intermediate + // \`type Annotations = GetAnnotations<...>\` alias. The intermediate + // alias forced TypeScript to eagerly compute the full annotations + // object, which triggered a type-alias circularity error + // (TS2456 / TS7022) when \`Route.ComponentProps\` was used as the + // contextual parameter type of a \`const\` arrow-function route + // export. See https://github.com/remix-run/react-router/issues/14676 + type AnnotationsInput = Info & { module: Module, matches: Matches }; export namespace Route { // links - export type LinkDescriptors = Annotations["LinkDescriptors"]; - export type LinksFunction = Annotations["LinksFunction"]; + export type LinkDescriptors = GetAnnotations["LinkDescriptors"]; + export type LinksFunction = GetAnnotations["LinksFunction"]; // meta - export type MetaArgs = Annotations["MetaArgs"]; - export type MetaDescriptors = Annotations["MetaDescriptors"]; - export type MetaFunction = Annotations["MetaFunction"]; + export type MetaArgs = GetAnnotations["MetaArgs"]; + export type MetaDescriptors = GetAnnotations["MetaDescriptors"]; + export type MetaFunction = GetAnnotations["MetaFunction"]; // headers - export type HeadersArgs = Annotations["HeadersArgs"]; - export type HeadersFunction = Annotations["HeadersFunction"]; + export type HeadersArgs = GetAnnotations["HeadersArgs"]; + export type HeadersFunction = GetAnnotations["HeadersFunction"]; // middleware - export type MiddlewareFunction = Annotations["MiddlewareFunction"]; + export type MiddlewareFunction = GetAnnotations["MiddlewareFunction"]; // clientMiddleware - export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"]; + export type ClientMiddlewareFunction = GetAnnotations["ClientMiddlewareFunction"]; // loader - export type LoaderArgs = Annotations["LoaderArgs"]; + export type LoaderArgs = GetAnnotations["LoaderArgs"]; // clientLoader - export type ClientLoaderArgs = Annotations["ClientLoaderArgs"]; + export type ClientLoaderArgs = GetAnnotations["ClientLoaderArgs"]; // action - export type ActionArgs = Annotations["ActionArgs"]; + export type ActionArgs = GetAnnotations["ActionArgs"]; // clientAction - export type ClientActionArgs = Annotations["ClientActionArgs"]; + export type ClientActionArgs = GetAnnotations["ClientActionArgs"]; // HydrateFallback - export type HydrateFallbackProps = Annotations["HydrateFallbackProps"]; + export type HydrateFallbackProps = GetAnnotations["HydrateFallbackProps"]; // ServerHydrateFallback - export type ServerHydrateFallbackProps = Annotations["ServerHydrateFallbackProps"]; + export type ServerHydrateFallbackProps = GetAnnotations["ServerHydrateFallbackProps"]; // Component - export type ComponentProps = Annotations["ComponentProps"]; + export type ComponentProps = GetAnnotations["ComponentProps"]; // ServerComponent - export type ServerComponentProps = Annotations["ServerComponentProps"]; + export type ServerComponentProps = GetAnnotations["ServerComponentProps"]; // ErrorBoundary - export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"]; + export type ErrorBoundaryProps = GetAnnotations["ErrorBoundaryProps"]; // ServerErrorBoundary - export type ServerErrorBoundaryProps = Annotations["ServerErrorBoundaryProps"]; + export type ServerErrorBoundaryProps = GetAnnotations["ServerErrorBoundaryProps"]; } `; return { filename, content };