Skip to content
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

refactor(react-router): Slight reorg of the RouteModule types #12560

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions .changeset/slimy-cows-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router": patch
---

Internal reorg of the `RouteModule` interfaces for deduplication and documentation purposes
69 changes: 55 additions & 14 deletions docs/start/framework/route-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Route modules are the foundation of React Router's framework features, they defi

This guide is a quick overview of every route module feature. The rest of the getting started guides will cover these features in more detail.

For additional details, please refer to the API documentation for a [Route Module][route-module]

## Component (`default`)

Defines the component that will render when the route matches.
Expand All @@ -40,6 +42,10 @@ export default function MyRouteComponent() {
}
```

See also:

- [`default` export Component props][component-props]

## `loader`

Route loaders provide data to route components before they are rendered. They are only called on the server when server rendering or during the build with pre-rendering.
Expand Down Expand Up @@ -130,6 +136,10 @@ export async function action({ request }) {
}
```

See also:

- [`action` params][action-params]

## `clientAction`

Like route actions but only called in the browser.
Expand Down Expand Up @@ -157,9 +167,7 @@ import {
useRouteError,
} from "react-router";

export function ErrorBoundary() {
const error = useRouteError();

export function ErrorBoundary({ error }) {
if (isRouteErrorResponse(error)) {
return (
<div>
Expand All @@ -184,6 +192,12 @@ export function ErrorBoundary() {
}
```

See also:

- [`ErrorBoundary` props][errorboundary-props]
- [`isRouteErorResponse` docs][is-route-error-response]
- [React Error Boundaries docs][error-boundaries]

## `HydrateFallback`

On initial page load, the route component renders only after the client loader is finished. If exported, a `HydrateFallback` can render immediately in place of the route component.
Expand All @@ -203,9 +217,13 @@ export default function Component({ loaderData }) {
}
```

See also:

- [`HydrateFallback` props][hydratefallback-props]

## `headers`

Route headers define HTTP headers to be sent with the response when server rendering.
Route headers define HTTP [headers] to be sent with the response when server rendering.

```tsx
export function headers() {
Expand All @@ -216,6 +234,11 @@ export function headers() {
}
```

See also:

- [`headers` params][headers-params]
- [`Cache-Control` header][cache-control-header]

## `handle`

Route handle allows apps to add anything to a route match in `useMatches` to create abstractions (like breadcrumbs, etc.).
Expand All @@ -226,6 +249,11 @@ export const handle = {
};
```

See also:

- [`handle` docs][handle]
- [`useMatches` docs][use-matches]

## `links`

Route links define [`<link>` element][link-element]s to be rendered in the document `<head>`.
Expand Down Expand Up @@ -269,9 +297,13 @@ export default function Root() {
}
```

See also:

- [`links` params][links-params]

## `meta`

Route meta defines meta tags to be rendered in the `<head>` of the document.
Route meta defines [`<meta>`][meta-element] tags to be rendered in the `<head>` of the document.

```tsx
export function meta() {
Expand Down Expand Up @@ -307,7 +339,7 @@ export default function Root() {
}
```

**See also**
See also:

- [`meta` params][meta-params]

Expand All @@ -325,22 +357,31 @@ export function shouldRevalidate(
}
```

See also:

- [`shouldRevalidate` params][shouldrevalidate-params]

---

Next: [Rendering Strategies](./rendering)

[fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
[loader-params]: https://api.reactrouter.com/v7/interfaces/react_router.LoaderFunctionArgs
[client-loader-params]: https://api.reactrouter.com/v7/types/react_router.ClientLoaderFunctionArgs
[action-params]: https://api.reactrouter.com/v7/interfaces/react_router.ActionFunctionArgs
[client-action-params]: https://api.reactrouter.com/v7/types/react_router.ClientActionFunctionArgs
[error-boundaries]: https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
[use-route-error]: https://api.reactrouter.com/v7/functions/react_router.useRouteError
[is-route-error-response]: https://api.reactrouter.com/v7/functions/react_router.isRouteErrorResponse
[cache-control-header]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
[headers]: https://developer.mozilla.org/en-US/docs/Web/API/Response
[use-matches]: https://api.reactrouter.com/v7/functions/react_router.useMatches
[link-element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link
[meta-element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
[meta-params]: https://api.reactrouter.com/v7/interfaces/react_router.MetaArgs
[use-revalidator]: https://api.reactrouter.com/v7/functions/react_router.useRevalidator.html
[route-module]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html
[component-props]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#default
[loader-params]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#loader
[client-loader-params]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#clientLoader
[action-params]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#actioin
[client-action-params]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#clientAction
[meta-params]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#meta
[links-params]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#links
[handle]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#handle
[errorboundary-props]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#ErrorBoundary
[hydratefallback-props]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#HydrateFallback
[headers-params]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#headers
[shouldrevalidate-params]: https://api.reactrouter.com/v7/interfaces/react_router.ServerRouteModule.html#shouldRevalidate
8 changes: 3 additions & 5 deletions packages/react-router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,13 @@ export type {
ClientActionFunctionArgs,
ClientLoaderFunction,
ClientLoaderFunctionArgs,
HeadersArgs,
HeadersFunction,
MetaArgs,
MetaDescriptor,
MetaFunction,
LinksFunction,
ServerRouteModule,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be weird, and potentially even a sign that this doesn't belong in type docs?

This is not a user-facing type but the only way to get the typedoc generated is to make it public API so we have to export it even though we never want anyone to import it and use it.

} from "./lib/dom/ssr/routeModules";
export type { ServerRouterProps } from "./lib/dom/ssr/server";
export { ServerRouter } from "./lib/dom/ssr/server";
Expand Down Expand Up @@ -251,11 +254,6 @@ export type {
LinkDescriptor,
} from "./lib/router/links";

export type {
HeadersArgs,
HeadersFunction,
} from "./lib/server-runtime/routeModules";

export type { RequestHandler } from "./lib/server-runtime/server";

export type {
Expand Down
100 changes: 81 additions & 19 deletions packages/react-router/lib/dom/ssr/routeModules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentType, ReactElement } from "react";
import type * as React from "react";
import type { Location } from "../../router/history";
import type {
ActionFunction,
Expand All @@ -18,19 +18,82 @@ export interface RouteModules {
[routeId: string]: RouteModule | undefined;
}

/**
* The shape of a route module shipped to the client
*/
export interface RouteModule {
/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a type as well

Suggested change
export interface RouteModule {
export type RouteModule = {

* Action function called only in the browser. Route client loaders provide
* data to route components in addition to, or in place of, route loaders.
*/
clientAction?: ClientActionFunction;
/**
* Like route actions but only called in the browser.
*/
clientLoader?: ClientLoaderFunction;
ErrorBoundary?: ErrorBoundaryComponent;
HydrateFallback?: HydrateFallbackComponent;
/**
* ErrorBoundary to display for this route
*/
ErrorBoundary?: React.ComponentType;
/**
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got rid of the type aliases here in favor of just using React.Component type directly.

* `<Route HydrateFallback>` component to render on initial loads
* when client loaders are present
*/
HydrateFallback?: React.ComponentType;
/**
* The `Layout` export is only applicable to the root route. Because the root
* route manages the document for all routes, it supports an additional optional
* `Layout` export that will be wrapped around the rendered UI, which may come
* from the default `Component` export, the `ErrorBoundary`, or the `HydrateFallback`
*/
Layout?: LayoutComponent;
default: RouteComponent;
/**
* Defines the component that will render when the route matches.
*/
default: React.ComponentType<{}>;
/**
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inlined but I think this is invalid now with the typegen props - going to look into making this actually pull the typegen stuff (CreateComponentProps) with unknown values for route-specific info

* Route handle allows apps to add anything to a route match in `useMatches`
* to create abstractions (like breadcrumbs, etc.).
*/
handle?: RouteHandle;
/**
* Route links define [`<link>` element][link-element]s to be rendered in the
* document `<head>`.
*/
links?: LinksFunction;
/**
* Route meta defines meta tags to be rendered in the `<head>` of the document.
*/
meta?: MetaFunction;
/**
* By default, all routes are revalidated after actions. This function allows
* a route to opt-out of revalidation for actions that don't affect its data.
*/
shouldRevalidate?: ShouldRevalidateFunction;
}

/**
* The shape of a route module file for your application
*/
export interface ServerRouteModule extends RouteModule {
/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this to be an interface

Suggested change
export interface ServerRouteModule extends RouteModule {
export type ServerRouteModule = RouteModule & {

* Route actions allow server-side data mutations with automatic revalidation
* of all loader data on the page when called from `<Form>`, `useFetcher`,
* and `useSubmit`.
* */
action?: ActionFunction;
/**
* Route headers define HTTP headers to be sent with the response when server rendering.
*/
headers?: HeadersFunction | { [name: string]: string };
/**
* Route loaders provide data to route components before they are rendered.
* They are only called on the server when server rendering or during the
* build with pre-rendering.
*/
loader?: LoaderFunction;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved into this file from the now deleted version from server-runtime


/**
* A function that handles data mutations for a route on the client
*/
Expand Down Expand Up @@ -62,26 +125,30 @@ export type ClientLoaderFunctionArgs = LoaderFunctionArgs<undefined> & {
};

/**
* ErrorBoundary to display for this route
* Parameters passed to the [`headers`]{@link HeadersFunction} function
*/
export type ErrorBoundaryComponent = ComponentType;
export type HeadersArgs = {
loaderHeaders: Headers;
parentHeaders: Headers;
actionHeaders: Headers;
errorHeaders: Headers | undefined;
};

/**
* `<Route HydrateFallback>` component to render on initial loads
* when client loaders are present
* A function that returns HTTP headers to be used for a route. These headers
* will be merged with (and take precedence over) headers from parent routes.
*/
export type HydrateFallbackComponent = ComponentType;
export interface HeadersFunction {
(args: HeadersArgs): Headers | HeadersInit;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved in from the server-runtime file

Comment on lines +141 to +143
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified to

Suggested change
export interface HeadersFunction {
(args: HeadersArgs): Headers | HeadersInit;
}
export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit;

/**
* Optional, root-only `<Route Layout>` component to wrap the root content in.
* Useful for defining the <html>/<head>/<body> document shell shared by the
* Component, HydrateFallback, and ErrorBoundary
*/
export type LayoutComponent = ComponentType<{
children: ReactElement<
unknown,
ErrorBoundaryComponent | HydrateFallbackComponent | RouteComponent
>;
export type LayoutComponent = React.ComponentType<{
children: React.ReactElement;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I legit didn't know this type existed, FWIW I added it here and got a comment from Michael to maybe change it to a different React type, check it out here:
#12543 (comment)

}>;

Comment on lines +150 to 152
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified to

Suggested change
export type LayoutComponent = React.ComponentType<{
children: React.ReactElement;
}>;
export type LayoutComponent = React.ComponentType<React.PropsWithChildren>;

/**
Expand Down Expand Up @@ -218,11 +285,6 @@ type LdJsonArray = LdJsonValue[] | readonly LdJsonValue[];
type LdJsonPrimitive = string | number | boolean | null;
type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray;

/**
* A React component that is rendered for a route.
*/
export type RouteComponent = ComponentType<{}>;

/**
* An arbitrary object that is associated with a route.
*
Expand Down
6 changes: 3 additions & 3 deletions packages/react-router/lib/server-runtime/entry.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { RouteModules } from "../dom/ssr/routeModules";
import type { ServerRouteManifest } from "./routes";
import type { RouteModules, EntryRouteModule } from "./routeModules";

export function createEntryRouteModules(
manifest: ServerRouteManifest
): RouteModules<EntryRouteModule> {
): RouteModules {
return Object.keys(manifest).reduce((memo, routeId) => {
let route = manifest[routeId];
if (route) {
memo[routeId] = route.module;
}
return memo;
}, {} as RouteModules<EntryRouteModule>);
}, {} as RouteModules);
}
Loading
Loading