new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

React Router · API · all subjects

route configuration & modes

44 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

dataStrategy config purpose and input

dataStrategy is an optional config that accepts an array of matches to load and returns a parallel array of results. It controls when handlers are called, not how. React Router remains in charge of calling handlers with the right parameters.

HandlerResult handling of Response objects

If a HandlerResult's result is a Response, React Router internally unwraps the data and processes any redirects, which may produce a SuccessResult, ErrorResult, or RedirectResult. If result is a DeferredData instance, it converts to DeferResult. For anything else, React Router does not touch the data based on the type field.

DataStrategyMatch and route.lazy loading

DataStrategyMatch is like RouteMatch but the match.route field is a Promise<Route>. This allows dataStrategy to manage when route.lazy methods execute. Statically defined properties like match.route.id, match.route.index, and match.route.path are extended onto match.route and can be accessed without awaiting the Promise.

match.resolve() method for dataStrategy

match.resolve() is a function on DataStrategyMatch that wraps all data loading concerns. It waits for route.lazy to resolve if needed, no-ops if the route does not need revalidation, knows whether to call loader or action, and accepts an optional handlerOverride function for custom handler execution. Called with no arguments, it behaves like the default behavior. Called with a handlerOverride function, that function receives the handler and can call it with custom arguments.

handlerOverride function in match.resolve()

The handlerOverride function passed to match.resolve() receives the handler and can invoke it with custom arguments. If a handlerOverride is provided, it must return a proper HandlerResult with type: 'data' | 'error'. The argument passed to the handler becomes the second argument to the loader/action function after request.

dataStrategy sequential loaders example

Example of running loaders sequentially with context: async function dataStrategy({ matches }) { let ctx = {}; let results = []; for (let match of matches) { let result = await match.resolve((handler) => { return handler(ctx); }); results.push(result); }; return results; }

dataStrategy parallel loaders example

Example of running all loaders in parallel: function dataStrategy({ matches }) { return Promise.all(matches.map(match => { return match.resolve(); })); }

dataStrategy single-fetch example

Example of custom data fetching strategy using single fetch: async function dataStrategy({ matches }) { let singleFetchData = await makeSingleFetchCall(); let results = []; for (let match of matches) { let result = await match.resolve(() => { if (singleFetchData.errors?.[match.route.id]) { return { type: 'error', result: singleFetchData.errors?.[match.route.id] }; } return { type: 'data', result: singleFetchData.data?.[match.route.id] }; }); results.push(result); }; return results; }

shouldRevalidate pre-filtering in dataStrategy

shouldRevalidate logic is applied before dataStrategy is called. Only matches that pass shouldRevalidate are included in the matches array passed to dataStrategy. This keeps revalidation decisions out of the dataStrategy implementation.

dataStrategy handles single-length match arrays for actions

Actions and fetchers pass a single-length array to dataStrategy containing only the target match. The same dataStrategy implementation works for loaders (multiple matches), actions (single match), and fetchers (single match) since the function always operates on a matches array.

dataStrategy responsibilities in React Router

React Router handles four aspects when executing loaders: (1) Match routes for URL, (2) Determine what routes to load via shouldRevalidate, (3) Call loader functions, (4) Decode Responses. dataStrategy primarily controls step 3, when handlers are called. The default behavior runs loaders in parallel; applications like Remix can opt into different strategies like single fetch via custom dataStrategy implementations.

dataStrategy with middleware pattern

Middleware can be implemented user-land within dataStrategy by running context and middleware sequentially on all matches before running loaders in parallel. Matches above or at the current level pass their context to loaders via the handlerOverride argument.

dataStrategy option purpose

The dataStrategy option gives full control over how action and loader functions are executed. It overrides React Router's default behavior of executing all loaders in parallel, allowing advanced functionality such as middleware, context, and caching layers.

DataStrategyMatch fields

A DataStrategyMatch is a normal route match with additional fields: shouldCallHandler (function that tells whether this route's handler should be called), shouldRevalidateArgs (arguments to pass to the route's shouldRevalidate), and resolve (function to call the route handler with custom execution control). The field shouldLoad is deprecated in favor of shouldCallHandler.

shouldCallHandler usage for determining matches to load

In a dataStrategy, filter matches using m.shouldCallHandler() to determine which route handlers should be executed. For loading navigations this returns true for new routes and existing routes requiring revalidation. For submission navigations it only returns true for the action route. For fetcher calls it only returns true for the fetcher route.

match.resolve() function call and handler execution

Call match.resolve() to execute the route handler and store the result. The resolve function can optionally accept a callback to customize handler execution, such as passing a custom context as a second argument to the handler.

runClientMiddleware function usage

Use runClientMiddleware to execute middleware around handlers. It takes a callback function as an argument and runs the middleware for all matched routes before executing the callback. It accepts the same arguments as dataStrategy so it can be composed with standalone dataStrategy implementations.

Custom revalidation behavior with shouldCallHandler

Pass a custom defaultShouldRevalidate function to match.shouldCallHandler() to alter revalidation behavior. The arguments that would be passed to route-level shouldRevalidate functions are available on match.shouldRevalidateArgs.

Migration from shouldLoad to shouldCallHandler

The deprecated shouldLoad was a boolean field. With shouldCallHandler, you must pre-filter matches before calling resolve() on them. When using shouldLoad, resolve() would only call the handler if shouldLoad was true. With shouldCallHandler, you control which handlers are called by deciding which matches to call resolve() on.

Custom middleware example with sequential execution

Custom middleware can be defined via route handle property. In a dataStrategy, iterate through matches sequentially and call match.route.handle.middleware() to add data to a context object, then run loaders in parallel passing the context as the second argument to handlers via match.resolve() callback.

Custom handler pattern with loader as boolean

Set route.loader=true to mark a route as having a loader without defining a loader function. Store custom data like GraphQL fragments on route.handle. In dataStrategy, compose all fragments, make a single data request, and parse results back into individual DataStrategyResult objects keyed by route ID without calling match.resolve().

loader function purpose and usage

The loader function provides data to route components on the server. For server-rendered applications, loader is used for both initial page loads and client navigations. Client navigations call the loader through an automatic fetch by React Router from the browser to the server. The loader function is removed from client bundles, allowing the use of server-only APIs without worrying about them being included in the browser.

clientLoader function purpose and usage

The clientLoader function is used to fetch data on the client side. This is useful for pages or full projects that prefer to fetch data from the browser only. clientLoader is called during client-side navigations and can be used independently or combined with server loaders.

Serializable types in loaders

Loaders can return promises, maps, sets, dates, and more in addition to primitive values like strings and numbers. React Router supports the same set of serializable types that React permits server components to pass as props to client components.

Using both loader and clientLoader together

When loader and clientLoader are both defined on a route, the loader is used on the server for initial SSR or pre-rendering, while clientLoader is used on subsequent client-side navigations. In clientLoader, the serverLoader function can be called to retrieve data from the server loader.

clientLoader.hydrate property

Setting clientLoader.hydrate = true as const forces the clientLoader to run during hydration before the page renders. When using this, a HydrateFallback component should be rendered to show fallback UI while the clientLoader runs. Use 'as const' for proper type inference.

Static data loading with pre-rendering

When pre-rendering, loaders are used to fetch data during the production build. The URLs to pre-render are specified in react-router.config.ts with an async prerender() function that returns an array of URL paths.

Server rendering with pre-rendering fallback

When server rendering, any URLs that aren't pre-rendered will be server rendered as usual. This allows pre-rendering data at a single route while still server rendering the rest of the routes.

clientLoader example with fetch

Example: export async function clientLoader({ params }: Route.ClientLoaderArgs) { const res = await fetch(`/api/products/${params.pid}`); const product = await res.json(); return product; }

loader example for server rendering

Example: export async function loader({ params }: Route.LoaderArgs) { const product = await fakeDb.getProduct(params.pid); return product; }

Combining loader and clientLoader example

Example: export async function loader({ params }: Route.LoaderArgs) { return fakeDb.getProduct(params.pid); } export async function clientLoader({ serverLoader, params }: Route.ClientLoaderArgs) { const res = await fetch(`/api/products/${params.pid}`); const serverData = await serverLoader(); return { ...serverData, ...(await res.json()) }; }

Setting clientLoader.hydrate for hydration loading

Example: export async function clientLoader() { /* ... */ } clientLoader.hydrate = true as const; export function HydrateFallback() { return <div>Loading...</div>; }

Cache-Control headers in loaders for browser caching

You can use Cache-Control headers within loaders to tap into the browser's native cache to avoid redundant data fetching. However, this approach has limitations and should be used judiciously. It's usually more beneficial to optimize backend queries or implement a server cache, as such changes benefit all users and do away with the need for individual browser caches.

Navigation blocking in framework and data modes

Navigation blocking is available in both framework mode and data mode in React Router.

ServerComponent export for server-first routes

If a route exports a `ServerComponent` instead of the typical `default` component export, the route renders on the server instead of the client. A route module cannot export both `default` and `ServerComponent`. Client-only annotations like `clientLoader` and `clientAction` can still be exported alongside a `ServerComponent`.

Server and client component export counterparts

In RSC Framework Mode, route modules have mutually exclusive server and client component exports: - `ServerComponent` (server) / `default` (client) - `ServerErrorBoundary` (server) / `ErrorBoundary` (client) - `ServerLayout` (server) / `Layout` (client) - `ServerHydrateFallback` (server) / `HydrateFallback` (client)

MDX route support in RSC Framework Mode

MDX routes are supported in RSC Framework Mode when using @mdx-js/rollup v3.1.1 or higher. Components exported from MDX routes must be valid in RSC environments and cannot use client-only features like Hooks. Components needing these features should be extracted into a client module.

RSC Data Mode route configuration at matchRSCServerRequest

In RSC Data Mode, routes are configured as an argument to `matchRSCServerRequest`. At a minimum, routes need a path and component. While components can be defined inline, using the `lazy()` option with Route Modules is recommended for startup performance and code organization.

RSC Data Mode lazy field expects Route Module exports

The `lazy` field of the RSC route config in Data Mode expects the same exports as the Route Module API, keeping route-module shape consistent across Framework Mode and RSC Data Mode. This includes exports like `loader`, `action`, `meta`, `links`, `headers`, `ErrorBoundary`, `HydrateFallback`, and client annotations.

RSC Data Mode routes config example

Example RSC Data Mode routes configuration: ```tsx import type { unstable_RSCRouteConfig as RSCRouteConfig } from "react-router"; export function routes() { return [ { id: "root", path: "", lazy: () => import("./root/route"), children: [ { id: "home", index: true, lazy: () => import("./home/route"), }, { id: "about", path: "about", lazy: () => import("./about/route"), }, ], }, ] satisfies RSCRouteConfig; } ```

RSC Data Mode Server Components are default export

In RSC Data Mode, by default each route's `default` export renders a Server Component. Server Components can be async and fetch data directly from the component without using loaders.

RSC Data Mode async Server Component example

Example of an async Server Component in RSC Data Mode: ```tsx export default async function Home() { let user = await getUserData(); return ( <main> <article> <h1>Welcome to React Router RSC</h1> <p> You won't find me running any JavaScript in the browser! </p> <p> Hello, {user ? user.name : "anonymous person"}! </p> </article> </main> ); } ```

RSC Data Mode clientLoader, clientAction via client references

In RSC Data Mode, `clientLoader`, `clientAction`, and `shouldRevalidate` are provided through client references and "use client" directive. These can be defined in a separate client module and re-exported from the lazy-loaded route module.

RSC Framework Mode and Data Mode differences

RSC Framework Mode has built-in features like routes.ts config, file system routing, HMR, and Hot Data Revalidation. RSC Data Mode lacks these features but is more flexible and allows integration with custom bundlers and server abstractions. Both modes provide lower-level RSC APIs.

Give your agent this brain