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 · Guides · all subjects

routing

157 notes in this subject, read out of this brain and free to use. This is page 1 of 3.

Multiple routes example with index query param

Example route structure: a parent route 'projects' at ./pages/projects.tsx with a child index route at ./pages/projects/index.tsx creates two routes matching /projects. Form submission to /projects targets the parent action, while /projects?index targets the index action.

Index routes share URL with parent route

Index routes match the same URL as their parent route. For example, both a parent route at `/projects` and an index route at `/projects` match the same URL path.

Index query param appears in URLs with nested routes

When submitting forms in applications with nested routes, a `?index` query parameter may appear in the URL. This happens because multiple routes in the hierarchy can match the same URL.

Index param works with Form, useSubmit, and useFetcher

The `?index` param can be used to control form submission routing across multiple APIs: the Form component, the useSubmit hook, and the useFetcher hook. All support both explicit `action` props with `?index` and automatic appending when rendered in context.

Form in index route automatically appends ?index param

When a Form component is rendered in an index route without an explicit action prop, React Router automatically appends the `?index` query parameter so the form posts to the index route's action instead of the parent route's action.

Index query param disambiguates form submissions between parent and index routes

Because multiple routes can match the same URL, the `?index` param lets you disambiguate which route's action should handle a form submission. Without `?index`, the form submits to the parent route; with `?index`, it submits to the index route.

routes.ts file requirement

routes.ts is a required file that contains route configuration mapping URLs to components.

Route.ComponentProps type for default export

The `Route.ComponentProps` type provides type safety for the default component export, including typed `loaderData` that matches the return type of the loader function.

Automatic type generation during development

When running `react-router dev` or when a custom server calls `vite.createServer`, React Router's Vite plugin automatically generates up-to-date types without requiring manual `typegen` command execution.

react-router typegen command

The `react-router typegen` command manually generates type definition files. It can be run with the `--watch` flag to automatically regenerate types as files change.

Route-specific type imports with +types prefix

React Router generates route-specific types that can be imported using the pattern `import type { Route } from "./+types/<route-file>"`. These generated types provide type safety for route module exports including loaders, actions, and components.

Generated route types

React Router's typegen command generates the following types for each route: LoaderArgs, ClientLoaderArgs, ActionArgs, ClientActionArgs, HydrateFallbackProps, ComponentProps (for the default export), and ErrorBoundaryProps.

React Router type generation process

React Router's type generation executes the route config file (app/routes.ts by default) to determine all routes in the app, then generates a `+types/<route-file>.d.ts` type definition file for each route in the `.react-router/types/` directory. With `rootDirs` configured in TypeScript, these files can be imported as if they were next to the route modules.

Route.LoaderArgs type includes route params

When importing `Route` types from the generated `+types` directory, the `Route.LoaderArgs` type automatically includes typed parameters from the route path. For a route like `products/:id`, the params object is typed as `{ id: string }`.

Error boundaries catch route errors automatically

Route modules automatically catch errors in your code and render the closest ErrorBoundary. This prevents rendering an empty page to users.

Example throwing data in loader with status code

import { data } from "react-router"; export async function loader({ params }) { let record = await fakeDb.getRecord(params.id); if (!record) { throw data("Record Not Found", { status: 404 }); } return record; } This example shows how to throw data with a 404 status code when a record is not found in a loader.

Data sent with throw data() is not sanitized

Data sent with throw data(yourData) is not sanitized in production because that data is intended to be rendered to the user.

Closest error boundary renders when error is thrown

When an error is thrown, the closest error boundary in the route hierarchy will be rendered to handle that error.

Example Data Mode root error boundary

import { useRouteError } from "react-router"; let router = createBrowserRouter([ { path: "/", ErrorBoundary: RootErrorBoundary, Component: Root, }, ]); function Root() { /* ... */ } function RootErrorBoundary() { let error = useRouteError(); if (isRouteErrorResponse(error)) { return ( <> <h1> {error.status} {error.statusText} </h1> <p>{error.data}</p> </> ); } else if (error instanceof Error) { return ( <div> <h1>Error</h1> <p>{error.message}</p> <p>The stack trace is:</p> <pre>{error.stack}</pre> </div> ); } else { return <h1>Unknown Error</h1>; } } This example shows how to handle three error cases in Data Mode using useRouteError hook.

Throw data with status codes for expected errors

Use throw data() with a proper status code to intentionally pass errors to the closest error boundary when your loader or action cannot complete its task, such as when returning a 404 for a not found record. This is an exception to the rule against intentionally throwing errors.

Do not intentionally throw errors for control flow

It is not recommended to intentionally throw errors to force the error boundary to render as a means of control flow. Error Boundaries are primarily for catching unintentional errors in your code.

Framework Mode error sanitization in production

In Framework Mode when building for production, any errors that happen on the server are automatically sanitized before being sent to the browser to prevent leaking sensitive server information like stack traces. A thrown Error will have a generic message and no stack trace in production in the browser, while the original error is untouched on the server.

Error boundaries work across all route module APIs

Error boundaries catch errors not just from loaders and actions, but from all route module APIs: loaders, actions, components, headers, links, and meta.

Example Framework Mode root error boundary

import { Route } from "./+types/root"; export function ErrorBoundary({ error, }: Route.ErrorBoundaryProps) { if (isRouteErrorResponse(error)) { return ( <> <h1> {error.status} {error.statusText} </h1> <p>{error.data}</p> </> ); } else if (error instanceof Error) { return ( <div> <h1>Error</h1> <p>{error.message}</p> <p>The stack trace is:</p> <pre>{error.stack}</pre> </div> ); } else { return <h1>Unknown Error</h1>; } } This example shows how to handle three error cases in Framework Mode: RouteErrorResponse objects, Error instances, and unknown values.

Framework Mode error boundary receives error as prop

In Framework Mode, errors are passed to the route-level error boundary as a prop via Route.ErrorBoundaryProps, so you do not need to use a hook to grab it. The ErrorBoundary component receives an object with an error property.

Data Mode error boundary uses useRouteError hook

In Data Mode, the ErrorBoundary does not receive props. Instead, use the useRouteError hook to access the error within the error boundary component.

Error boundaries are not for form validation or error reporting

Error boundaries should not be used for rendering form validation errors or error reporting. Use Form Validation and Error Reporting instead for those purposes.

Root error boundary handles three error cases

All applications should export a root error boundary that handles three main cases: thrown data with a status code and text, instances of errors with a stack trace, and randomly thrown values.

useFetcher hook basic usage

Import and call useFetcher to create a fetcher instance: let fetcher = useFetcher();. The fetcher provides a Form component and state tracking for managing data interactions without navigation.

What are fetchers in React Router

Fetchers are useful for creating complex, dynamic user interfaces that require multiple, concurrent data interactions without causing a navigation. Fetchers track their own independent state and can be used to load data, mutate data, submit forms, and generally interact with loaders and actions.

Fetcher action validation example

Example showing error handling and validation in a fetcher action: export async function clientAction({ request }) { let data = await request.formData(); let title = data.get("title") as string; if (title.trim() === "") { return { ok: false, error: "Title cannot be empty" }; } localStorage.setItem("title", title); return { ok: true, error: null }; } export default function Component() { let fetcher = useFetcher(); return <fetcher.Form method="post"><input type="text" name="title" />{fetcher.data?.error && <p style={{ color: "red" }}>{fetcher.data.error}</p>}</fetcher.Form>; }

fetcher.Form component

The fetcher.Form component renders a form that submits to a route action or loader without causing navigation. It accepts method and action props. Example: <fetcher.Form method="post"><input type="text" name="title" /></fetcher.Form>

Fetcher for calling actions example

Example showing fetcher with action and optimistic UI: export async function clientAction({ request }) { let data = await request.formData(); localStorage.setItem("title", data.get("title")); return { ok: true }; } export default function Component() { let data = useLoaderData(); let fetcher = useFetcher(); let title = fetcher.formData?.get("title") || data.title; return <div><h1>{title}</h1><fetcher.Form method="post"><input type="text" name="title" />{fetcher.state !== "idle" && <p>Saving...</p>}</fetcher.Form></div>; }

fetcher.submit method

Fetchers can be submitted programmatically with fetcher.submit(). Pass the form element as the first argument to submit the form data: fetcher.submit(event.currentTarget.form). The fetcher will read the form's attributes and serialize the data from its elements.

Type inference with useFetcher

Use useFetcher with a type parameter to get type inference from a loader: import type { loader } from "./search-users"; let fetcher = useFetcher<typeof loader>();. Always use import type to import only the types, not the runtime code.

fetcher.data property

The fetcher.data property contains data returned from an action. This is primarily useful for returning error messages to the user for a failed mutation. The data is available after the action completes.

fetcher.formData property

The fetcher.formData property contains the serialized form data from the current submission. It is available immediately after form submission and can be used to implement optimistic UI updates before the action completes. Example: let title = fetcher.formData?.get("title") || data.title;

Fetcher for data loading with search example

Example showing fetcher for loading data from a search route: export async function loader({ request }) { let url = new URL(request.url); let query = url.searchParams.get("q"); return users.filter((user) => user.name.toLowerCase().includes(query.toLowerCase())); } export function UserSearchCombobox() { let fetcher = useFetcher<typeof loader>(); return <div><fetcher.Form method="get" action="/search-users"><input type="text" name="q" onChange={(event) => { fetcher.submit(event.currentTarget.form); }} /></fetcher.Form>{fetcher.data && <ul style={{opacity: fetcher.state === "idle" ? 1 : 0.25}}>{fetcher.data.map((user) => <li key={user.id}>{user.name}</li>)}</ul>}</div>; }

Fetcher state property

The fetcher.state property indicates the current state of the async work. It can be checked to render pending UI. Example: {fetcher.state !== "idle" && <p>Saving...</p>}

Folder route organization rules

When using folder-based routes, the route path is completely defined by the folder name, not by files inside the folder. Only the route.tsx file in the folder is treated as a route module; other files in the folder can be components, utilities, or other assets specific to that route.

Escaping special characters in filenames

Special route convention characters can be escaped with [] brackets to include them literally in the URL. For example, sitemap[.]xml.tsx matches /sitemap.xml, and reports.$id[.pdf].ts matches /reports/123.pdf.

Catch-all route with $.tsx

A file named $.tsx in the routes directory creates a catch-all route that matches any requests that don't match other defined routes. This is commonly used for 404 pages.

Accessing splat route matched path

Splat route matched paths are accessible in loaders via params["*"]. For example, in a files.$.tsx route, access the matched path with params["*"].

Optional segments matching behavior with multiple dynamic params

When an optional dynamic param segment is followed by another dynamic param, optional segments match eagerly. For example, ($lang)._index.tsx will match /american-flag-speedo instead of ($lang).$productId.tsx. In this case, check params.lang in the loader and redirect if needed.

Optional segments with parentheses

Wrapping a route segment in parentheses makes the segment optional. For example, ($lang)._index.tsx creates routes that match both / and /en or /fr. Optional segments match eagerly.

Pathless routes with leading underscore

A leading underscore (e.g., _auth.login.tsx) creates a pathless route. These routes share a layout with other routes in the group without adding path segments to the URL. The leading underscore hides the filename from the URL path.

Trailing underscore opts out of layout nesting

A trailing underscore on the parent segment (e.g., concerts_.mine.tsx) creates a path segment but does not create layout nesting. The route will nest with the root instead of the parent route.

Nested routes best practice: add index routes

When creating nested routes, you typically want to add an index route (e.g., concerts._index.tsx) so that something renders inside the parent's outlet when users visit the parent URL directly.

Multiple dynamic segments

Routes can have multiple dynamic segments. For example, concerts.$city.$date creates a route where both params.date and params.city are accessible in loaders and actions.

Dynamic segments with $ prefix

Dynamic segments are created with the $ prefix. For example, concerts.$city.tsx matches /concerts/salt-lake-city and /concerts/san-diego. The $ prefix is replaced with the actual segment value.

Dot delimiters create nesting hierarchy

Dot delimiters in filenames create both URL paths and layout nesting. If the filename before the . matches another route filename, it automatically becomes a child route.

Dot delimiters create nested URL paths

Adding a . to a route filename creates a / in the URL. For example, concerts.trending.tsx matches /concerts/trending.

_index.tsx is the index route for root

_index.tsx files are index routes. At the root level, app/routes/_index.tsx is the index route for the root route and matches the / URL.

Splat routes match rest of URL including slashes

Splat routes use $ and match the rest of a URL including slashes, unlike dynamic segments which only match a single segment. For example, $.tsx matches /beef/and/cheese, and files.$.tsx matches /files/talks/react-conf_old.pdf.

File extensions for routes

Route files can use .js, .jsx, .ts, or .tsx file extensions.

rootDirectory option for custom route folder

The rootDirectory option on flatRoutes() configures where to look for routes. It defaults to app/routes but is relative to your app directory. For example: flatRoutes({ rootDirectory: "file-routes" }).

ignoredRouteFiles option

The ignoredRouteFiles option on flatRoutes() allows you to specify files that should not be included as routes. For example: flatRoutes({ ignoredRouteFiles: ["home.tsx"] }).

Using folders with route.tsx for code organization

Routes can be folders with a route.tsx file inside. Other files in the folder will not become routes, allowing code organization closer to the routes that use them. For example, app/routes/app._index/route.tsx is equivalent to app/routes/app._index.tsx.

Setting up file-based routes in app/routes.ts

To enable file-based routing, import flatRoutes from @react-router/fs-routes and RouteConfig from @react-router/dev/routes, then export flatRoutes() satisfies RouteConfig in your app/routes.ts file.

@react-router/fs-routes package installation

The @react-router/fs-routes package enables file-convention based route config. Install it with npm i @react-router/fs-routes.

Give your agent this brain