diff --git a/.eslintignore b/.eslintignore index f36aec4840..49a812d5b4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,8 +8,6 @@ worker-configuration.d.ts /playground-local/ integration/helpers/**/dist/ integration/helpers/**/build/ -# Temporary until we can get prettier upgraded to support `import ... with` syntax -integration/helpers/rsc-parcel/src/server.tsx playwright-report/ test-results/ build.utils.d.ts diff --git a/.eslintrc b/.eslintrc index 61400497f8..bca19acfa1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,6 +18,71 @@ "env": { "jest/globals": false } + }, + { + // Only apply JSDoc lint rules to files we auto-generate docs for + "files": [ + "packages/react-router/lib/components.tsx", + "packages/react-router/lib/hooks.tsx", + "packages/react-router/lib/dom/lib.tsx", + "packages/react-router/lib/dom/ssr/components.tsx", + "packages/react-router/lib/dom/ssr/server.tsx", + "packages/react-router/lib/dom-export/hydrated-router.tsx", + "packages/react-router/lib/dom/server.tsx", + "packages/react-router/lib/rsc/browser.tsx", + "packages/react-router/lib/rsc/server.rsc.ts", + "packages/react-router/lib/rsc/server.ssr.tsx", + "packages/react-router/lib/rsc/html-stream/browser.ts", + ], + "plugins": ["jsdoc"], + "rules": { + "jsdoc/check-access": "error", + "jsdoc/check-alignment": "error", + "jsdoc/check-param-names": "error", + "jsdoc/check-property-names": "error", + "jsdoc/check-tag-names": [ + "error", + { + "definedTags": ["additionalExamples", "category", "mode"] + } + ], + "jsdoc/no-defaults": "error", + "jsdoc/no-multi-asterisks": ["error", { "allowWhitespace": true }], + "jsdoc/require-description": "error", + "jsdoc/require-param": ["error", { "enableRootFixer": false }], + "jsdoc/require-param-description": "error", + "jsdoc/require-param-name": "error", + "jsdoc/require-returns": "error", + "jsdoc/require-returns-check": "error", + "jsdoc/require-returns-description": "error", + "jsdoc/sort-tags": [ + "error", + { + "tagSequence": [ + { + "tags": ["description"] + }, + { + "tags": ["example"] + }, + { + "tags": ["additionalExamples"] + }, + { + "tags": [ + "name", + "public", + "private", + "category", + "mode", + "param", + "returns" + ] + } + ] + } + ] + } } ], "reportUnusedDisableDirectives": true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ad63e44b8d..87adb0e69a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,15 +1,16 @@ name: 📚 Docs on: + push: + branches: + # Enable main after the next release beyond `7.7.0` + # - main + - dev workflow_dispatch: inputs: branch: - description: "Branch to generate docs for" + description: "Branch to generate docs for (usually dev)" required: true - api: - description: "API Names to generate docs for" - required: false - default: "" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -43,15 +44,10 @@ jobs: run: pnpm build - name: 📚 Generate Typedoc Docs - run: pnpm run docs + run: pnpm run docs:typedoc - - name: 📚 Generate Markdown Docs (for all APIs) - if: github.event.inputs.api == '' - run: node --experimental-strip-types scripts/docs.ts --path packages/react-router/lib/hooks.tsx --write - - - name: 📚 Generate Markdown Docs (for specific APIs) - if: github.event.inputs.api != '' - run: node --experimental-strip-types scripts/docs.ts --path packages/react-router/lib/hooks.tsx --write --api ${{ github.event.inputs.api }} + - name: 📚 Generate Markdown Docs + run: pnpm run docs:jsdoc --write - name: 💪 Commit run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 1410011e91..119ded6486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,56 +13,59 @@ We manage release notes in this file instead of the paginated Github Releases Pa Table of Contents - [React Router Releases](#react-router-releases) + - [v7.7.1](#v771) + - [Patch Changes](#patch-changes) + - [Unstable Changes](#unstable-changes) - [v7.7.0](#v770) - [What's Changed](#whats-changed) - [Unstable RSC APIs](#unstable-rsc-apis) - [Minor Changes](#minor-changes) - - [Patch Changes](#patch-changes) - - [Unstable Changes](#unstable-changes) + - [Patch Changes](#patch-changes-1) + - [Unstable Changes](#unstable-changes-1) - [Changes by Package](#changes-by-package) - [v7.6.3](#v763) - - [Patch Changes](#patch-changes-1) - - [v7.6.2](#v762) - [Patch Changes](#patch-changes-2) - - [v7.6.1](#v761) + - [v7.6.2](#v762) - [Patch Changes](#patch-changes-3) - - [Unstable Changes](#unstable-changes-1) + - [v7.6.1](#v761) + - [Patch Changes](#patch-changes-4) + - [Unstable Changes](#unstable-changes-2) - [v7.6.0](#v760) - [What's Changed](#whats-changed-1) - [`routeDiscovery` Config Option](#routediscovery-config-option) - [Automatic Types for Future Flags](#automatic-types-for-future-flags) - [Minor Changes](#minor-changes-1) - - [Patch Changes](#patch-changes-4) - - [Unstable Changes](#unstable-changes-2) + - [Patch Changes](#patch-changes-5) + - [Unstable Changes](#unstable-changes-3) - [Changes by Package](#changes-by-package-1) - [v7.5.3](#v753) - - [Patch Changes](#patch-changes-5) + - [Patch Changes](#patch-changes-6) - [v7.5.2](#v752) - [Security Notice](#security-notice) - - [Patch Changes](#patch-changes-6) - - [v7.5.1](#v751) - [Patch Changes](#patch-changes-7) - - [Unstable Changes](#unstable-changes-3) + - [v7.5.1](#v751) + - [Patch Changes](#patch-changes-8) + - [Unstable Changes](#unstable-changes-4) - [v7.5.0](#v750) - [What's Changed](#whats-changed-2) - [`route.lazy` Object API](#routelazy-object-api) - [Minor Changes](#minor-changes-2) - - [Patch Changes](#patch-changes-8) - - [Unstable Changes](#unstable-changes-4) + - [Patch Changes](#patch-changes-9) + - [Unstable Changes](#unstable-changes-5) - [Changes by Package](#changes-by-package-2) - [v7.4.1](#v741) - [Security Notice](#security-notice-1) - - [Patch Changes](#patch-changes-9) - - [Unstable Changes](#unstable-changes-5) - - [v7.4.0](#v740) - - [Minor Changes](#minor-changes-3) - [Patch Changes](#patch-changes-10) - [Unstable Changes](#unstable-changes-6) + - [v7.4.0](#v740) + - [Minor Changes](#minor-changes-3) + - [Patch Changes](#patch-changes-11) + - [Unstable Changes](#unstable-changes-7) - [Changes by Package](#changes-by-package-3) - [v7.3.0](#v730) - [Minor Changes](#minor-changes-4) - - [Patch Changes](#patch-changes-11) - - [Unstable Changes](#unstable-changes-7) + - [Patch Changes](#patch-changes-12) + - [Unstable Changes](#unstable-changes-8) - [Client-side `context` (unstable)](#client-side-context-unstable) - [Middleware (unstable)](#middleware-unstable) - [Middleware `context` parameter](#middleware-context-parameter) @@ -74,28 +77,28 @@ We manage release notes in this file instead of the paginated Github Releases Pa - [Prerendering with a SPA Fallback](#prerendering-with-a-spa-fallback) - [Allow a root `loader` in SPA Mode](#allow-a-root-loader-in-spa-mode) - [Minor Changes](#minor-changes-5) - - [Patch Changes](#patch-changes-12) - - [Unstable Changes](#unstable-changes-8) + - [Patch Changes](#patch-changes-13) + - [Unstable Changes](#unstable-changes-9) - [Split Route Modules (unstable)](#split-route-modules-unstable) - [Changes by Package](#changes-by-package-5) - [v7.1.5](#v715) - - [Patch Changes](#patch-changes-13) - - [v7.1.4](#v714) - [Patch Changes](#patch-changes-14) - - [v7.1.3](#v713) + - [v7.1.4](#v714) - [Patch Changes](#patch-changes-15) - - [v7.1.2](#v712) + - [v7.1.3](#v713) - [Patch Changes](#patch-changes-16) - - [v7.1.1](#v711) + - [v7.1.2](#v712) - [Patch Changes](#patch-changes-17) + - [v7.1.1](#v711) + - [Patch Changes](#patch-changes-18) - [v7.1.0](#v710) - [Minor Changes](#minor-changes-6) - - [Patch Changes](#patch-changes-18) + - [Patch Changes](#patch-changes-19) - [Changes by Package](#changes-by-package-6) - [v7.0.2](#v702) - - [Patch Changes](#patch-changes-19) - - [v7.0.1](#v701) - [Patch Changes](#patch-changes-20) + - [v7.0.1](#v701) + - [Patch Changes](#patch-changes-21) - [v7.0.0](#v700) - [Breaking Changes](#breaking-changes) - [Package Restructuring](#package-restructuring) @@ -107,206 +110,206 @@ We manage release notes in this file instead of the paginated Github Releases Pa - [Exposed Router Promises](#exposed-router-promises) - [Other Notable Changes](#other-notable-changes) - [`routes.ts`](#routests) - - [Typesafety improvements](#typesafety-improvements) + - [Type-safety improvements](#type-safety-improvements) - [Prerendering](#prerendering) - [Major Changes (`react-router`)](#major-changes-react-router) - [Major Changes (`@react-router/*`)](#major-changes-react-router-1) - [Minor Changes](#minor-changes-7) - - [Patch Changes](#patch-changes-21) + - [Patch Changes](#patch-changes-22) - [Changes by Package](#changes-by-package-7) - [React Router v6 Releases](#react-router-v6-releases) - [v6.30.1](#v6301) - - [Patch Changes](#patch-changes-22) + - [Patch Changes](#patch-changes-23) - [v6.30.0](#v6300) - [Minor Changes](#minor-changes-8) - - [Patch Changes](#patch-changes-23) + - [Patch Changes](#patch-changes-24) - [v6.29.0](#v6290) - [Minor Changes](#minor-changes-9) - - [Patch Changes](#patch-changes-24) - - [v6.28.2](#v6282) - [Patch Changes](#patch-changes-25) - - [v6.28.1](#v6281) + - [v6.28.2](#v6282) - [Patch Changes](#patch-changes-26) + - [v6.28.1](#v6281) + - [Patch Changes](#patch-changes-27) - [v6.28.0](#v6280) - [What's Changed](#whats-changed-4) - [Minor Changes](#minor-changes-10) - - [Patch Changes](#patch-changes-27) + - [Patch Changes](#patch-changes-28) - [v6.27.0](#v6270) - [What's Changed](#whats-changed-5) - [Stabilized APIs](#stabilized-apis) - [Minor Changes](#minor-changes-11) - - [Patch Changes](#patch-changes-28) - - [v6.26.2](#v6262) - [Patch Changes](#patch-changes-29) - - [v6.26.1](#v6261) + - [v6.26.2](#v6262) - [Patch Changes](#patch-changes-30) + - [v6.26.1](#v6261) + - [Patch Changes](#patch-changes-31) - [v6.26.0](#v6260) - [Minor Changes](#minor-changes-12) - - [Patch Changes](#patch-changes-31) - - [v6.25.1](#v6251) - [Patch Changes](#patch-changes-32) + - [v6.25.1](#v6251) + - [Patch Changes](#patch-changes-33) - [v6.25.0](#v6250) - [What's Changed](#whats-changed-6) - [Stabilized `v7_skipActionErrorRevalidation`](#stabilized-v7_skipactionerrorrevalidation) - [Minor Changes](#minor-changes-13) - - [Patch Changes](#patch-changes-33) - - [v6.24.1](#v6241) - [Patch Changes](#patch-changes-34) + - [v6.24.1](#v6241) + - [Patch Changes](#patch-changes-35) - [v6.24.0](#v6240) - [What's Changed](#whats-changed-7) - [Lazy Route Discovery (a.k.a. "Fog of War")](#lazy-route-discovery-aka-fog-of-war) - [Minor Changes](#minor-changes-14) - - [Patch Changes](#patch-changes-35) - - [v6.23.1](#v6231) - [Patch Changes](#patch-changes-36) + - [v6.23.1](#v6231) + - [Patch Changes](#patch-changes-37) - [v6.23.0](#v6230) - [What's Changed](#whats-changed-8) - [Data Strategy (unstable)](#data-strategy-unstable) - [Skip Action Error Revalidation (unstable)](#skip-action-error-revalidation-unstable) - [Minor Changes](#minor-changes-15) - [v6.22.3](#v6223) - - [Patch Changes](#patch-changes-37) - - [v6.22.2](#v6222) - [Patch Changes](#patch-changes-38) - - [v6.22.1](#v6221) + - [v6.22.2](#v6222) - [Patch Changes](#patch-changes-39) + - [v6.22.1](#v6221) + - [Patch Changes](#patch-changes-40) - [v6.22.0](#v6220) - [What's Changed](#whats-changed-9) - [Core Web Vitals Technology Report Flag](#core-web-vitals-technology-report-flag) - [Minor Changes](#minor-changes-16) - - [Patch Changes](#patch-changes-40) - - [v6.21.3](#v6213) - [Patch Changes](#patch-changes-41) - - [v6.21.2](#v6212) + - [v6.21.3](#v6213) - [Patch Changes](#patch-changes-42) - - [v6.21.1](#v6211) + - [v6.21.2](#v6212) - [Patch Changes](#patch-changes-43) + - [v6.21.1](#v6211) + - [Patch Changes](#patch-changes-44) - [v6.21.0](#v6210) - [What's Changed](#whats-changed-10) - [`future.v7_relativeSplatPath`](#futurev7_relativesplatpath) - [Partial Hydration](#partial-hydration) - [Minor Changes](#minor-changes-17) - - [Patch Changes](#patch-changes-44) - - [v6.20.1](#v6201) - [Patch Changes](#patch-changes-45) + - [v6.20.1](#v6201) + - [Patch Changes](#patch-changes-46) - [v6.20.0](#v6200) - [Minor Changes](#minor-changes-18) - - [Patch Changes](#patch-changes-46) + - [Patch Changes](#patch-changes-47) - [v6.19.0](#v6190) - [What's Changed](#whats-changed-11) - [`unstable_flushSync` API](#unstable_flushsync-api) - [Minor Changes](#minor-changes-19) - - [Patch Changes](#patch-changes-47) + - [Patch Changes](#patch-changes-48) - [v6.18.0](#v6180) - [What's Changed](#whats-changed-12) - [New Fetcher APIs](#new-fetcher-apis) - [Persistence Future Flag (`future.v7_fetcherPersist`)](#persistence-future-flag-futurev7_fetcherpersist) - [Minor Changes](#minor-changes-20) - - [Patch Changes](#patch-changes-48) + - [Patch Changes](#patch-changes-49) - [v6.17.0](#v6170) - [What's Changed](#whats-changed-13) - [View Transitions 🚀](#view-transitions-) - [Minor Changes](#minor-changes-21) - - [Patch Changes](#patch-changes-49) + - [Patch Changes](#patch-changes-50) - [v6.16.0](#v6160) - [Minor Changes](#minor-changes-22) - - [Patch Changes](#patch-changes-50) + - [Patch Changes](#patch-changes-51) - [v6.15.0](#v6150) - [Minor Changes](#minor-changes-23) - - [Patch Changes](#patch-changes-51) - - [v6.14.2](#v6142) - [Patch Changes](#patch-changes-52) - - [v6.14.1](#v6141) + - [v6.14.2](#v6142) - [Patch Changes](#patch-changes-53) + - [v6.14.1](#v6141) + - [Patch Changes](#patch-changes-54) - [v6.14.0](#v6140) - [What's Changed](#whats-changed-14) - [JSON/Text Submissions](#jsontext-submissions) - [Minor Changes](#minor-changes-24) - - [Patch Changes](#patch-changes-54) + - [Patch Changes](#patch-changes-55) - [v6.13.0](#v6130) - [What's Changed](#whats-changed-15) - [`future.v7_startTransition`](#futurev7_starttransition) - [Minor Changes](#minor-changes-25) - - [Patch Changes](#patch-changes-55) - - [v6.12.1](#v6121) - [Patch Changes](#patch-changes-56) + - [v6.12.1](#v6121) + - [Patch Changes](#patch-changes-57) - [v6.12.0](#v6120) - [What's Changed](#whats-changed-16) - [`React.startTransition` support](#reactstarttransition-support) - [Minor Changes](#minor-changes-26) - - [Patch Changes](#patch-changes-57) - - [v6.11.2](#v6112) - [Patch Changes](#patch-changes-58) - - [v6.11.1](#v6111) + - [v6.11.2](#v6112) - [Patch Changes](#patch-changes-59) + - [v6.11.1](#v6111) + - [Patch Changes](#patch-changes-60) - [v6.11.0](#v6110) - [Minor Changes](#minor-changes-27) - - [Patch Changes](#patch-changes-60) + - [Patch Changes](#patch-changes-61) - [v6.10.0](#v6100) - [What's Changed](#whats-changed-17) - [Minor Changes](#minor-changes-28) - [`future.v7_normalizeFormMethod`](#futurev7_normalizeformmethod) - - [Patch Changes](#patch-changes-61) + - [Patch Changes](#patch-changes-62) - [v6.9.0](#v690) - [What's Changed](#whats-changed-18) - [`Component`/`ErrorBoundary` route properties](#componenterrorboundary-route-properties) - [Introducing Lazy Route Modules](#introducing-lazy-route-modules) - [Minor Changes](#minor-changes-29) - - [Patch Changes](#patch-changes-62) - - [v6.8.2](#v682) - [Patch Changes](#patch-changes-63) - - [v6.8.1](#v681) + - [v6.8.2](#v682) - [Patch Changes](#patch-changes-64) + - [v6.8.1](#v681) + - [Patch Changes](#patch-changes-65) - [v6.8.0](#v680) - [Minor Changes](#minor-changes-30) - - [Patch Changes](#patch-changes-65) + - [Patch Changes](#patch-changes-66) - [v6.7.0](#v670) - [Minor Changes](#minor-changes-31) - - [Patch Changes](#patch-changes-66) - - [v6.6.2](#v662) - [Patch Changes](#patch-changes-67) - - [v6.6.1](#v661) + - [v6.6.2](#v662) - [Patch Changes](#patch-changes-68) + - [v6.6.1](#v661) + - [Patch Changes](#patch-changes-69) - [v6.6.0](#v660) - [What's Changed](#whats-changed-19) - [Minor Changes](#minor-changes-32) - - [Patch Changes](#patch-changes-69) + - [Patch Changes](#patch-changes-70) - [v6.5.0](#v650) - [What's Changed](#whats-changed-20) - [Minor Changes](#minor-changes-33) - - [Patch Changes](#patch-changes-70) - - [v6.4.5](#v645) - [Patch Changes](#patch-changes-71) - - [v6.4.4](#v644) + - [v6.4.5](#v645) - [Patch Changes](#patch-changes-72) - - [v6.4.3](#v643) + - [v6.4.4](#v644) - [Patch Changes](#patch-changes-73) - - [v6.4.2](#v642) + - [v6.4.3](#v643) - [Patch Changes](#patch-changes-74) - - [v6.4.1](#v641) + - [v6.4.2](#v642) - [Patch Changes](#patch-changes-75) + - [v6.4.1](#v641) + - [Patch Changes](#patch-changes-76) - [v6.4.0](#v640) - [What's Changed](#whats-changed-21) - [Remix Data APIs](#remix-data-apis) - - [Patch Changes](#patch-changes-76) + - [Patch Changes](#patch-changes-77) - [v6.3.0](#v630) - [Minor Changes](#minor-changes-34) - [v6.2.2](#v622) - - [Patch Changes](#patch-changes-77) - - [v6.2.1](#v621) - [Patch Changes](#patch-changes-78) + - [v6.2.1](#v621) + - [Patch Changes](#patch-changes-79) - [v6.2.0](#v620) - [Minor Changes](#minor-changes-35) - - [Patch Changes](#patch-changes-79) - - [v6.1.1](#v611) - [Patch Changes](#patch-changes-80) + - [v6.1.1](#v611) + - [Patch Changes](#patch-changes-81) - [v6.1.0](#v610) - [Minor Changes](#minor-changes-36) - - [Patch Changes](#patch-changes-81) - - [v6.0.2](#v602) - [Patch Changes](#patch-changes-82) - - [v6.0.1](#v601) + - [v6.0.2](#v602) - [Patch Changes](#patch-changes-83) + - [v6.0.1](#v601) + - [Patch Changes](#patch-changes-84) - [v6.0.0](#v600) @@ -348,6 +351,23 @@ Date: YYYY-MM-DD **Full Changelog**: [`v7.X.Y...v7.X.Y`](https://github.com/remix-run/react-router/compare/react-router@7.X.Y...react-router@7.X.Y) --> +## v7.7.1 + +Date: 2025-07-24 + +### Patch Changes + +- `@react-router/dev` - Update to Prettier v3 for formatting when running `react-router reveal --no-typescript` ([#14049](https://github.com/remix-run/react-router/pull/14049)) + +### Unstable Changes + +⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ + +- `react-router` - RSC Data Mode: fix bug where routes with errors weren't forced to revalidate when `shouldRevalidate` returned `false` ([#14026](https://github.com/remix-run/react-router/pull/14026)) +- `react-router` - RSC Data Mode: fix `Matched leaf route at location "/..." does not have an element or Component` warnings when error boundaries are rendered ([#14021](https://github.com/remix-run/react-router/pull/14021)) + +**Full Changelog**: [`v7.7.0...v7.7.1`](https://github.com/remix-run/react-router/compare/react-router@7.7.0...react-router@7.7.1) + ## v7.7.0 Date: 2025-07-16 @@ -385,7 +405,6 @@ For more information, check out the [blog post](https://remix.run/blog/react-rou - `react-router` - Do not throw if the url hash is not a valid URI component ([#13247](https://github.com/remix-run/react-router/pull/13247)) - `react-router` - Remove `Content-Length` header from Single Fetch responses ([#13902](https://github.com/remix-run/react-router/pull/13902)) - `react-router` - Fix a regression in `createRoutesStub` introduced with the middleware feature ([#13946](https://github.com/remix-run/react-router/pull/13946)) - - As part of that work we altered the signature to align with the new middleware APIs without making it backwards compatible with the prior `AppLoadContext` API - This permitted `createRoutesStub` to work if you were opting into middleware and the updated `context` typings, but broke `createRoutesStub` for users not yet opting into middleware - We've reverted this change and re-implemented it in such a way that both sets of users can leverage it @@ -417,8 +436,8 @@ For more information, check out the [blog post](https://remix.run/blog/react-rou ⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_ -- Add unstable RSC support ([#13700](https://github.com/remix-run/react-router/pull/13700)) - - For more information, see the [RSC documentation](https://reactrouter.com/start/rsc/installation) +- Add unstable RSC support for Data Mode ([#13700](https://github.com/remix-run/react-router/pull/13700)) + - For more information, see the [RSC documentation](https://reactrouter.com/how-to/react-server-components) ### Changes by Package @@ -442,7 +461,6 @@ Date: 2025-06-27 ### Patch Changes - `react-router` - Do not serialize types for `useRouteLoaderData` ([#13752](https://github.com/remix-run/react-router/pull/13752)) - - For types to distinguish a `clientLoader` from a `serverLoader`, you MUST annotate `clientLoader` args: ```ts @@ -543,7 +561,6 @@ Date: 2025-05-25 - `@react-router/dev` - Add additional logging to `build` command output when cleaning assets from server build ([#13547](https://github.com/remix-run/react-router/pull/13547)) - `@react-router/dev` - Don't clean assets from server build when `build.ssrEmitAssets` has been enabled in Vite config ([#13547](https://github.com/remix-run/react-router/pull/13547)) - `@react-router/dev` - Fix typegen when same route is used at multiple paths ([#13574](https://github.com/remix-run/react-router/pull/13574)) - - For example, `routes/route.tsx` is used at 4 different paths here: ```ts @@ -562,7 +579,6 @@ Date: 2025-05-25 - Now, typegen creates unions as necessary for alternate paths for the same route file - `@react-router/dev` - Better types for `params` ([#13543](https://github.com/remix-run/react-router/pull/13543)) - - For example: ```ts @@ -609,7 +625,6 @@ Date: 2025-05-25 ``` - `@react-router/dev` - Fix `href` for optional segments ([#13595](https://github.com/remix-run/react-router/pull/13595)) - - Type generation now expands paths with optionals into their corresponding non-optional paths - For example, the path `/user/:id?` gets expanded into `/user` and `/user/:id` to more closely model visitable URLs - `href` then uses these expanded (non-optional) paths to construct type-safe paths for your app: @@ -717,7 +732,6 @@ Behind the scenes, React Router will generate the corresponding `declare module` - `react-router` - Added a new `routeDiscovery` option in `react-router.config.ts` to configure Lazy Route Discovery behavior ([#13451](https://github.com/remix-run/react-router/pull/13451)) - `react-router` - Add support for route component props in `createRoutesStub` ([#13528](https://github.com/remix-run/react-router/pull/13528)) - - This allows you to unit test your route components using the props instead of the hooks: ```tsx @@ -836,7 +850,6 @@ Date: 2025-04-17 ### Patch Changes - `react-router` - When using the object-based `route.lazy` API, the `HydrateFallback` and `hydrateFallbackElement` properties are now skipped when lazy loading routes after hydration ([#13376](https://github.com/remix-run/react-router/pull/13376)) - - If you move the code for these properties into a separate file, since the hydrate properties were unused already (if the route wasn't present during hydration), you can avoid downloading them at all. For example: ```ts @@ -1141,7 +1154,7 @@ Here's a simple example of a client-side logging middleware that can be placed o ```tsx const clientLogger: Route.unstable_ClientMiddlewareFunction = async ( { request }, - next + next, ) => { let start = performance.now(); @@ -1160,7 +1173,7 @@ For a server-side middleware, the `next` function will return the HTTP `Response ```tsx const serverLogger: Route.unstable_MiddlewareFunction = async ( { request, params, context }, - next + next, ) => { let start = performance.now(); @@ -1181,7 +1194,7 @@ You can throw a `redirect` from a middleware to short circuit any remaining proc import { sessionContext } from "../context"; const serverAuth: Route.unstable_MiddlewareFunction = ( { request, params, context }, - next + next, ) => { let session = context.get(sessionContext); let user = session.get("user"); @@ -1434,7 +1447,7 @@ import { MassiveComponent } from "~/components"; export async function clientLoader() { return await fetch("https://redirect.github.com/service/https://example.com/api").then((response) => - response.json() + response.json(), ); } @@ -1469,7 +1482,7 @@ To achieve this optimization, React Router will split the route module into mult ```tsx filename=routes/example.tsx?route-chunk=clientLoader export async function clientLoader() { return await fetch("https://redirect.github.com/service/https://example.com/api").then((response) => - response.json() + response.json(), ); } ``` @@ -1515,7 +1528,7 @@ const shared = () => console.log("hello"); export async function clientLoader() { shared(); return await fetch("https://redirect.github.com/service/https://example.com/api").then((response) => - response.json() + response.json(), ); } @@ -1542,7 +1555,7 @@ import { shared } from "./shared"; export async function clientLoader() { shared(); return await fetch("https://redirect.github.com/service/https://example.com/api").then((response) => - response.json() + response.json(), ); } @@ -1560,7 +1573,7 @@ import { shared } from "./shared"; export async function clientLoader() { shared(); return await fetch("https://redirect.github.com/service/https://example.com/api").then((response) => - response.json() + response.json(), ); } ``` @@ -1954,7 +1967,7 @@ Also note that, if you were using Remix's `routes` option to define config-based +]; ``` -#### Typesafety improvements +#### Type-safety improvements React Router now generates types for each of your route modules and passes typed props to route module component exports ([#11961](https://github.com/remix-run/react-router/pull/11961), [#12019](https://github.com/remix-run/react-router/pull/12019)). You can access those types by importing them from `./+types/`. @@ -2426,7 +2439,7 @@ const router = createBrowserRouter( patch("root", [route]); } }, - } + }, ); ``` @@ -3206,7 +3219,7 @@ let routes = createRoutesFromElements( } /> import("./a")} /> import("./b")} /> - + , ); ``` diff --git a/GOVERNANCE.md b/GOVERNANCE.md index a64a9f99c3..0187a84d64 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -18,6 +18,7 @@ React Router has been around since 2014 largely under the development and oversight of [Michael Jackson](https://x.com/mjackson) and [Ryan Florence](https://x.com/ryanflorence). After the launch of [Remix](https://remix.run/) in 2021, the subsequent creation of the Remix team, and the merging of Remix v2 into React Router v7[^1][^2], the project shifted from a [Founder-Leader](https://www.redhat.com/en/blog/understanding-open-source-governance-models) model to a "Steering Committee" (SC) model that operates on a Request for Comments (RFC) process. [^1]: https://remix.run/blog/merging-remix-and-react-router + [^2]: https://remix.run/blog/incremental-path-to-react-19 This document will outline the process in which React Router will continue to evolve and how new features will make their way into the codebase. This is an evergreen document and will be updated as needed to reflect future changes in the process. diff --git a/contributors.yml b/contributors.yml index 55a5cc202a..74c4f8deb7 100644 --- a/contributors.yml +++ b/contributors.yml @@ -137,6 +137,7 @@ - haivuw - hampelm - harshmangalam +- HelpMe-Pls - HenriqueLimas - hernanif1 - hi-ogawa @@ -152,6 +153,7 @@ - igniscyan - imjordanxd - infoxicator +- ioNihal - IsaiStormBlesed - Isammoc - iskanderbroere @@ -268,6 +270,7 @@ - namoscato - ned-park - nenene3 +- ngbrown - nichtsam - nikeee - nilubisan @@ -328,6 +331,7 @@ - sbolel - scarf005 - sealer3 +- seasick - senseibarni - sergiodxa - serranoarevalo diff --git a/decisions/0003-data-strategy.md b/decisions/0003-data-strategy.md index 61d0a12566..cb8884363e 100644 --- a/decisions/0003-data-strategy.md +++ b/decisions/0003-data-strategy.md @@ -113,7 +113,7 @@ Therefore, we're introducing the concept of a `DataStrategyMatch` which is just ```js function dataStrategy({ matches, defaultStrategy }) { return Promise.all( - matches.map((m) => match.route.then((route) => route.loader(/* ... */))) + matches.map((m) => match.route.then((route) => route.loader(/* ... */))), ); } ``` diff --git a/decisions/0005-remixing-react-router.md b/decisions/0005-remixing-react-router.md index 3826ab784f..e5b6e7389d 100644 --- a/decisions/0005-remixing-react-router.md +++ b/decisions/0005-remixing-react-router.md @@ -283,7 +283,7 @@ If folks still prefer the JSX notation, they can leverage `createRoutesFromEleme const routes = createRoutesFromElements( }> } /> - + , ); const router = createBrowserRouter(routes); diff --git a/decisions/0010-splitting-up-client-and-server-code-in-vite.md b/decisions/0010-splitting-up-client-and-server-code-in-vite.md index 70755110d7..144ce7d57c 100644 --- a/decisions/0010-splitting-up-client-and-server-code-in-vite.md +++ b/decisions/0010-splitting-up-client-and-server-code-in-vite.md @@ -96,6 +96,7 @@ Checking for `.server` modules only requires checking the module's path and does `vite-env-only` does require AST parsing and transformations so it will always be slower than `.server` modules. [^1]: Vite provides a lower-level module graph API, but the module graph is not guaranteed to be complete as it is only populated as modules are requested. + [^2]: When a file changes on disk, Vite invalidates the corresponding module in its cache to power features like HMR. [decision-0009]: ./0009-do-not-rely-on-treeshaking-for-correctness.md diff --git a/decisions/0014-context-middleware.md b/decisions/0014-context-middleware.md index a5be46d17f..10dd5db633 100644 --- a/decisions/0014-context-middleware.md +++ b/decisions/0014-context-middleware.md @@ -105,7 +105,7 @@ The middleware API we landed on to ship looks as follows: ```ts const myMiddleware: Route.unstable_MiddlewareFunction = async ( { request, context }, - next + next, ) => { // Do stuff before the handlers are called context.user = await getUser(request); @@ -125,7 +125,7 @@ export const middleware = [myMiddleware]; // `clientLoader`/`clientAction` const myClientMiddleware: Route.unstable_ClientMiddlewareFunction = ( { context }, - next + next, ) => { //... }; diff --git a/docs/api/components/Await.md b/docs/api/components/Await.md index 1d45df6513..259f55d1d4 100644 --- a/docs/api/components/Await.md +++ b/docs/api/components/Await.md @@ -4,6 +4,18 @@ title: Await # Await + + [MODES: framework, data] ## Summary @@ -12,6 +24,8 @@ title: Await Used to render promise values with automatic error handling. +**Note:** `` expects to be rendered inside a [``](https://react.dev/reference/react/Suspense) + ```tsx import { Await, useLoaderData } from "react-router"; @@ -19,9 +33,7 @@ export async function loader() { // not awaited const reviews = getReviews(); // awaited (blocks the transition) - const book = await fetch("https://redirect.github.com/api/book").then((res) => - res.json() - ); + const book = await fetch("https://redirect.github.com/api/book").then((res) => res.json()); return { book, reviews }; } @@ -47,14 +59,20 @@ function Book() { } ``` -`` expects to be rendered inside of a `` +## Signature + +```tsx +function Await({ + children, + errorElement, + resolve, +}: AwaitProps) +``` ## Props ### children -[modes: framework, data] - When using a function, the resolved value is provided as the parameter. ```tsx [2] @@ -63,13 +81,13 @@ When using a function, the resolved value is provided as the parameter. ``` -When using React elements, [useAsyncValue](../hooks/useAsyncValue) will provide the +When using React elements, [`useAsyncValue`](../hooks/useAsyncValue) will provide the resolved value: ```tsx [2] -; + function Reviews() { const resolvedReviews = useAsyncValue(); @@ -79,9 +97,8 @@ function Reviews() { ### errorElement -[modes: framework, data] - -The error element renders instead of the children when the promise rejects. +The error element renders instead of the `children` when the [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +rejects. ```tsx ``` -To provide a more contextual error, you can use the [useAsyncError](../hooks/useAsyncError) in a +To provide a more contextual error, you can use the [`useAsyncError`](../hooks/useAsyncError) in a child component ```tsx @@ -101,7 +118,7 @@ child component resolve={reviewsPromise} > -; + function ReviewsError() { const error = useAsyncError(); @@ -109,18 +126,18 @@ function ReviewsError() { } ``` -If you do not provide an errorElement, the rejected value will bubble up to -the nearest route-level ErrorBoundary and be accessible -via [useRouteError](../hooks/useRouteError) hook. +If you do not provide an `errorElement`, the rejected value will bubble up +to the nearest route-level [`ErrorBoundary`](../../start/framework/route-module#errorboundary) +and be accessible via the [`useRouteError`](../hooks/useRouteError) hook. ### resolve -[modes: framework, data] +Takes a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +returned from a [`loader`](../../start/framework/route-module#loader) to be +resolved and rendered. -Takes a promise returned from a [LoaderFunction](https://api.reactrouter.com/v7/types/react_router.LoaderFunction.html) value to be resolved and rendered. - -```jsx -import { useLoaderData, Await } from "react-router"; +```tsx +import { Await, useLoaderData } from "react-router"; export async function loader() { let reviews = getReviews(); // not awaited @@ -153,3 +170,4 @@ export default function Book() { ); } ``` + diff --git a/docs/api/components/Form.md b/docs/api/components/Form.md index a393a37e4f..b642099430 100644 --- a/docs/api/components/Form.md +++ b/docs/api/components/Form.md @@ -4,17 +4,40 @@ title: Form # Form + + [MODES: framework, data] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.Form.html) -A progressively enhanced HTML [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) that submits data to actions via `fetch`, activating pending states in `useNavigation` which enables advanced user interfaces beyond a basic HTML form. After a form's action completes, all data on the page is automatically revalidated to keep the UI in sync with the data. +A progressively enhanced HTML [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) +that submits data to actions via [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), +activating pending states in [`useNavigation`](../hooks/useNavigation) which enables advanced +user interfaces beyond a basic HTML [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form). +After a form's `action` completes, all data on the page is automatically +revalidated to keep the UI in sync with the data. -Because it uses the HTML form API, server rendered pages are interactive at a basic level before JavaScript loads. Instead of React Router managing the submission, the browser manages the submission as well as the pending states (like the spinning favicon). After JavaScript loads, React Router takes over enabling web application user experiences. +Because it uses the HTML form API, server rendered pages are interactive at a +basic level before JavaScript loads. Instead of React Router managing the +submission, the browser manages the submission as well as the pending states +(like the spinning favicon). After JavaScript loads, React Router takes over +enabling web application user experiences. -Form is most useful for submissions that should also change the URL or otherwise add an entry to the browser history stack. For forms that shouldn't manipulate the browser history stack, use [``][fetcher_form]. +`Form` is most useful for submissions that should also change the URL or +otherwise add an entry to the browser history stack. For forms that shouldn't +manipulate the browser history stack, use [``][fetcher_form]. ```tsx import { Form } from "react-router"; @@ -33,96 +56,90 @@ function NewEvent() { ### action -[modes: framework, data] - -The URL to submit the form data to. If `undefined`, this defaults to the closest route in context. +The URL to submit the form data to. If `undefined`, this defaults to the +closest route in context. ### discover -[modes: framework, data] +Defines the link discovery behavior. See [`DiscoverBehavior`](https://api.reactrouter.com/v7/types/react_router.DiscoverBehavior.html). -Determines application manifest discovery behavior. +```tsx + // default ("render") + + +``` -### encType +- **render** - default, discover the route when the link renders +- **none** - don't eagerly discover, only discover if the link is clicked -[modes: framework, data] +### encType The encoding type to use for the form submission. -### fetcherKey +```tsx + // Default + + +``` -[modes: framework, data] +### fetcherKey Indicates a specific fetcherKey to use when using `navigate={false}` so you -can pick up the fetcher's state in a different component in a [useFetcher](../hooks/useFetcher). +can pick up the fetcher's state in a different component in a [`useFetcher`](../hooks/useFetcher). ### method -[modes: framework, data] - The HTTP verb to use when the form is submitted. Supports "get", "post", "put", "delete", and "patch". -Native `` only supports `get` and `post`, avoid the other verbs if -you'd like to support progressive enhancement +Native [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) +only supports `get` and `post`, avoid the other verbs if you'd like to +support progressive enhancement ### navigate -[modes: framework, data] - -Skips the navigation and uses a [useFetcher](../hooks/useFetcher) internally -when `false`. This is essentially a shorthand for `useFetcher()` + -`` where you don't care about the resulting data in this -component. +When `false`, skips the navigation and submits via a fetcher internally. +This is essentially a shorthand for [`useFetcher`](../hooks/useFetcher) + `` where +you don't care about the resulting data in this component. ### onSubmit -[modes: framework, data] - A function to call when the form is submitted. If you call -`event.preventDefault()` then this form will not do anything. +[`event.preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) +then this form will not do anything. ### preventScrollReset -[modes: framework, data] - Prevent the scroll position from resetting to the top of the viewport on -completion of the navigation when using the component +completion of the navigation when using the +```` component ### relative -[modes: framework, data] - Determines whether the form action is relative to the route hierarchy or the pathname. Use this if you want to opt out of navigating the route -hierarchy and want to instead route based on /-delimited URL segments +hierarchy and want to instead route based on slash-delimited URL segments. +See [`RelativeRoutingType`](https://api.reactrouter.com/v7/types/react_router.RelativeRoutingType.html). ### reloadDocument -[modes: framework, data] - -Forces a full document navigation instead of client side routing + data +Forces a full document navigation instead of client side routing and data fetch. ### replace -[modes: framework, data] - -Replaces the current entry in the browser history stack when the form -navigates. Use this if you don't want the user to be able to click "back" -to the page with the form on it. +Replaces the current entry in the browser [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) +stack when the form navigates. Use this if you don't want the user to be +able to click "back" to the page with the form on it. ### state -[modes: framework, data] - -State object to add to the history stack entry for this navigation +State object to add to the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) +stack entry for this navigation ### viewTransition -[modes: framework, data] +Enables a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) +for this navigation. To apply specific styles during the transition, see +[`useViewTransitionState`](../hooks/useViewTransitionState). -Enables a [View -Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) -for this navigation. To apply specific styles during the transition see -[useViewTransitionState](../hooks/useViewTransitionState). diff --git a/docs/api/components/Link.md b/docs/api/components/Link.md index 041728a9be..ab1b306014 100644 --- a/docs/api/components/Link.md +++ b/docs/api/components/Link.md @@ -4,13 +4,26 @@ title: Link # Link + + [MODES: framework, data, declarative] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.Link.html) -A progressively enhanced `` wrapper to enable navigation with client-side routing. +A progressively enhanced [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) +wrapper to enable navigation with client-side routing. ```tsx import { Link } from "react-router"; @@ -35,7 +48,9 @@ import { Link } from "react-router"; Defines the link discovery behavior ```tsx + // default ("render") + ``` - **render** - default, discover the route when the link renders @@ -48,7 +63,11 @@ Defines the link discovery behavior Defines the data and module prefetching behavior for the link. ```tsx + // default + + + ``` - **none** - default, no prefetching @@ -56,7 +75,8 @@ Defines the data and module prefetching behavior for the link. - **render** - prefetches when the link renders - **viewport** - prefetches when the link is in the viewport, very useful for mobile -Prefetching is done with HTML `` tags. They are inserted after the link. +Prefetching is done with HTML [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link) +tags. They are inserted after the link. ```tsx @@ -64,13 +84,18 @@ Prefetching is done with HTML `` tags. They are inserted af // might conditionally render ``` -Because of this, if you are using `nav :last-child` you will need to use `nav :last-of-type` so the styles don't conditionally fall off your last link (and any other similar selectors). +Because of this, if you are using `nav :last-child` you will need to use +`nav :last-of-type` so the styles don't conditionally fall off your last link +(and any other similar selectors). ### preventScrollReset [modes: framework, data] -Prevents the scroll position from being reset to the top of the window when the link is clicked and the app is using [ScrollRestoration](../components/ScrollRestoration). This only prevents new locations reseting scroll to the top, scroll position will be restored for back/forward button navigation. +Prevents the scroll position from being reset to the top of the window when +the link is clicked and the app is using [`ScrollRestoration`](../components/ScrollRestoration). This only +prevents new locations resetting scroll to the top, scroll position will be +restored for back/forward button navigation. ```tsx @@ -88,18 +113,25 @@ Defines the relative path behavior for the link. ``` -Consider a route hierarchy where a parent route pattern is "blog" and a child route pattern is "blog/:slug/edit". +Consider a route hierarchy where a parent route pattern is `"blog"` and a child +route pattern is `"blog/:slug/edit"`. -- **route** - default, resolves the link relative to the route pattern. In the example above a relative link of `".."` will remove both `:slug/edit` segments back to "/blog". -- **path** - relative to the path so `..` will only remove one URL segment up to "/blog/:slug" +- **route** - default, resolves the link relative to the route pattern. In the +example above, a relative link of `".."` will remove both `:slug/edit` segments +back to `"/blog"`. +- **path** - relative to the path so `".."` will only remove one URL segment up +to `"/blog/:slug"` -Note that index routes and layout routes have no paths so they are not included in the relative path calculation. +Note that index routes and layout routes do not have paths so they are not +included in the relative path calculation. ### reloadDocument [modes: framework, data, declarative] -Will use document navigation instead of client side routing when the link is clicked: the browser will handle the transition normally (as if it were an ``). +Will use document navigation instead of client side routing when the link is +clicked: the browser will handle the transition normally (as if it were an +[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)). ```tsx @@ -109,7 +141,8 @@ Will use document navigation instead of client side routing when the link is cli [modes: framework, data, declarative] -Replaces the current entry in the history stack instead of pushing a new one onto it. +Replaces the current entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) +stack instead of pushing a new one onto it. ```tsx @@ -145,13 +178,14 @@ function SomeComp() { } ``` -This state is inaccessible on the server as it is implemented on top of [`history.state`](https://developer.mozilla.org/en-US/docs/Web/API/History/state) +This state is inaccessible on the server as it is implemented on top of +[`history.state`](https://developer.mozilla.org/en-US/docs/Web/API/History/state) ### to [modes: framework, data, declarative] -Can be a string or a partial [Path](https://api.reactrouter.com/v7/interfaces/react_router.Path.html): +Can be a string or a partial [`Path`](https://api.reactrouter.com/v7/interfaces/react_router.Path.html): ```tsx @@ -167,9 +201,10 @@ Can be a string or a partial [Path](https://api.reactrouter.com/v7/interfaces/re ### viewTransition -[modes: framework, data, declarative] +[modes: framework, data] -Enables a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) for this navigation. +Enables a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) +for this navigation. ```jsx @@ -177,4 +212,5 @@ Enables a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/Vie ``` -To apply specific styles for the transition, see [useViewTransitionState](../hooks/useViewTransitionState) +To apply specific styles for the transition, see [`useViewTransitionState`](../hooks/useViewTransitionState) + diff --git a/docs/api/components/Links.md b/docs/api/components/Links.md index ccad683437..46e3bd7b50 100644 --- a/docs/api/components/Links.md +++ b/docs/api/components/Links.md @@ -4,13 +4,27 @@ title: Links # Links + + [MODES: framework] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.Links.html) -Renders all of the `` tags created by route module [`links`](../../start/framework/route-module#links) export. You should render it inside the `` of your document. +Renders all of the `` tags created by the route module +[`links`](../../start/framework/route-module#links) export. You should render +it inside the `` of your document. ```tsx import { Links } from "react-router"; @@ -27,6 +41,9 @@ export default function Root() { } ``` -## Props +## Signature + +```tsx +function Links(): React.JSX.Element +``` -None diff --git a/docs/api/components/Meta.md b/docs/api/components/Meta.md index 8c90529312..ca2a04d434 100644 --- a/docs/api/components/Meta.md +++ b/docs/api/components/Meta.md @@ -4,13 +4,27 @@ title: Meta # Meta + + [MODES: framework] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.Meta.html) -Renders all the `` tags created by route module [`meta`](../../start/framework/route-module#meta) export. You should render it inside the `` of your HTML. +Renders all the `` tags created by the route module +[`meta`](../../start/framework/route-module#meta) exports. You should render +it inside the `` of your HTML. ```tsx import { Meta } from "react-router"; @@ -26,6 +40,9 @@ export default function Root() { } ``` -## Props +## Signature + +```tsx +function Meta(): React.JSX.Element +``` -None diff --git a/docs/api/components/NavLink.md b/docs/api/components/NavLink.md index 4dcbba963f..455fdee0b1 100644 --- a/docs/api/components/NavLink.md +++ b/docs/api/components/NavLink.md @@ -4,25 +4,40 @@ title: NavLink # NavLink + + [MODES: framework, data, declarative] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.NavLink.html) -Wraps [Link](../components/Link) with additional props for styling active and pending states. +Wraps [``](../components/Link) with additional props for styling active and +pending states. -- Automatically applies classes to the link based on its active and pending states, see NavLinkProps.className. -- Automatically applies `aria-current="page"` to the link when the link is active. See [`aria-current`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current) on MDN. +- Automatically applies classes to the link based on its `active` and `pending` +states, see [`NavLinkProps.className`](https://api.reactrouter.com/v7/interfaces/react_router.NavLinkProps.html#className) + - Note that `pending` is only available with Framework and Data modes. +- Automatically applies `aria-current="page"` to the link when the link is active. +See [`aria-current`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current) +on MDN. +- States are additionally available through the className, style, and children +render props. See [`NavLinkRenderProps`](https://api.reactrouter.com/v7/types/react_router.NavLinkRenderProps.html). ```tsx -import { NavLink } from "react-router"; -; -``` +Messages -States are available through the className, style, and children render props. See [NavLinkRenderProps](https://api.reactrouter.com/v7/types/react_router.NavLinkRenderProps). - -```tsx +// Using render props @@ -50,21 +65,22 @@ Changes the matching logic to make it case-sensitive: [modes: framework, data, declarative] -Can be regular React children or a function that receives an object with the active and pending states of the link. +Can be regular React children or a function that receives an object with the +`active` and `pending` states of the link. -```tsx - - {({ isActive }) => ( - Tasks - )} - -``` + ```tsx + + {({ isActive }) => ( + Tasks + )} + + ``` ### className [modes: framework, data, declarative] -Classes are automatically applied to NavLink that correspond to the state. +Classes are automatically applied to `NavLink` that correspond to the state. ```css a.active { @@ -78,7 +94,16 @@ a.transitioning { } ``` -Note that `pending` is only available with Framework and Data modes. +Or you can specify a function that receives [`NavLinkRenderProps`](https://api.reactrouter.com/v7/types/react_router.NavLinkRenderProps.html) and +returns the `className`: + +```tsx + ( + isActive ? "my-active-class" : + isPending ? "my-pending-class" : + "" +)} /> +``` ### discover @@ -87,7 +112,9 @@ Note that `pending` is only available with Framework and Data modes. Defines the link discovery behavior ```tsx + // default ("render") + ``` - **render** - default, discover the route when the link renders @@ -97,7 +124,9 @@ Defines the link discovery behavior [modes: framework, data, declarative] -Changes the matching logic for the `active` and `pending` states to only match to the "end" of the NavLinkProps.to. If the URL is longer, it will no longer be considered active. +Changes the matching logic for the `active` and `pending` states to only match +to the "end" of the [`NavLinkProps.to`](https://api.reactrouter.com/v7/interfaces/react_router.NavLinkProps.html#to). If the URL is longer, it will no +longer be considered active. | Link | URL | isActive | | ----------------------------- | ------------ | -------- | @@ -106,7 +135,9 @@ Changes the matching logic for the `active` and `pending` states to only match t | `` | `/tasks` | true | | `` | `/tasks/123` | false | -`` is an exceptional case because _every_ URL matches `/`. To avoid this matching every single route by default, it effectively ignores the `end` prop and only matches when you're at the root route. +`` is an exceptional case because _every_ URL matches `/`. +To avoid this matching every single route by default, it effectively ignores +the `end` prop and only matches when you're at the root route. ### prefetch @@ -115,7 +146,11 @@ Changes the matching logic for the `active` and `pending` states to only match t Defines the data and module prefetching behavior for the link. ```tsx + // default + + + ``` - **none** - default, no prefetching @@ -123,7 +158,8 @@ Defines the data and module prefetching behavior for the link. - **render** - prefetches when the link renders - **viewport** - prefetches when the link is in the viewport, very useful for mobile -Prefetching is done with HTML `` tags. They are inserted after the link. +Prefetching is done with HTML [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link) +tags. They are inserted after the link. ```tsx @@ -131,13 +167,18 @@ Prefetching is done with HTML `` tags. They are inserted af // might conditionally render ``` -Because of this, if you are using `nav :last-child` you will need to use `nav :last-of-type` so the styles don't conditionally fall off your last link (and any other similar selectors). +Because of this, if you are using `nav :last-child` you will need to use +`nav :last-of-type` so the styles don't conditionally fall off your last link +(and any other similar selectors). ### preventScrollReset [modes: framework, data] -Prevents the scroll position from being reset to the top of the window when the link is clicked and the app is using [ScrollRestoration](../components/ScrollRestoration). This only prevents new locations reseting scroll to the top, scroll position will be restored for back/forward button navigation. +Prevents the scroll position from being reset to the top of the window when +the link is clicked and the app is using [`ScrollRestoration`](../components/ScrollRestoration). This only +prevents new locations resetting scroll to the top, scroll position will be +restored for back/forward button navigation. ```tsx @@ -155,16 +196,25 @@ Defines the relative path behavior for the link. ``` -Consider a route hierarchy where a parent route pattern is "blog" and a child route pattern is "blog/:slug/edit". +Consider a route hierarchy where a parent route pattern is `"blog"` and a child +route pattern is `"blog/:slug/edit"`. + +- **route** - default, resolves the link relative to the route pattern. In the +example above, a relative link of `".."` will remove both `:slug/edit` segments +back to `"/blog"`. +- **path** - relative to the path so `".."` will only remove one URL segment up +to `"/blog/:slug"` -- **route** - default, resolves the link relative to the route pattern. In the example above a relative link of `".."` will remove both `:slug/edit` segments back to "/blog". -- **path** - relative to the path so `..` will only remove one URL segment up to "/blog/:slug" +Note that index routes and layout routes do not have paths so they are not +included in the relative path calculation. ### reloadDocument [modes: framework, data, declarative] -Will use document navigation instead of client side routing when the link is clicked: the browser will handle the transition normally (as if it were an ``). +Will use document navigation instead of client side routing when the link is +clicked: the browser will handle the transition normally (as if it were an +[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)). ```tsx @@ -174,7 +224,8 @@ Will use document navigation instead of client side routing when the link is cli [modes: framework, data, declarative] -Replaces the current entry in the history stack instead of pushing a new one onto it. +Replaces the current entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) +stack instead of pushing a new one onto it. ```tsx @@ -210,13 +261,15 @@ function SomeComp() { } ``` -This state is inaccessible on the server as it is implemented on top of [`history.state`](https://developer.mozilla.org/en-US/docs/Web/API/History/state) +This state is inaccessible on the server as it is implemented on top of +[`history.state`](https://developer.mozilla.org/en-US/docs/Web/API/History/state) ### style [modes: framework, data, declarative] -Regular React style object or a function that receives an object with the active and pending states of the link. +Styles can also be applied dynamically via a function that receives +[`NavLinkRenderProps`](https://api.reactrouter.com/v7/types/react_router.NavLinkRenderProps.html) and returns the styles: ```tsx @@ -227,13 +280,11 @@ Regular React style object or a function that receives an object with the active })} /> ``` -Note that `pending` is only available with Framework and Data modes. - ### to [modes: framework, data, declarative] -Can be a string or a partial [Path](https://api.reactrouter.com/v7/interfaces/react_router.Path): +Can be a string or a partial [`Path`](https://api.reactrouter.com/v7/interfaces/react_router.Path.html): ```tsx @@ -249,9 +300,10 @@ Can be a string or a partial [Path](https://api.reactrouter.com/v7/interfaces/re ### viewTransition -[modes: framework, data, declarative] +[modes: framework, data] -Enables a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) for this navigation. +Enables a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) +for this navigation. ```jsx @@ -259,4 +311,5 @@ Enables a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/Vie ``` -To apply specific styles for the transition, see [useViewTransitionState](../hooks/useViewTransitionState) +To apply specific styles for the transition, see [`useViewTransitionState`](../hooks/useViewTransitionState) + diff --git a/docs/api/components/Navigate.md b/docs/api/components/Navigate.md index d351c552d5..e512b36323 100644 --- a/docs/api/components/Navigate.md +++ b/docs/api/components/Navigate.md @@ -4,44 +4,57 @@ title: Navigate # Navigate + + [MODES: framework, data, declarative] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.Navigate.html) -A component-based version of [useNavigate](../hooks/useNavigate) to use in a [`React.Component -Class`](https://reactjs.org/docs/react-component.html) where hooks are not -able to be used. +A component-based version of [`useNavigate`](../hooks/useNavigate) to use in a +[`React.Component` class](https://react.dev/reference/react/Component) where +hooks cannot be used. -It's recommended to avoid using this component in favor of [useNavigate](../hooks/useNavigate) +It's recommended to avoid using this component in favor of [`useNavigate`](../hooks/useNavigate). ```tsx ``` +## Signature + +```tsx +function Navigate({ to, replace, state, relative }: NavigateProps): null +``` + ## Props ### relative -[modes: framework, data, declarative] - -_No documentation_ +How to interpret relative routing in the `to` prop. +See [`RelativeRoutingType`](https://api.reactrouter.com/v7/types/react_router.RelativeRoutingType.html). ### replace -[modes: framework, data, declarative] - -_No documentation_ +Whether to replace the current entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) +stack ### state -[modes: framework, data, declarative] - -_No documentation_ +State to pass to the new [`Location`](https://api.reactrouter.com/v7/interfaces/react_router.Location.html) to store in [`history.state`](https://developer.mozilla.org/en-US/docs/Web/API/History/state). ### to -[modes: framework, data, declarative] +The path to navigate to. This can be a string or a [`Path`](https://api.reactrouter.com/v7/interfaces/react_router.Path.html) object -_No documentation_ diff --git a/docs/api/components/Outlet.md b/docs/api/components/Outlet.md index 0d38ae8b8e..385e233435 100644 --- a/docs/api/components/Outlet.md +++ b/docs/api/components/Outlet.md @@ -4,13 +4,26 @@ title: Outlet # Outlet + + [MODES: framework, data, declarative] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.Outlet.html) -Renders the matching child route of a parent route or nothing if no child route matches. +Renders the matching child route of a parent route or nothing if no child +route matches. ```tsx import { Outlet } from "react-router"; @@ -25,16 +38,22 @@ export default function SomeParent() { } ``` +## Signature + +```tsx +function Outlet(props: OutletProps): React.ReactElement | null +``` + ## Props ### context -[modes: framework, data, declarative] - -Provides a context value to the element tree below the outlet. Use when the parent route needs to provide values to child routes. +Provides a context value to the element tree below the outlet. Use when +the parent route needs to provide values to child routes. ```tsx ``` -Access the context with [useOutletContext](../hooks/useOutletContext). +Access the context with [`useOutletContext`](../hooks/useOutletContext). + diff --git a/docs/api/components/PrefetchPageLinks.md b/docs/api/components/PrefetchPageLinks.md index 47bfe6afb5..69422cd6d3 100644 --- a/docs/api/components/PrefetchPageLinks.md +++ b/docs/api/components/PrefetchPageLinks.md @@ -4,62 +4,52 @@ title: PrefetchPageLinks # PrefetchPageLinks + + [MODES: framework] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.PrefetchPageLinks.html) -Renders `` tags for modules and data of another page to enable an instant navigation to that page. `` uses this internally, but you can render it to prefetch a page for any other reason. +Renders `` tags for modules and data of +another page to enable an instant navigation to that page. +[``](../../components/Link#prefetch) uses this internally, but +you can render it to prefetch a page for any other reason. + +For example, you may render one of this as the user types into a search field +to prefetch search results before they click through to their selection. ```tsx import { PrefetchPageLinks } from "react-router"; -; + ``` -For example, you may render one of this as the user types into a search field to prefetch search results before they click through to their selection. - -## Props - -### crossOrigin - -[modes: framework] - -How the element handles crossorigin requests - -### disabled - -[modes: framework] +## Signature -Whether the link is disabled - -### hrefLang - -[modes: framework] - -Language of the linked resource - -### integrity - -[modes: framework] - -Integrity metadata used in Subresource Integrity checks - -### media - -[modes: framework] +```tsx +function PrefetchPageLinks({ page, ...linkProps }: PageLinkDescriptor) +``` -Applicable media: "screen", "print", "(max-width: 764px)" +## Props ### page -[modes: framework] - -The absolute path of the page to prefetch. +The absolute path of the page to prefetch, e.g. `/absolute/path`. -### referrerPolicy +### linkProps -[modes: framework] +Additional props to spread onto the [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/link) +tags, such as `crossOrigin`, `integrity`, `rel`, etc. -Referrer policy for fetches initiated by the element diff --git a/docs/api/components/Route.md b/docs/api/components/Route.md index 90c59b871c..3db1398209 100644 --- a/docs/api/components/Route.md +++ b/docs/api/components/Route.md @@ -4,6 +4,18 @@ title: Route # Route + + [MODES: framework, data, declarative] ## Summary @@ -11,38 +23,121 @@ title: Route [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.Route.html) Configures an element to render when a pattern matches the current location. -It must be rendered within a [Routes](../components/Routes) element. Note that these routes +It must be rendered within a [`Routes`](../components/Routes) element. Note that these routes do not participate in data loading, actions, code splitting, or any other route module features. +```tsx +// Usually used in a declarative router +function App() { + return ( + + + } /> + } /> + } /> + + + ); +} + +// But can be used with a data router as well if you prefer the JSX notation +const routes = createRoutesFromElements( + <> + + + + +); + +const router = createBrowserRouter(routes); + +function App() { + return ; +} +``` + +## Signature + +```tsx +function Route(props: RouteProps): React.ReactElement | null +``` + ## Props +### action + +The route action. +See [`action`](../../start/data/route-object#action). + ### caseSensitive -[modes: framework, data, declarative] +Whether the path should be case-sensitive. Defaults to `false`. -Whether the path should be matched in a case-sensitive manner. +### Component + +The React Component to render when this route matches. +Mutually exclusive with `element`. ### children -[modes: framework, data, declarative] +Child Route components -_No documentation_ +### element -### Component +The React element to render when this Route matches. +Mutually exclusive with `Component`. -[modes: framework, data, declarative] +### ErrorBoundary -_No documentation_ +The React Component to render at this route if an error occurs. +Mutually exclusive with `errorElement`. -### element +### errorElement + +The React element to render at this route if an error occurs. +Mutually exclusive with `ErrorBoundary`. + +### handle -[modes: framework, data, declarative] +The route handle. -_No documentation_ +### HydrateFallback + +The React Component to render while this router is loading data. +Mutually exclusive with `hydrateFallbackElement`. + +### hydrateFallbackElement + +The React element to render while this router is loading data. +Mutually exclusive with `HydrateFallback`. + +### id + +The unique identifier for this route (for use with [`DataRouter`](https://api.reactrouter.com/v7/interfaces/react_router.DataRouter.html)s) + +### index + +Whether this is an index route. + +### lazy + +A function that returns a promise that resolves to the route object. +Used for code-splitting routes. +See [`lazy`](../../start/data/route-object#lazy). + +### loader + +The route loader. +See [`loader`](../../start/data/route-object#loader). ### path -[modes: framework, data, declarative] +The path pattern to match. If unspecified or empty, then this becomes a +layout route. + +### shouldRevalidate + +The route shouldRevalidate function. +See [`shouldRevalidate`](../../start/data/route-object#shouldRevalidate). -The path to match against the current location. diff --git a/docs/api/components/Routes.md b/docs/api/components/Routes.md index 187b52cc4c..4fa2c28c9e 100644 --- a/docs/api/components/Routes.md +++ b/docs/api/components/Routes.md @@ -4,36 +4,55 @@ title: Routes # Routes + + [MODES: framework, data, declarative] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.Routes.html) -Renders a branch of [Route](../components/Route) that best matches the current -location. Note that these routes do not participate in data loading, actions, -code splitting, or any other route module features. +Renders a branch of [``s](../components/Route) that best matches the current +location. Note that these routes do not participate in [data loading](../../start/framework/route-module#loader), +[`action`](../../start/framework/route-module#action), code splitting, or +any other [route module](../../start/framework/route-module) features. ```tsx -import { Routes, Route } from "react-router" +import { Route, Routes } from "react-router"; - } /> - } /> - } /> + } /> + } /> + }> ``` +## Signature + +```tsx +function Routes({ + children, + location, +}: RoutesProps): React.ReactElement | null +``` + ## Props ### children -[modes: framework, data, declarative] - -Nested [Route](../components/Route) elements +Nested [`Route`](../components/Route) elements ### location -[modes: framework, data, declarative] +The [`Location`](https://api.reactrouter.com/v7/interfaces/react_router.Location.html) to match against. Defaults to the current location. -The location to match against. Defaults to the current location. diff --git a/docs/api/components/Scripts.md b/docs/api/components/Scripts.md index 1e1d1830db..0c880c0e69 100644 --- a/docs/api/components/Scripts.md +++ b/docs/api/components/Scripts.md @@ -4,13 +4,31 @@ title: Scripts # Scripts + + [MODES: framework] ## Summary [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react_router.Scripts.html) -Renders the client runtime of your app. It should be rendered inside the `` of the document. +Renders the client runtime of your app. It should be rendered inside the +[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/body) + of the document. + +If server rendering, you can omit `` and the app will work as a +traditional web app without JavaScript, relying solely on HTML and browser +behaviors. ```tsx import { Scripts } from "react-router"; @@ -27,17 +45,16 @@ export default function Root() { } ``` -If server rendering, you can omit `` and the app will work as a traditional web app without JavaScript, relying solely on HTML and browser behaviors. +## Signature -## Props - -### ScriptsProps +```tsx +function Scripts(scriptProps: ScriptsProps): React.JSX.Element | null +``` -[modes: framework] +## Props -A couple common attributes: +### scriptProps -- `` for hosting your static assets on a different server than your app. -- `` to support a [content security policy for scripts](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) with [nonce-sources](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources) for your `` + ``, ); }); @@ -452,7 +452,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/the/path", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -461,7 +461,7 @@ describe("A ", () => { router={createStaticRouter(dataRoutes, context)} context={context} /> - + , ); expect(html).toMatch("

👋

"); @@ -473,10 +473,10 @@ describe("A ", () => { }, actionData: null, errors: null, - }) + }), ); expect(html).toMatch( - `` + ``, ); }); @@ -495,7 +495,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -504,10 +504,10 @@ describe("A ", () => { router={createStaticRouter(routes, context)} context={context} /> - + , ); expect(html).toMatchInlineSnapshot( - `"

👋

"` + `"

👋

"`, ); }); @@ -518,7 +518,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/path/with%20space", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -527,10 +527,10 @@ describe("A ", () => { router={createStaticRouter(routes, context)} context={context} /> - + , ); expect(html).toContain( - '
👋' + '👋', ); }); @@ -543,7 +543,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -552,10 +552,10 @@ describe("A ", () => { router={createStaticRouter(routes, context)} context={context} /> - + , ); expect(html).toContain( - '👋' + '👋', ); }); @@ -566,7 +566,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/path/with%20space", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -575,10 +575,10 @@ describe("A ", () => { router={createStaticRouter(routes, context)} context={context} /> - + , ); expect(html).toContain( - '👋' + '
👋
', ); }); @@ -591,7 +591,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/path/with%20space", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -600,10 +600,10 @@ describe("A ", () => { router={createStaticRouter(routes, context)} context={context} /> - + , ); expect(html).toContain( - '
👋
' + '
👋
', ); }); @@ -616,7 +616,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -625,10 +625,10 @@ describe("A ", () => { router={createStaticRouter(routes, context)} context={context} /> - + , ); expect(html).toContain( - '
👋
' + '
👋
', ); }); @@ -639,7 +639,7 @@ describe("A ", () => { loader: () => { throw Response.json( { not: "found" }, - { status: 404, statusText: "Not Found" } + { status: 404, statusText: "Not Found" }, ); }, }, @@ -649,7 +649,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -658,7 +658,7 @@ describe("A ", () => { router={createStaticRouter(routes, context)} context={context} /> - + , ); let expectedJsonString = JSON.stringify( @@ -674,10 +674,10 @@ describe("A ", () => { __type: "RouteErrorResponse", }, }, - }) + }), ); expect(html).toMatch( - `` + ``, ); }); @@ -689,7 +689,7 @@ describe("A ", () => { loader: () => { throw Response.json( { not: "found" }, - { status: 404, statusText: "Not Found" } + { status: 404, statusText: "Not Found" }, ); }, }), @@ -700,7 +700,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -709,7 +709,7 @@ describe("A ", () => { router={createStaticRouter(dataRoutes, context)} context={context} /> - + , ); let expectedJsonString = JSON.stringify( @@ -725,10 +725,10 @@ describe("A ", () => { __type: "RouteErrorResponse", }, }, - }) + }), ); expect(html).toMatch( - `` + ``, ); }); @@ -746,7 +746,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -755,7 +755,7 @@ describe("A ", () => { router={createStaticRouter(routes, context)} context={context} /> - + , ); // stack is stripped by default from SSR errors @@ -769,10 +769,10 @@ describe("A ", () => { __type: "Error", }, }, - }) + }), ); expect(html).toMatch( - `` + ``, ); }); @@ -792,7 +792,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -801,7 +801,7 @@ describe("A ", () => { router={createStaticRouter(dataRoutes, context)} context={context} /> - + , ); // stack is stripped by default from SSR errors @@ -815,10 +815,10 @@ describe("A ", () => { __type: "Error", }, }, - }) + }), ); expect(html).toMatch( - `` + ``, ); }); @@ -836,7 +836,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -845,7 +845,7 @@ describe("A ", () => { router={createStaticRouter(routes, context)} context={context} /> - + , ); // stack is stripped by default from SSR errors @@ -860,10 +860,10 @@ describe("A ", () => { __subType: "ReferenceError", }, }, - }) + }), ); expect(html).toMatch( - `` + ``, ); }); @@ -885,7 +885,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/the/path", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -895,7 +895,7 @@ describe("A ", () => { context={context} nonce="nonce-string" /> - + , ); expect(html).toMatch("

👋

"); @@ -904,10 +904,10 @@ describe("A ", () => { loaderData: {}, actionData: null, errors: null, - }) + }), ); expect(html).toMatch( - `` + ``, ); }); @@ -935,7 +935,7 @@ describe("A ", () => { let context = (await query( new Request("http://localhost/the/path", { signal: new AbortController().signal, - }) + }), )) as StaticHandlerContext; let html = ReactDOMServer.renderToStaticMarkup( @@ -945,7 +945,7 @@ describe("A ", () => { context={context} hydrate={false} /> - + , ); expect(html).toMatch("

👋

"); expect(html).not.toMatch("" }; expect(escapeHtml(JSON.stringify(evilObj))).toBe( - '{"evil":"\\u003cscript\\u003e\\u003c/script\\u003e"}' + '{"evil":"\\u003cscript\\u003e\\u003c/script\\u003e"}', ); }); test("with angle brackets should parse back", () => { let evilObj = { evil: "" }; expect(JSON.parse(escapeHtml(JSON.stringify(evilObj)))).toMatchObject( - evilObj + evilObj, ); }); @@ -28,28 +28,28 @@ describe("escapeHtml", () => { test("with ampersands should parse back", () => { let evilObj = { evil: "&" }; expect(JSON.parse(escapeHtml(JSON.stringify(evilObj)))).toMatchObject( - evilObj + evilObj, ); }); test('with "LINE SEPARATOR" and "PARAGRAPH SEPARATOR" should escape', () => { let evilObj = { evil: "\u2028\u2029" }; expect(escapeHtml(JSON.stringify(evilObj))).toBe( - '{"evil":"\\u2028\\u2029"}' + '{"evil":"\\u2028\\u2029"}', ); }); test('with "LINE SEPARATOR" and "PARAGRAPH SEPARATOR" should parse back', () => { let evilObj = { evil: "\u2028\u2029" }; expect(JSON.parse(escapeHtml(JSON.stringify(evilObj)))).toMatchObject( - evilObj + evilObj, ); }); test("escaped line terminators should work", () => { expect(() => { vm.runInNewContext( - "(" + escapeHtml(JSON.stringify({ evil: "\u2028\u2029" })) + ")" + "(" + escapeHtml(JSON.stringify({ evil: "\u2028\u2029" })) + ")", ); }).not.toThrow(); }); diff --git a/packages/react-router/__tests__/server-runtime/responses-test.ts b/packages/react-router/__tests__/server-runtime/responses-test.ts index 44bb4ab8f4..734a751087 100644 --- a/packages/react-router/__tests__/server-runtime/responses-test.ts +++ b/packages/react-router/__tests__/server-runtime/responses-test.ts @@ -18,11 +18,11 @@ describe("json", () => { "Content-Type": "application/json; charset=iso-8859-1", "X-Remix": "is awesome", }, - } + }, ); expect(response.headers.get("Content-Type")).toEqual( - "application/json; charset=iso-8859-1" + "application/json; charset=iso-8859-1", ); expect(response.headers.get("X-Remix")).toEqual("is awesome"); }); diff --git a/packages/react-router/__tests__/server-runtime/server-test.ts b/packages/react-router/__tests__/server-runtime/server-test.ts index 5e88c84f20..2b00f6eb42 100644 --- a/packages/react-router/__tests__/server-runtime/server-test.ts +++ b/packages/react-router/__tests__/server-runtime/server-test.ts @@ -43,7 +43,7 @@ describe("server", () => { handleDocumentRequest(request) { return new Response(`${request.method}, ${request.url} COMPONENT`); }, - } + }, ); describe("createRequestHandler", () => { @@ -72,7 +72,7 @@ describe("server", () => { let response = await handler( new Request(`http://localhost:3000${to}`, { method, - }) + }), ); expect(response.status).toBe(200); @@ -80,7 +80,7 @@ describe("server", () => { expect(text).toContain(method); expect(text).toContain(expected); expect(spy.console).not.toHaveBeenCalled(); - } + }, ); it("strips body for HEAD requests", async () => { @@ -88,7 +88,7 @@ describe("server", () => { let response = await handler( new Request("http://localhost:3000/", { method: "HEAD", - }) + }), ); expect(await response.text()).toBe(""); @@ -107,14 +107,14 @@ describe("server", () => { handleDocumentRequest(request) { return new Response(`${request.method}, ${request.url} COMPONENT`); }, - } + }, ); let handler = createRequestHandler(build); let response = await handler( new Request("http://localhost:3000/_root.data"), { foo: "FOO", - } + }, ); expect(await response.text()).toContain("FOO"); @@ -134,13 +134,13 @@ describe("server", () => { handleDocumentRequest(request) { return new Response(`${request.method}, ${request.url} COMPONENT`); }, - } + }, ); let handler = createRequestHandler(build); let response = await handler( new Request("http://localhost:3000/_root.data"), // @ts-expect-error In apps the expected type is handled via the Future interface - new Map([[fooContext, "FOO"]]) + new Map([[fooContext, "FOO"]]), ); expect(await response.text()).toContain("FOO"); @@ -164,14 +164,14 @@ describe("server", () => { handleDocumentRequest(request) { return new Response(`${request.method}, ${request.url} COMPONENT`); }, - } + }, ); let handler = createRequestHandler(build); let response = await handler( new Request("http://localhost:3000/_root.data"), { foo: "FOO", - } + }, ); expect(response.status).toBe(500); @@ -314,7 +314,7 @@ describe("shared server runtime", () => { let result = await handler(request); expect(await result.text()).toBe( - "Unexpected Server Error\n\nError: should be logged when resource loader throws" + "Unexpected Server Error\n\nError: should be logged when resource loader throws", ); }); @@ -457,7 +457,7 @@ describe("shared server runtime", () => { let result = await handler(request); expect(await result.text()).toBe( - "Unexpected Server Error\n\nError: should be logged when resource loader throws" + "Unexpected Server Error\n\nError: should be logged when resource loader throws", ); }); @@ -505,7 +505,7 @@ describe("shared server runtime", () => { }, { handleError: handleErrorSpy, - } + }, ); let handler = createRequestHandler(build, ServerMode.Test); @@ -526,15 +526,15 @@ describe("shared server runtime", () => { `); expect(handleErrorSpy).toHaveBeenCalledTimes(1); expect(handleErrorSpy.mock.calls[0][0] instanceof DOMException).toBe( - true + true, ); expect(handleErrorSpy.mock.calls[0][0].name).toBe("AbortError"); expect(handleErrorSpy.mock.calls[0][0].message).toBe( - "This operation was aborted" + "This operation was aborted", ); expect(handleErrorSpy.mock.calls[0][1].request.method).toBe("GET"); expect(handleErrorSpy.mock.calls[0][1].request.url).toBe( - "http://test.com/resource" + "http://test.com/resource", ); }); }); @@ -999,7 +999,7 @@ describe("shared server runtime", () => { }, { handleError: handleErrorSpy, - } + }, ); let handler = createRequestHandler(build, ServerMode.Test); @@ -1016,19 +1016,19 @@ describe("shared server runtime", () => { let error = await result.json(); expect(error.message).toBe("This operation was aborted"); expect( - error.stack.startsWith("AbortError: This operation was aborted") + error.stack.startsWith("AbortError: This operation was aborted"), ).toBe(true); expect(handleErrorSpy).toHaveBeenCalledTimes(1); expect(handleErrorSpy.mock.calls[0][0] instanceof DOMException).toBe( - true + true, ); expect(handleErrorSpy.mock.calls[0][0].name).toBe("AbortError"); expect(handleErrorSpy.mock.calls[0][0].message).toBe( - "This operation was aborted" + "This operation was aborted", ); expect(handleErrorSpy.mock.calls[0][1].request.method).toBe("GET"); expect(handleErrorSpy.mock.calls[0][1].request.url).toBe( - "http://test.com/?_data=routes/_index" + "http://test.com/?_data=routes/_index", ); }); }); @@ -1538,7 +1538,7 @@ describe("shared server runtime", () => { expect(context.errors).toBeTruthy(); expect(context.errors!["routes/_index"]).toBeInstanceOf(Error); expect(context.errors!["routes/_index"].message).toBe( - "Unexpected Server Error" + "Unexpected Server Error", ); expect(context.errors!["routes/_index"].stack).toBeUndefined(); expect(context.loaderData).toEqual({ @@ -1680,7 +1680,7 @@ describe("shared server runtime", () => { expect(context.errors).toBeTruthy(); expect(context.errors!["routes/test"]).toBeInstanceOf(Error); expect(context.errors!["routes/test"].message).toBe( - "Unexpected Server Error" + "Unexpected Server Error", ); expect(context.errors!["routes/test"].stack).toBeUndefined(); expect(context.loaderData).toEqual({ @@ -1730,7 +1730,7 @@ describe("shared server runtime", () => { expect(context.errors).toBeTruthy(); expect(context.errors!["routes/_index"]).toBeInstanceOf(Error); expect(context.errors!["routes/_index"].message).toBe( - "Unexpected Server Error" + "Unexpected Server Error", ); expect(context.errors!["routes/_index"].stack).toBeUndefined(); expect(context.loaderData).toEqual({ @@ -1788,7 +1788,7 @@ describe("shared server runtime", () => { expect(context.errors).toBeTruthy(); expect(context.errors!["routes/__layout"]).toBeInstanceOf(Error); expect(context.errors!["routes/__layout"].message).toBe( - "Unexpected Server Error" + "Unexpected Server Error", ); expect(context.errors!["routes/__layout"].stack).toBeUndefined(); expect(context.loaderData).toEqual({ @@ -1846,7 +1846,7 @@ describe("shared server runtime", () => { expect(context.errors).toBeTruthy(); expect(context.errors!["routes/__layout"]).toBeInstanceOf(Error); expect(context.errors!["routes/__layout"].message).toBe( - "Unexpected Server Error" + "Unexpected Server Error", ); expect(context.errors!["routes/__layout"].stack).toBeUndefined(); expect(context.loaderData).toEqual({ @@ -1923,7 +1923,7 @@ describe("shared server runtime", () => { let ogHandleDocumentRequest = build.entry.module.default; build.entry.module.default = function ( _: Request, - responseStatusCode: number + responseStatusCode: number, ) { if (responseStatusCode === 200) { throw new Response("Uh oh!", { @@ -1972,7 +1972,7 @@ describe("shared server runtime", () => { let result = await handler(request); expect(result.status).toBe(500); expect(await result.text()).toBe( - "Unexpected Server Error\n\nError: rofl" + "Unexpected Server Error\n\nError: rofl", ); expect(rootLoader.mock.calls.length).toBe(0); expect(indexLoader.mock.calls.length).toBe(0); @@ -2025,7 +2025,7 @@ describe("shared server runtime", () => { expect(spy.console.mock.calls).toEqual([ [ new Error( - "thrown from handleDocumentRequest and expected to be logged in console only once" + "thrown from handleDocumentRequest and expected to be logged in console only once", ), ], [new Error("second error thrown from handleDocumentRequest")], @@ -2055,7 +2055,7 @@ describe("shared server runtime", () => { }, { handleError: handleErrorSpy, - } + }, ); let handler = createRequestHandler(build, ServerMode.Test); @@ -2073,15 +2073,15 @@ describe("shared server runtime", () => { expect(handleErrorSpy).toHaveBeenCalledTimes(1); expect(handleErrorSpy.mock.calls[0][0] instanceof DOMException).toBe( - true + true, ); expect(handleErrorSpy.mock.calls[0][0].name).toBe("AbortError"); expect(handleErrorSpy.mock.calls[0][0].message).toBe( - "This operation was aborted" + "This operation was aborted", ); expect(handleErrorSpy.mock.calls[0][1].request.method).toBe("GET"); expect(handleErrorSpy.mock.calls[0][1].request.url).toBe( - "http://test.com/" + "http://test.com/", ); }); }); @@ -2113,7 +2113,7 @@ describe("shared server runtime", () => { headers: responseHeaders, }); }, - } + }, ); let handler = createRequestHandler(build, ServerMode.Development); diff --git a/packages/react-router/__tests__/server-runtime/sessions-test.ts b/packages/react-router/__tests__/server-runtime/sessions-test.ts index d45a14a657..dd8a1e15f4 100644 --- a/packages/react-router/__tests__/server-runtime/sessions-test.ts +++ b/packages/react-router/__tests__/server-runtime/sessions-test.ts @@ -103,7 +103,7 @@ describe("Cookie session storage", () => { let setCookie = await commitSession(session); session = await getSession( // Tamper with the session cookie... - getCookieFromSetCookie(setCookie).slice(0, -1) + getCookieFromSetCookie(setCookie).slice(0, -1), ); expect(session.get("user")).toBeUndefined(); @@ -139,7 +139,7 @@ describe("Cookie session storage", () => { let session = await getSession(); let setCookie = await destroySession(session); expect(setCookie).toMatchInlineSnapshot( - `"__session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax"` + `"__session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax"`, ); spy.mockRestore(); }); @@ -155,7 +155,7 @@ describe("Cookie session storage", () => { let session = await getSession(); let setCookie = await destroySession(session); expect(setCookie).toMatchInlineSnapshot( - `"__session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax"` + `"__session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax"`, ); spy.mockRestore(); }); @@ -173,7 +173,7 @@ describe("Cookie session storage", () => { expect(spy.console).toHaveBeenCalledTimes(1); expect(spy.console).toHaveBeenCalledWith( - 'The "__session" cookie has an "expires" property set. This will cause the expires value to not be updated when the session is committed. Instead, you should set the expires value when serializing the cookie. You can use `commitSession(session, { expires })` if using a session storage object, or `cookie.serialize("value", { expires })` if you\'re using the cookie directly.' + 'The "__session" cookie has an "expires" property set. This will cause the expires value to not be updated when the session is committed. Instead, you should set the expires value when serializing the cookie. You can use `commitSession(session, { expires })` if using a session storage object, or `cookie.serialize("value", { expires })` if you\'re using the cookie directly.', ); }); @@ -182,7 +182,7 @@ describe("Cookie session storage", () => { expect(spy.console).toHaveBeenCalledTimes(1); expect(spy.console).toHaveBeenCalledWith( - 'The "__session" cookie is not signed, but session cookies should be signed to prevent tampering on the client before they are sent back to the server. See https://reactrouter.com/explanation/sessions-and-cookies#signing-cookies for more information.' + 'The "__session" cookie is not signed, but session cookies should be signed to prevent tampering on the client before they are sent back to the server. See https://reactrouter.com/explanation/sessions-and-cookies#signing-cookies for more information.', ); }); }); diff --git a/packages/react-router/__tests__/server-runtime/utils.ts b/packages/react-router/__tests__/server-runtime/utils.ts index 76f4ba77bb..6e4fa4dce5 100644 --- a/packages/react-router/__tests__/server-runtime/utils.ts +++ b/packages/react-router/__tests__/server-runtime/utils.ts @@ -30,7 +30,7 @@ export function mockServerBuild( future?: Partial; handleError?: HandleErrorFunction; handleDocumentRequest?: HandleDocumentRequestFunction; - } = {} + } = {}, ): ServerBuild { return { ssr: true, @@ -87,7 +87,7 @@ export function mockServerBuild( new Response(null, { status: responseStatusCode, headers: responseHeaders, - }) + }), ), handleDataRequest: jest.fn(async (response) => response), handleError: opts.handleError, @@ -113,7 +113,7 @@ export function mockServerBuild( [id]: route, }; }, - {} + {}, ), }; } diff --git a/packages/react-router/__tests__/use-revalidator-test.tsx b/packages/react-router/__tests__/use-revalidator-test.tsx index 973b62dca1..7d3d08f92f 100644 --- a/packages/react-router/__tests__/use-revalidator-test.tsx +++ b/packages/react-router/__tests__/use-revalidator-test.tsx @@ -32,7 +32,7 @@ describe("useRevalidator", () => { loader={async () => `count=${++count}`} element={} /> -
+
, ), { initialEntries: ["/foo"], @@ -41,7 +41,7 @@ describe("useRevalidator", () => { "0-0": "count=1", }, }, - } + }, ); let { container } = render(); @@ -157,14 +157,14 @@ describe("useRevalidator", () => { ); }} /> - - ) + , + ), ); let { container } = render(
-
+ , ); fireEvent.click(screen.getByText("/child")); @@ -214,14 +214,14 @@ describe("useRevalidator", () => { }} Component={() =>

{("Child:" + useLoaderData()) as string}

} /> - - ) + , + ), ); let { container } = render(
-
+ , ); fireEvent.click(screen.getByText("/child")); @@ -312,7 +312,7 @@ describe("useRevalidator", () => { hydrationData: { loaderData: { root: 0 }, }, - } + }, ); render(); diff --git a/packages/react-router/__tests__/useHref-basename-test.tsx b/packages/react-router/__tests__/useHref-basename-test.tsx index 0b5bde505f..866a36a658 100644 --- a/packages/react-router/__tests__/useHref-basename-test.tsx +++ b/packages/react-router/__tests__/useHref-basename-test.tsx @@ -16,7 +16,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -37,7 +37,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -57,7 +57,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -78,7 +78,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -100,7 +100,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -120,7 +120,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -141,7 +141,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -163,7 +163,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -183,7 +183,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -204,7 +204,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -229,7 +229,7 @@ describe("useHref under a basename", () => { element={} /> - + , ); }); @@ -253,7 +253,7 @@ describe("useHref under a basename", () => { } /> - + , ); }); @@ -281,7 +281,7 @@ describe("useHref under a basename", () => { element={} /> - + , ); }); @@ -309,7 +309,7 @@ describe("useHref under a basename", () => { element={} /> - + , ); }); diff --git a/packages/react-router/__tests__/useHref-test.tsx b/packages/react-router/__tests__/useHref-test.tsx index f3e2c4c173..0aa3c23bd0 100644 --- a/packages/react-router/__tests__/useHref-test.tsx +++ b/packages/react-router/__tests__/useHref-test.tsx @@ -19,7 +19,7 @@ describe("useHref", () => { element={} /> - + , ); }); @@ -42,7 +42,7 @@ describe("useHref", () => { element={} /> - + , ); }); @@ -66,7 +66,7 @@ describe("useHref", () => { element={} /> - + , ); }); @@ -88,7 +88,7 @@ describe("useHref", () => { } /> - + , ); }); @@ -108,7 +108,7 @@ describe("useHref", () => { } /> - + , ); }); @@ -129,7 +129,7 @@ describe("useHref", () => { } /> - + , ); }); @@ -153,7 +153,7 @@ describe("useHref", () => { } /> - + , ); }); @@ -175,7 +175,7 @@ describe("useHref", () => { } /> - + , ); }); @@ -201,7 +201,7 @@ describe("useHref", () => { /> - + , ); }); @@ -226,7 +226,7 @@ describe("useHref", () => { element={} /> - + , ); }); @@ -252,7 +252,7 @@ describe("useHref", () => { /> - + , ); }); @@ -272,7 +272,7 @@ describe("useHref", () => { } /> - + , ); }); diff --git a/packages/react-router/__tests__/useLocation-test.tsx b/packages/react-router/__tests__/useLocation-test.tsx index 121073a0d5..de87981e74 100644 --- a/packages/react-router/__tests__/useLocation-test.tsx +++ b/packages/react-router/__tests__/useLocation-test.tsx @@ -16,7 +16,7 @@ describe("useLocation", () => { } /> - + , ); }); @@ -33,7 +33,7 @@ describe("useLocation", () => { renderer = TestRenderer.create( - + , ); }); @@ -74,7 +74,7 @@ describe("useLocation", () => { } /> - + , ); }); diff --git a/packages/react-router/__tests__/useMatch-test.tsx b/packages/react-router/__tests__/useMatch-test.tsx index f51219a055..21dcbe6d53 100644 --- a/packages/react-router/__tests__/useMatch-test.tsx +++ b/packages/react-router/__tests__/useMatch-test.tsx @@ -19,7 +19,7 @@ describe("useMatch", () => { Home} /> - + , ); }); @@ -51,7 +51,7 @@ describe("useMatch", () => { Home} /> - + , ); }); @@ -83,7 +83,7 @@ describe("useMatch", () => { Home} /> - + , ); }); @@ -118,7 +118,7 @@ describe("useMatch", () => { } /> - + , ); }); @@ -128,7 +128,7 @@ describe("useMatch", () => { } /> - + , ); }); diff --git a/packages/react-router/__tests__/useNavigate-test.tsx b/packages/react-router/__tests__/useNavigate-test.tsx index 66823db747..743aed2709 100644 --- a/packages/react-router/__tests__/useNavigate-test.tsx +++ b/packages/react-router/__tests__/useNavigate-test.tsx @@ -38,7 +38,7 @@ describe("useNavigate", () => { } /> About} /> - + , ); }); @@ -74,7 +74,7 @@ describe("useNavigate", () => { } /> - + , ); }); @@ -131,7 +131,7 @@ describe("useNavigate", () => { } /> - + , ); }); @@ -188,7 +188,7 @@ describe("useNavigate", () => { } /> - + , ); }); @@ -264,40 +264,40 @@ describe("useNavigate", () => { } /> - + , ); }); expect(() => TestRenderer.act(() => { renderer.root.findAllByType("button")[0].props.onClick(); - }) + }), ).toThrowErrorMatchingInlineSnapshot( - `"Cannot include a '?' character in a manually specified \`to.pathname\` field [{"pathname":"/about/thing?search"}]. Please separate it out to the \`to.search\` field. Alternatively you may provide the full path as a string in and the router will parse it for you."` + `"Cannot include a '?' character in a manually specified \`to.pathname\` field [{"pathname":"/about/thing?search"}]. Please separate it out to the \`to.search\` field. Alternatively you may provide the full path as a string in and the router will parse it for you."`, ); expect(() => TestRenderer.act(() => { renderer.root.findAllByType("button")[1].props.onClick(); - }) + }), ).toThrowErrorMatchingInlineSnapshot( - `"Cannot include a '#' character in a manually specified \`to.pathname\` field [{"pathname":"/about/thing#hash"}]. Please separate it out to the \`to.hash\` field. Alternatively you may provide the full path as a string in and the router will parse it for you."` + `"Cannot include a '#' character in a manually specified \`to.pathname\` field [{"pathname":"/about/thing#hash"}]. Please separate it out to the \`to.hash\` field. Alternatively you may provide the full path as a string in and the router will parse it for you."`, ); expect(() => TestRenderer.act(() => { renderer.root.findAllByType("button")[2].props.onClick(); - }) + }), ).toThrowErrorMatchingInlineSnapshot( - `"Cannot include a '?' character in a manually specified \`to.pathname\` field [{"pathname":"/about/thing?search#hash"}]. Please separate it out to the \`to.search\` field. Alternatively you may provide the full path as a string in and the router will parse it for you."` + `"Cannot include a '?' character in a manually specified \`to.pathname\` field [{"pathname":"/about/thing?search#hash"}]. Please separate it out to the \`to.search\` field. Alternatively you may provide the full path as a string in and the router will parse it for you."`, ); expect(() => TestRenderer.act(() => { renderer.root.findAllByType("button")[3].props.onClick(); - }) + }), ).toThrowErrorMatchingInlineSnapshot( - `"Cannot include a '#' character in a manually specified \`to.search\` field [{"pathname":"/about/thing","search":"?search#hash"}]. Please separate it out to the \`to.hash\` field. Alternatively you may provide the full path as a string in and the router will parse it for you."` + `"Cannot include a '#' character in a manually specified \`to.search\` field [{"pathname":"/about/thing","search":"?search#hash"}]. Please separate it out to the \`to.hash\` field. Alternatively you may provide the full path as a string in and the router will parse it for you."`, ); }); @@ -493,7 +493,7 @@ describe("useNavigate", () => { } /> About} /> - + , ); }); @@ -510,7 +510,7 @@ describe("useNavigate", () => { `); expect(warnSpy).toHaveBeenCalledWith( - "You should call navigate() in a React.useEffect(), not when your component is first rendered." + "You should call navigate() in a React.useEffect(), not when your component is first rendered.", ); }); @@ -523,7 +523,7 @@ describe("useNavigate", () => { } /> About} /> - + , ); }); @@ -553,7 +553,7 @@ describe("useNavigate", () => { } /> About} /> - + , ); }); @@ -561,7 +561,7 @@ describe("useNavigate", () => { let navigate = useNavigate(); let onChildRendered = React.useCallback( () => navigate("/about"), - [navigate] + [navigate], ); return ; } @@ -610,7 +610,7 @@ describe("useNavigate", () => { `); expect(warnSpy).toHaveBeenCalledWith( - "You should call navigate() in a React.useEffect(), not when your component is first rendered." + "You should call navigate() in a React.useEffect(), not when your component is first rendered.", ); }); @@ -653,7 +653,7 @@ describe("useNavigate", () => { let navigate = useNavigate(); let onChildRendered = React.useCallback( () => navigate("/about"), - [navigate] + [navigate], ); return ; }, @@ -714,7 +714,7 @@ describe("useNavigate", () => { } /> } /> - + , ); }); @@ -746,7 +746,7 @@ describe("useNavigate", () => { /> About} /> - + , ); }); @@ -776,7 +776,7 @@ describe("useNavigate", () => { /> About} /> - + , ); }); @@ -803,7 +803,7 @@ describe("useNavigate", () => { About} /> - + , ); }); @@ -833,7 +833,7 @@ describe("useNavigate", () => { About} /> - + , ); }); @@ -869,7 +869,7 @@ describe("useNavigate", () => { About} /> - + , ); }); @@ -905,7 +905,7 @@ describe("useNavigate", () => { About} /> - + , ); }); @@ -954,7 +954,7 @@ describe("useNavigate", () => { About} /> - + , ); }); @@ -984,7 +984,7 @@ describe("useNavigate", () => { - + , ); }); @@ -1014,7 +1014,7 @@ describe("useNavigate", () => { element={} /> - + , ); }); @@ -1044,7 +1044,7 @@ describe("useNavigate", () => { /> - + , ); }); @@ -1074,7 +1074,7 @@ describe("useNavigate", () => { /> - + , ); }); @@ -1112,7 +1112,7 @@ describe("useNavigate", () => { - + , ); }); @@ -1150,7 +1150,7 @@ describe("useNavigate", () => { - + , ); }); @@ -1183,7 +1183,7 @@ describe("useNavigate", () => { - + , ); }); @@ -1213,7 +1213,7 @@ describe("useNavigate", () => { } /> - + , ); }); @@ -1268,7 +1268,7 @@ describe("useNavigate", () => { About} /> - + , ); }); @@ -1381,9 +1381,9 @@ describe("useNavigate", () => { <> } /> About} /> - + , ), - { initialEntries: ["/home"] } + { initialEntries: ["/home"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1414,9 +1414,9 @@ describe("useNavigate", () => { element={} /> About} /> - + , ), - { initialEntries: ["/home"] } + { initialEntries: ["/home"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1444,9 +1444,9 @@ describe("useNavigate", () => { } /> About} /> - + , ), - { initialEntries: ["/home"] } + { initialEntries: ["/home"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1477,9 +1477,9 @@ describe("useNavigate", () => { /> About} /> - + , ), - { initialEntries: ["/home"] } + { initialEntries: ["/home"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1516,9 +1516,9 @@ describe("useNavigate", () => { About} /> - + , ), - { initialEntries: ["/home"] } + { initialEntries: ["/home"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1555,9 +1555,9 @@ describe("useNavigate", () => { About} /> - + , ), - { initialEntries: ["/home/page"] } + { initialEntries: ["/home/page"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1607,9 +1607,9 @@ describe("useNavigate", () => { About} /> - + , ), - { initialEntries: ["/home/page"] } + { initialEntries: ["/home/page"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1640,9 +1640,9 @@ describe("useNavigate", () => { Destination} /> - + , ), - { initialEntries: ["/layout/thing"] } + { initialEntries: ["/layout/thing"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1673,9 +1673,9 @@ describe("useNavigate", () => { path="contacts/:id" element={} /> - + , ), - { initialEntries: ["/contacts/1"] } + { initialEntries: ["/contacts/1"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1706,9 +1706,9 @@ describe("useNavigate", () => { element={} /> - + , ), - { initialEntries: ["/contacts/1"] } + { initialEntries: ["/contacts/1"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1739,9 +1739,9 @@ describe("useNavigate", () => { element={} /> - + , ), - { initialEntries: ["/contacts/1"] } + { initialEntries: ["/contacts/1"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1778,9 +1778,9 @@ describe("useNavigate", () => { - + , ), - { initialEntries: ["/contacts/1"] } + { initialEntries: ["/contacts/1"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1817,9 +1817,9 @@ describe("useNavigate", () => { - + , ), - { initialEntries: ["/contacts/1"] } + { initialEntries: ["/contacts/1"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1853,9 +1853,9 @@ describe("useNavigate", () => { Destination} /> - + , ), - { initialEntries: ["/layout/thing"] } + { initialEntries: ["/layout/thing"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -1886,9 +1886,9 @@ describe("useNavigate", () => { } /> - + , ), - { initialEntries: ["/contacts/1"] } + { initialEntries: ["/contacts/1"] }, ); function Contacts() { @@ -1951,7 +1951,7 @@ describe("useNavigate", () => { ], }, ], - { initialEntries: ["/home"] } + { initialEntries: ["/home"] }, ); let renderer: TestRenderer.ReactTestRenderer; @@ -2071,7 +2071,7 @@ describe("useNavigate", () => { } /> Path} /> - + , ); }); @@ -2115,7 +2115,7 @@ describe("useNavigate", () => { /> Path} /> - + , ); }); @@ -2154,7 +2154,7 @@ describe("useNavigate", () => { }, { path: "/path", Component: () =>

Path

}, ], - { basename: "/base", initialEntries: ["/base"] } + { basename: "/base", initialEntries: ["/base"] }, ); function Home() { @@ -2201,7 +2201,7 @@ describe("useNavigate", () => { }, { path: "/path", Component: () =>

Path

}, ], - { basename: "/base", initialEntries: ["/base"] } + { basename: "/base", initialEntries: ["/base"] }, ); function Home() { diff --git a/packages/react-router/__tests__/useOutlet-test.tsx b/packages/react-router/__tests__/useOutlet-test.tsx index f283bd4874..32dc725d96 100644 --- a/packages/react-router/__tests__/useOutlet-test.tsx +++ b/packages/react-router/__tests__/useOutlet-test.tsx @@ -22,7 +22,7 @@ describe("useOutlet", () => { } /> - + , ); }); @@ -42,7 +42,7 @@ describe("useOutlet", () => { } /> - + , ); }); @@ -66,7 +66,7 @@ describe("useOutlet", () => { } /> - + , ); }); @@ -93,7 +93,7 @@ describe("useOutlet", () => { Profile} /> - + , ); }); @@ -119,7 +119,7 @@ describe("useOutlet", () => { index} /> - + , ); }); @@ -145,7 +145,7 @@ describe("useOutlet", () => { index} /> - + , ); }); @@ -183,7 +183,7 @@ describe("useOutlet", () => { } /> - + , ); }); @@ -235,7 +235,7 @@ describe("useOutlet", () => { } /> - + , ); }); @@ -286,7 +286,7 @@ describe("useOutlet", () => {
- + , ); }); @@ -328,7 +328,7 @@ describe("useOutlet", () => {
- + , ); }); diff --git a/packages/react-router/__tests__/useParams-test.tsx b/packages/react-router/__tests__/useParams-test.tsx index 4b34daf16a..5413cee245 100644 --- a/packages/react-router/__tests__/useParams-test.tsx +++ b/packages/react-router/__tests__/useParams-test.tsx @@ -14,7 +14,7 @@ describe("useParams", () => { renderer = TestRenderer.create( - + , ); }); @@ -35,7 +35,7 @@ describe("useParams", () => { } /> - + , ); }); @@ -56,7 +56,7 @@ describe("useParams", () => { } /> - + , ); }); @@ -89,7 +89,7 @@ describe("useParams", () => { } /> - + , ); }); @@ -116,7 +116,7 @@ describe("useParams", () => { } /> - + , ); }); @@ -137,7 +137,7 @@ describe("useParams", () => { } /> - + , ); }); @@ -171,7 +171,7 @@ describe("useParams", () => { } /> - + , ); }); @@ -182,7 +182,7 @@ describe("useParams", () => { `); expect(consoleWarn).toHaveBeenCalledWith( - expect.stringMatching("malformed URL segment") + expect.stringMatching("malformed URL segment"), ); }); }); @@ -198,7 +198,7 @@ describe("useParams", () => { - + , ); }); diff --git a/packages/react-router/__tests__/useResolvedPath-test.tsx b/packages/react-router/__tests__/useResolvedPath-test.tsx index 7f183b4f7a..785694708e 100644 --- a/packages/react-router/__tests__/useResolvedPath-test.tsx +++ b/packages/react-router/__tests__/useResolvedPath-test.tsx @@ -26,7 +26,7 @@ describe("useResolvedPath", () => { element={} /> - + , ); }); @@ -56,7 +56,7 @@ describe("useResolvedPath", () => { } /> - + , ); }); @@ -79,7 +79,7 @@ describe("useResolvedPath", () => { element={} /> - + , ); }); @@ -102,7 +102,7 @@ describe("useResolvedPath", () => { } /> - + , ); }); @@ -123,7 +123,7 @@ describe("useResolvedPath", () => { } /> - + , ); }); @@ -149,7 +149,7 @@ describe("useResolvedPath", () => { } /> - + , ); }); @@ -175,7 +175,7 @@ describe("useResolvedPath", () => { } /> - + , ); }); @@ -198,7 +198,7 @@ describe("useResolvedPath", () => { } /> - + , ); }); @@ -219,7 +219,7 @@ describe("useResolvedPath", () => { } /> - + , ); }); @@ -381,7 +381,7 @@ describe("useResolvedPath", () => { - + , ); let html = getHtml(container); html = html ? html.replace(/</g, "<").replace(/>/g, ">") : html; diff --git a/packages/react-router/__tests__/useRoutes-test.tsx b/packages/react-router/__tests__/useRoutes-test.tsx index 91de38a632..b433e986b9 100644 --- a/packages/react-router/__tests__/useRoutes-test.tsx +++ b/packages/react-router/__tests__/useRoutes-test.tsx @@ -15,7 +15,7 @@ describe("useRoutes", () => { renderer = TestRenderer.create( - + , ); }); @@ -41,7 +41,7 @@ describe("useRoutes", () => { renderer = TestRenderer.create( - + , ); }); @@ -64,7 +64,7 @@ describe("useRoutes", () => { renderer = TestRenderer.create( - + , ); }); @@ -90,7 +90,7 @@ describe("useRoutes", () => { renderer = TestRenderer.create( - + , ); }); @@ -124,7 +124,7 @@ describe("useRoutes", () => { routes={routes} location={{ pathname: "/three", search: "", hash: "" }} /> - + , ); }); @@ -159,15 +159,15 @@ describe("useRoutes", () => { TestRenderer.create( - + , ); }); expect(consoleWarn).toHaveBeenCalledTimes(1); expect(consoleWarn).toHaveBeenCalledWith( expect.stringContaining( - `Matched leaf route at location "/layout" does not have an element` - ) + `Matched leaf route at location "/layout" does not have an element`, + ), ); }); }); diff --git a/packages/react-router/__tests__/utils/MemoryNavigate.tsx b/packages/react-router/__tests__/utils/MemoryNavigate.tsx index 146e059011..a1c0a31d30 100644 --- a/packages/react-router/__tests__/utils/MemoryNavigate.tsx +++ b/packages/react-router/__tests__/utils/MemoryNavigate.tsx @@ -25,7 +25,7 @@ export default function MemoryNavigate({ dataRouterContext?.router.navigate(to); } }, - [dataRouterContext, to, formMethod, formData] + [dataRouterContext, to, formMethod, formData], ); // Only prepend the basename to the rendered href, send the non-prefixed `to` diff --git a/packages/react-router/__tests__/utils/framework.ts b/packages/react-router/__tests__/utils/framework.ts index 824fc3a761..72762d49d7 100644 --- a/packages/react-router/__tests__/utils/framework.ts +++ b/packages/react-router/__tests__/utils/framework.ts @@ -4,7 +4,7 @@ import type { } from "../../lib/dom/ssr/entry"; export function mockFrameworkContext( - overrides?: Partial + overrides?: Partial, ): FrameworkContextObject { return { routeModules: { root: { default: () => null } }, @@ -44,7 +44,7 @@ export function mockFrameworkContext( } export function mockEntryContext( - overrides?: Partial + overrides?: Partial, ): EntryContext { return { ...mockFrameworkContext(overrides), diff --git a/packages/react-router/__tests__/utils/renderStrict.tsx b/packages/react-router/__tests__/utils/renderStrict.tsx index 3ce64e06c4..a9f4d19483 100644 --- a/packages/react-router/__tests__/utils/renderStrict.tsx +++ b/packages/react-router/__tests__/utils/renderStrict.tsx @@ -13,7 +13,7 @@ function renderStrict( element: | React.FunctionComponentElement | React.FunctionComponentElement[], - node: ReactDOM.Container + node: ReactDOM.Container, ): void { ReactDOM.render({element}, node); } diff --git a/packages/react-router/__tests__/vendor/turbo-stream-test.ts b/packages/react-router/__tests__/vendor/turbo-stream-test.ts index 54332b1703..8c9d615e55 100644 --- a/packages/react-router/__tests__/vendor/turbo-stream-test.ts +++ b/packages/react-router/__tests__/vendor/turbo-stream-test.ts @@ -244,7 +244,7 @@ test("should encode and decode object and dedupe object key, value, and promise write(chunk) { encoded += chunk; }, - }) + }), ); expect(Array.from(encoded.matchAll(/"foo"/g))).toHaveLength(1); @@ -288,7 +288,7 @@ test("should encode and decode rejected promise", async () => { const decoded = await decode(encode(input)); expect(decoded.value).toBeInstanceOf(Promise); await expect(decoded.value).rejects.toEqual( - await input.catch((reason) => reason) + await input.catch((reason) => reason), ); await decoded.done; }); @@ -308,7 +308,7 @@ test("should encode and decode object with rejected promise", async () => { const value = decoded.value as typeof input; expect(value.foo).toBeInstanceOf(Promise); await expect(value.foo).rejects.toEqual( - await input.foo.catch((reason) => reason) + await input.foo.catch((reason) => reason), ); return decoded.done; }); @@ -352,7 +352,7 @@ test("should encode and decode custom type", async () => { }), { plugins: [decoder], - } + }, ); const value = decoded.value as Custom; expect(value).toBeInstanceOf(Custom); @@ -393,7 +393,7 @@ test("should encode and decode custom type when nested alongside Promise", async } }, ], - } + }, )) as unknown as { value: { number: number; @@ -431,7 +431,7 @@ test("should allow plugins to encode and decode functions", async () => { } }, ], - } + }, ); expect(decoded.value).toBeInstanceOf(Function); expect((decoded.value as typeof input)()).toBe("foo"); @@ -460,7 +460,7 @@ test("should allow postPlugins to handle values that would otherwise throw", asy } }, ], - } + }, ); expect(decoded.value).toEqual({ func: undefined, class: undefined }); await decoded.done; @@ -471,7 +471,7 @@ test("should propagate abort reason to deferred promises for sync resolved promi const reason = new Error("reason"); abortController.abort(reason); const decoded = await decode( - encode(Promise.resolve("foo"), { signal: abortController.signal }) + encode(Promise.resolve("foo"), { signal: abortController.signal }), ); await expect(decoded.value).rejects.toEqual(reason); }); @@ -481,7 +481,7 @@ test("should propagate abort reason to deferred promises for async resolved prom const deferred = new Deferred(); const reason = new Error("reason"); const decoded = await decode( - encode(deferred.promise, { signal: abortController.signal }) + encode(deferred.promise, { signal: abortController.signal }), ); abortController.abort(reason); await expect(decoded.value).rejects.toEqual(reason); @@ -511,7 +511,7 @@ test("should encode and decode objects with multiple promises resolving to the s write(chunk) { encoded += chunk; }, - }) + }), ); expect(Array.from(encoded.matchAll(/"baz"/g))).toHaveLength(1); }); @@ -542,7 +542,7 @@ test("should encode and decode objects with reused values", async () => { write(chunk) { encoded += chunk; }, - }) + }), ); expect(Array.from(encoded.matchAll(/"baz"/g))).toHaveLength(1); await decoded.done; @@ -573,7 +573,7 @@ test("should encode and decode objects with multiple promises rejecting to the s write(chunk) { encoded += chunk; }, - }) + }), ); expect(Array.from(encoded.matchAll(/"baz"/g))).toHaveLength(1); }); diff --git a/packages/react-router/index.ts b/packages/react-router/index.ts index 6610e517f5..5e96c5f30d 100644 --- a/packages/react-router/index.ts +++ b/packages/react-router/index.ts @@ -204,7 +204,11 @@ export { Scripts, PrefetchPageLinks, } from "./lib/dom/ssr/components"; -export type { ScriptsProps } from "./lib/dom/ssr/components"; +export type { + ScriptsProps, + PrefetchBehavior, + DiscoverBehavior, +} from "./lib/dom/ssr/components"; export type { EntryContext } from "./lib/dom/ssr/entry"; export type { ClientActionFunction, @@ -286,12 +290,16 @@ export { href } from "./lib/href"; export type { BrowserCreateFromReadableStreamFunction as unstable_BrowserCreateFromReadableStreamFunction, EncodeReplyFunction as unstable_EncodeReplyFunction, + RSCHydratedRouterProps as unstable_RSCHydratedRouterProps, } from "./lib/rsc/browser"; export { createCallServer as unstable_createCallServer, RSCHydratedRouter as unstable_RSCHydratedRouter, } from "./lib/rsc/browser"; -export type { SSRCreateFromReadableStreamFunction as unstable_SSRCreateFromReadableStreamFunction } from "./lib/rsc/server.ssr"; +export type { + SSRCreateFromReadableStreamFunction as unstable_SSRCreateFromReadableStreamFunction, + RSCStaticRouterProps as unstable_RSCStaticRouterProps, +} from "./lib/rsc/server.ssr"; export { routeRSCServerRequest as unstable_routeRSCServerRequest, RSCStaticRouter as unstable_RSCStaticRouter, diff --git a/packages/react-router/lib/components.tsx b/packages/react-router/lib/components.tsx index a422823662..d0c814b22a 100644 --- a/packages/react-router/lib/components.tsx +++ b/packages/react-router/lib/components.tsx @@ -69,9 +69,6 @@ import { import type { ViewTransition } from "./dom/global"; import { warnOnce } from "./server-runtime/warnings"; -/** - * @private - */ export function mapRouteProperties(route: RouteObject) { let updates: Partial & { hasErrorBoundary: boolean } = { // Note: this check also occurs in createRoutesFromChildren so update @@ -88,7 +85,7 @@ export function mapRouteProperties(route: RouteObject) { warning( false, "You should not include both `Component` and `element` on your route - " + - "`Component` will be used." + "`Component` will be used.", ); } } @@ -104,7 +101,7 @@ export function mapRouteProperties(route: RouteObject) { warning( false, "You should not include both `HydrateFallback` and `hydrateFallbackElement` on your route - " + - "`HydrateFallback` will be used." + "`HydrateFallback` will be used.", ); } } @@ -120,7 +117,7 @@ export function mapRouteProperties(route: RouteObject) { warning( false, "You should not include both `ErrorBoundary` and `errorElement` on your route - " + - "`ErrorBoundary` will be used." + "`ErrorBoundary` will be used.", ); } } @@ -138,13 +135,17 @@ export const hydrationRouteProperties: (keyof RouteObject)[] = [ "hydrateFallbackElement", ]; +/** + * @category Data Routers + */ export interface MemoryRouterOpts { /** * Basename path for the application. */ basename?: string; /** - * Function to provide the initial context values for all client side navigations/fetches + * Function to provide the initial context values for all client side + * navigations/fetches */ unstable_getContext?: RouterInit["unstable_getContext"]; /** @@ -157,11 +158,11 @@ export interface MemoryRouterOpts { */ hydrationData?: HydrationState; /** - * Initial entires in the in-memory history stack + * Initial entries in the in-memory history stack */ initialEntries?: InitialEntry[]; /** - * Index of `initialEntries` the application should initialize to + * Index of {@link initialEntries} the application should initialize to */ initialIndex?: number; /** @@ -176,20 +177,28 @@ export interface MemoryRouterOpts { } /** - * Create a new data router that manages the application path using an in-memory - * history stack. Useful for non-browser environments without a DOM API. + * Create a new {@link DataRouter} that manages the application path using an + * in-memory [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) + * stack. Useful for non-browser environments without a DOM API. * + * @public * @category Data Routers + * @mode data + * @param routes Application routes + * @param opts Options + * @param {MemoryRouterOpts.basename} opts.basename n/a + * @param {MemoryRouterOpts.dataStrategy} opts.dataStrategy n/a + * @param {MemoryRouterOpts.future} opts.future n/a + * @param {MemoryRouterOpts.unstable_getContext} opts.unstable_getContext n/a + * @param {MemoryRouterOpts.hydrationData} opts.hydrationData n/a + * @param {MemoryRouterOpts.initialEntries} opts.initialEntries n/a + * @param {MemoryRouterOpts.initialIndex} opts.initialIndex n/a + * @param {MemoryRouterOpts.patchRoutesOnNavigation} opts.patchRoutesOnNavigation n/a + * @returns An initialized {@link DataRouter} to pass to {@link RouterProvider | ``} */ export function createMemoryRouter( - /** - * Application routes - */ routes: RouteObject[], - /** - * Router options - */ - opts?: MemoryRouterOpts + opts?: MemoryRouterOpts, ): DataRouter { return createRouter({ basename: opts?.basename, @@ -233,14 +242,47 @@ class Deferred { } } -// Copied from react-dom types +/** + * @category Types + */ export interface RouterProviderProps { + /** + * The {@link DataRouter} instance to use for navigation and data fetching. + */ router: DataRouter; + /** + * The [`ReactDOM.flushSync`](https://react.dev/reference/react-dom/flushSync) + * implementation to use for flushing updates. + * + * You usually don't have to worry about this: + * - The `RouterProvider` exported from `react-router/dom` handles this internally for you + * - If you are rendering in a non-DOM environment, you can import + * `RouterProvider` from `react-router` and ignore this prop + */ flushSync?: (fn: () => unknown) => undefined; } /** - * Given a Remix Router instance, render the appropriate UI + * Render the UI for the given {@link DataRouter}. This component should + * typically be at the top of an app's element tree. + * + * @example + * import { createBrowserRouter } from "react-router"; + * import { RouterProvider } from "react-router/dom"; + * import { createRoot } from "react-dom/client"; + * + * const router = createBrowserRouter(routes); + * createRoot(document.getElementById("root")).render( + * + * ); + * + * @public + * @category Data Routers + * @mode data + * @param props Props + * @param {RouterProviderProps.flushSync} props.flushSync n/a + * @param {RouterProviderProps.router} props.router n/a + * @returns React element for the rendered router */ export function RouterProvider({ router, @@ -263,7 +305,7 @@ export function RouterProvider({ let setState = React.useCallback( ( newState: RouterState, - { deletedFetchers, flushSync, viewTransitionOpts } + { deletedFetchers, flushSync, viewTransitionOpts }, ) => { newState.fetchers.forEach((fetcher, key) => { if (fetcher.data !== undefined) { @@ -279,7 +321,7 @@ export function RouterProvider({ "so `ReactDOM.flushSync()` is unavailable. Please update your app " + 'to `import { RouterProvider } from "react-router/dom"` and ensure ' + "you have `react-dom` installed as a dependency to use the " + - "`flushSync` option." + "`flushSync` option.", ); let isViewTransitionAvailable = @@ -291,7 +333,7 @@ export function RouterProvider({ viewTransitionOpts == null || isViewTransitionAvailable, "You provided the `viewTransition` option to a router update, " + "but you do not appear to be running in a DOM environment as " + - "`window.startViewTransition` is not available." + "`window.startViewTransition` is not available.", ); // If this isn't a view transition or it's not available in this browser, @@ -363,7 +405,7 @@ export function RouterProvider({ }); } }, - [router.window, reactDomFlushSyncImpl, transition, renderDfd] + [router.window, reactDomFlushSyncImpl, transition, renderDfd], ); // Need to use a layout effect here so we are subscribed early enough to @@ -454,7 +496,7 @@ export function RouterProvider({ static: false, basename, }), - [router, navigator, basename] + [router, navigator, basename], ); // The fragment and {null} here are important! We need them to keep React 18's @@ -509,16 +551,36 @@ function DataRoutes({ * @category Types */ export interface MemoryRouterProps { + /** + * Application basename + */ basename?: string; + /** + * Nested {@link Route} elements describing the route tree + */ children?: React.ReactNode; + /** + * Initial entries in the in-memory history stack + */ initialEntries?: InitialEntry[]; + /** + * Index of {@link initialEntries} the application should initialize to + */ initialIndex?: number; } /** - * A `` that stores all entries in memory. + * A declarative {@link Router | ``} that stores all entries in memory. * - * @category Component Routers + * @public + * @category Declarative Routers + * @mode declarative + * @param props Props + * @param {MemoryRouterProps.basename} props.basename n/a + * @param {MemoryRouterProps.children} props.children n/a + * @param {MemoryRouterProps.initialEntries} props.initialEntries n/a + * @param {MemoryRouterProps.initialIndex} props.initialIndex n/a + * @returns A declarative in memory router for client side routing. */ export function MemoryRouter({ basename, @@ -544,7 +606,7 @@ export function MemoryRouter({ (newState: { action: NavigationType; location: Location }) => { React.startTransition(() => setStateImpl(newState)); }, - [setStateImpl] + [setStateImpl], ); React.useLayoutEffect(() => history.listen(setState), [history, setState]); @@ -564,20 +626,45 @@ export function MemoryRouter({ * @category Types */ export interface NavigateProps { + /** + * The path to navigate to. This can be a string or a {@link Path} object + */ to: To; + /** + * Whether to replace the current entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) + * stack + */ replace?: boolean; + /** + * State to pass to the new {@link Location} to store in [`history.state`](https://developer.mozilla.org/en-US/docs/Web/API/History/state). + */ state?: any; + /** + * How to interpret relative routing in the {@link to} prop. + * See {@link RelativeRoutingType}. + */ relative?: RelativeRoutingType; } /** - * A component-based version of {@link useNavigate} to use in a [`React.Component - * Class`](https://reactjs.org/docs/react-component.html) where hooks are not - * able to be used. + * A component-based version of {@link useNavigate} to use in a + * [`React.Component` class](https://react.dev/reference/react/Component) where + * hooks cannot be used. + * + * It's recommended to avoid using this component in favor of {@link useNavigate}. * - * It's recommended to avoid using this component in favor of {@link useNavigate} + * @example + * * + * @public * @category Components + * @param props Props + * @param {NavigateProps.relative} props.relative n/a + * @param {NavigateProps.replace} props.replace n/a + * @param {NavigateProps.state} props.state n/a + * @param {NavigateProps.to} props.to n/a + * @returns {void} + * */ export function Navigate({ to, @@ -589,7 +676,7 @@ export function Navigate({ useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of // the router loaded. We can help them understand how to avoid that. - ` may be used only in the context of a component.` + ` may be used only in the context of a component.`, ); let { static: isStatic } = React.useContext(NavigationContext); @@ -598,7 +685,7 @@ export function Navigate({ !isStatic, ` must not be used on the initial render in a . ` + `This is a no-op, but you should modify your code so the is ` + - `only ever rendered in response to some user interaction or state change.` + `only ever rendered in response to some user interaction or state change.`, ); let { matches } = React.useContext(RouteContext); @@ -611,7 +698,7 @@ export function Navigate({ to, getResolveToMatches(matches), locationPathname, - relative === "path" + relative === "path", ); let jsonPath = JSON.stringify(path); @@ -627,34 +714,39 @@ export function Navigate({ */ export interface OutletProps { /** - Provides a context value to the element tree below the outlet. Use when the parent route needs to provide values to child routes. - - ```tsx - - ``` - - Access the context with {@link useOutletContext}. + * Provides a context value to the element tree below the outlet. Use when + * the parent route needs to provide values to child routes. + * + * ```tsx + * + * ``` + * + * Access the context with {@link useOutletContext}. */ context?: unknown; } /** - Renders the matching child route of a parent route or nothing if no child route matches. - - ```tsx - import { Outlet } from "react-router" - - export default function SomeParent() { - return ( -
-

Parent Content

- -
- ); - } - ``` - - @category Components + * Renders the matching child route of a parent route or nothing if no child + * route matches. + * + * @example + * import { Outlet } from "react-router"; + * + * export default function SomeParent() { + * return ( + *
+ *

Parent Content

+ * + *
+ * ); + * } + * + * @public + * @category Components + * @param props Props + * @param {OutletProps.context} props.context n/a + * @returns React element for the rendered outlet or `null` if no child route matches. */ export function Outlet(props: OutletProps): React.ReactElement | null { return useOutlet(props.context); @@ -664,22 +756,82 @@ export function Outlet(props: OutletProps): React.ReactElement | null { * @category Types */ export interface PathRouteProps { + /** + * Whether the path should be case-sensitive. Defaults to `false`. + */ caseSensitive?: NonIndexRouteObject["caseSensitive"]; + /** + * The path pattern to match. If unspecified or empty, then this becomes a + * layout route. + */ path?: NonIndexRouteObject["path"]; + /** + * The unique identifier for this route (for use with {@link DataRouter}s) + */ id?: NonIndexRouteObject["id"]; + /** + * A function that returns a promise that resolves to the route object. + * Used for code-splitting routes. + * See [`lazy`](../../start/data/route-object#lazy). + */ lazy?: LazyRouteFunction; + /** + * The route loader. + * See [`loader`](../../start/data/route-object#loader). + */ loader?: NonIndexRouteObject["loader"]; + /** + * The route action. + * See [`action`](../../start/data/route-object#action). + */ action?: NonIndexRouteObject["action"]; hasErrorBoundary?: NonIndexRouteObject["hasErrorBoundary"]; + /** + * The route shouldRevalidate function. + * See [`shouldRevalidate`](../../start/data/route-object#shouldRevalidate). + */ shouldRevalidate?: NonIndexRouteObject["shouldRevalidate"]; + /** + * The route handle. + */ handle?: NonIndexRouteObject["handle"]; + /** + * Whether this is an index route. + */ index?: false; + /** + * Child Route components + */ children?: React.ReactNode; + /** + * The React element to render when this Route matches. + * Mutually exclusive with {@link Component}. + */ element?: React.ReactNode | null; + /** + * The React element to render while this router is loading data. + * Mutually exclusive with {@link HydrateFallback}. + */ hydrateFallbackElement?: React.ReactNode | null; + /** + * The React element to render at this route if an error occurs. + * Mutually exclusive with {@link ErrorBoundary}. + */ errorElement?: React.ReactNode | null; + /** + * The React Component to render when this route matches. + * Mutually exclusive with {@link element}. + */ Component?: React.ComponentType | null; + /** + * The React Component to render while this router is loading data. + * Mutually exclusive with {@link hydrateFallbackElement}. + */ HydrateFallback?: React.ComponentType | null; + /** + * The React Component to render at this route if an error occurs. + * Mutually exclusive with {@link errorElement}. + */ ErrorBoundary?: React.ComponentType | null; } @@ -692,22 +844,82 @@ export interface LayoutRouteProps extends PathRouteProps {} * @category Types */ export interface IndexRouteProps { + /** + * Whether the path should be case-sensitive. Defaults to `false`. + */ caseSensitive?: IndexRouteObject["caseSensitive"]; + /** + * The path pattern to match. If unspecified or empty, then this becomes a + * layout route. + */ path?: IndexRouteObject["path"]; + /** + * The unique identifier for this route (for use with {@link DataRouter}s) + */ id?: IndexRouteObject["id"]; + /** + * A function that returns a promise that resolves to the route object. + * Used for code-splitting routes. + * See [`lazy`](../../start/data/route-object#lazy). + */ lazy?: LazyRouteFunction; + /** + * The route loader. + * See [`loader`](../../start/data/route-object#loader). + */ loader?: IndexRouteObject["loader"]; + /** + * The route action. + * See [`action`](../../start/data/route-object#action). + */ action?: IndexRouteObject["action"]; hasErrorBoundary?: IndexRouteObject["hasErrorBoundary"]; + /** + * The route shouldRevalidate function. + * See [`shouldRevalidate`](../../start/data/route-object#shouldRevalidate). + */ shouldRevalidate?: IndexRouteObject["shouldRevalidate"]; + /** + * The route handle. + */ handle?: IndexRouteObject["handle"]; + /** + * Whether this is an index route. + */ index: true; + /** + * Child Route components + */ children?: undefined; + /** + * The React element to render when this Route matches. + * Mutually exclusive with {@link Component}. + */ element?: React.ReactNode | null; + /** + * The React element to render while this router is loading data. + * Mutually exclusive with {@link HydrateFallback}. + */ hydrateFallbackElement?: React.ReactNode | null; + /** + * The React element to render at this route if an error occurs. + * Mutually exclusive with {@link ErrorBoundary}. + */ errorElement?: React.ReactNode | null; + /** + * The React Component to render when this route matches. + * Mutually exclusive with {@link element}. + */ Component?: React.ComponentType | null; + /** + * The React Component to render while this router is loading data. + * Mutually exclusive with {@link hydrateFallbackElement}. + */ HydrateFallback?: React.ComponentType | null; + /** + * The React Component to render at this route if an error occurs. + * Mutually exclusive with {@link errorElement}. + */ ErrorBoundary?: React.ComponentType | null; } @@ -719,13 +931,61 @@ export type RouteProps = PathRouteProps | LayoutRouteProps | IndexRouteProps; * do not participate in data loading, actions, code splitting, or any other * route module features. * + * @example + * // Usually used in a declarative router + * function App() { + * return ( + * + * + * } /> + * } /> + * } /> + * + * + * ); + * } + * + * // But can be used with a data router as well if you prefer the JSX notation + * const routes = createRoutesFromElements( + * <> + * + * + * + * + * ); + * + * const router = createBrowserRouter(routes); + * + * function App() { + * return ; + * } + * + * @public * @category Components + * @param props Props + * @param {PathRouteProps.action} props.action n/a + * @param {PathRouteProps.caseSensitive} props.caseSensitive n/a + * @param {PathRouteProps.Component} props.Component n/a + * @param {PathRouteProps.children} props.children n/a + * @param {PathRouteProps.element} props.element n/a + * @param {PathRouteProps.ErrorBoundary} props.ErrorBoundary n/a + * @param {PathRouteProps.errorElement} props.errorElement n/a + * @param {PathRouteProps.handle} props.handle n/a + * @param {PathRouteProps.HydrateFallback} props.HydrateFallback n/a + * @param {PathRouteProps.hydrateFallbackElement} props.hydrateFallbackElement n/a + * @param {PathRouteProps.id} props.id n/a + * @param {PathRouteProps.index} props.index n/a + * @param {PathRouteProps.lazy} props.lazy n/a + * @param {PathRouteProps.loader} props.loader n/a + * @param {PathRouteProps.path} props.path n/a + * @param {PathRouteProps.shouldRevalidate} props.shouldRevalidate n/a + * @returns {void} */ -export function Route(_props: RouteProps): React.ReactElement | null { +export function Route(props: RouteProps): React.ReactElement | null { invariant( false, `A is only ever to be used as the child of element, ` + - `never rendered directly. Please wrap your in a .` + `never rendered directly. Please wrap your in a .`, ); } @@ -733,11 +993,33 @@ export function Route(_props: RouteProps): React.ReactElement | null { * @category Types */ export interface RouterProps { + /** + * The base path for the application. This is prepended to all locations + */ basename?: string; + /** + * Nested {@link Route} elements describing the route tree + */ children?: React.ReactNode; + /** + * The location to match against. Defaults to the current location. + * This can be a string or a {@link Location} object. + */ location: Partial | string; + /** + * The type of navigation that triggered this location change. + * Defaults to {@link NavigationType.Pop}. + */ navigationType?: NavigationType; + /** + * The navigator to use for navigation. This is usually a history object + * or a custom navigator that implements the {@link Navigator} interface. + */ navigator: Navigator; + /** + * Whether this router is static or not (used for SSR). If `true`, the router + * will not be reactive to location changes. + */ static?: boolean; } @@ -745,10 +1027,21 @@ export interface RouterProps { * Provides location context for the rest of the app. * * Note: You usually won't render a `` directly. Instead, you'll render a - * router that is more specific to your environment such as a `` - * in web browsers or a `` for server rendering. + * router that is more specific to your environment such as a {@link BrowserRouter} + * in web browsers or a {@link ServerRouter} for server rendering. * - * @category Components + * @public + * @category Declarative Routers + * @mode declarative + * @param props Props + * @param {RouterProps.basename} props.basename n/a + * @param {RouterProps.children} props.children n/a + * @param {RouterProps.location} props.location n/a + * @param {RouterProps.navigationType} props.navigationType n/a + * @param {RouterProps.navigator} props.navigator n/a + * @param {RouterProps.static} props.static n/a + * @returns React element for the rendered router or `null` if the location does + * not match the {@link props.basename} */ export function Router({ basename: basenameProp = "/", @@ -761,7 +1054,7 @@ export function Router({ invariant( !useInRouterContext(), `You cannot render a inside another .` + - ` You should never have more than one in your app.` + ` You should never have more than one in your app.`, ); // Preserve trailing slashes on basename, so we can let the user control @@ -774,7 +1067,7 @@ export function Router({ static: staticProp, future: {}, }), - [basename, navigator, staticProp] + [basename, navigator, staticProp], ); if (typeof locationProp === "string") { @@ -812,7 +1105,7 @@ export function Router({ locationContext != null, ` is not able to match the URL ` + `"${pathname}${search}${hash}" because it does not start with the ` + - `basename, so the won't render anything.` + `basename, so the won't render anything.`, ); if (locationContext == null) { @@ -834,29 +1127,33 @@ export interface RoutesProps { * Nested {@link Route} elements */ children?: React.ReactNode; - /** - * The location to match against. Defaults to the current location. + * The {@link Location} to match against. Defaults to the current location. */ location?: Partial | string; } /** - Renders a branch of {@link Route | ``} that best matches the current - location. Note that these routes do not participate in data loading, actions, - code splitting, or any other route module features. - - ```tsx - import { Routes, Route } from "react-router" - - - } /> - } /> - }> - - ``` - - @category Components + * Renders a branch of {@link Route | ``s} that best matches the current + * location. Note that these routes do not participate in [data loading](../../start/framework/route-module#loader), + * [`action`](../../start/framework/route-module#action), code splitting, or + * any other [route module](../../start/framework/route-module) features. + * + * @example + * import { Route, Routes } from "react-router"; + * + * + * } /> + * } /> + * }> + * + * + * @public + * @category Components + * @param props Props + * @param {RoutesProps.children} props.children n/a + * @param {RoutesProps.location} props.location n/a + * @returns React element for the rendered routes or `null` if no route matches */ export function Routes({ children, @@ -874,147 +1171,156 @@ export interface AwaitResolveRenderFunction { */ export interface AwaitProps { /** - When using a function, the resolved value is provided as the parameter. - - ```tsx [2] - - {(resolvedReviews) => } - - ``` - - When using React elements, {@link useAsyncValue} will provide the - resolved value: - - ```tsx [2] - - - - - function Reviews() { - const resolvedReviews = useAsyncValue() - return
...
- } - ``` - */ + * When using a function, the resolved value is provided as the parameter. + * + * ```tsx [2] + * + * {(resolvedReviews) => } + * + * ``` + * + * When using React elements, {@link useAsyncValue} will provide the + * resolved value: + * + * ```tsx [2] + * + * + * + * + * function Reviews() { + * const resolvedReviews = useAsyncValue(); + * return
...
; + * } + * ``` + */ children: React.ReactNode | AwaitResolveRenderFunction; /** - The error element renders instead of the children when the promise rejects. - - ```tsx - Oops} - resolve={reviewsPromise} - > - - - ``` - - To provide a more contextual error, you can use the {@link useAsyncError} in a - child component - - ```tsx - } - resolve={reviewsPromise} - > - - - - function ReviewsError() { - const error = useAsyncError() - return
Error loading reviews: {error.message}
- } - ``` - - If you do not provide an errorElement, the rejected value will bubble up to - the nearest route-level {@link NonIndexRouteObject#ErrorBoundary | ErrorBoundary} and be accessible - via {@link useRouteError} hook. - */ + * The error element renders instead of the `children` when the [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) + * rejects. + * + * ```tsx + * Oops} + * resolve={reviewsPromise} + * > + * + * + * ``` + * + * To provide a more contextual error, you can use the {@link useAsyncError} in a + * child component + * + * ```tsx + * } + * resolve={reviewsPromise} + * > + * + * + * + * function ReviewsError() { + * const error = useAsyncError(); + * return
Error loading reviews: {error.message}
; + * } + * ``` + * + * If you do not provide an `errorElement`, the rejected value will bubble up + * to the nearest route-level [`ErrorBoundary`](../../start/framework/route-module#errorboundary) + * and be accessible via the {@link useRouteError} hook. + */ errorElement?: React.ReactNode; /** - Takes a promise returned from a {@link LoaderFunction | loader} value to be resolved and rendered. - - ```jsx - import { useLoaderData, Await } from "react-router" - - export async function loader() { - let reviews = getReviews() // not awaited - let book = await getBook() - return { - book, - reviews, // this is a promise - } - } - - export default function Book() { - const { - book, - reviews, // this is the same promise - } = useLoaderData() - - return ( -
-

{book.title}

-

{book.description}

- }> - - - - -
- ); - } - ``` + * Takes a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) + * returned from a [`loader`](../../start/framework/route-module#loader) to be + * resolved and rendered. + * + * ```tsx + * import { Await, useLoaderData } from "react-router"; + * + * export async function loader() { + * let reviews = getReviews(); // not awaited + * let book = await getBook(); + * return { + * book, + * reviews, // this is a promise + * }; + * } + * + * export default function Book() { + * const { + * book, + * reviews, // this is the same promise + * } = useLoaderData(); + * + * return ( + *
+ *

{book.title}

+ *

{book.description}

+ * }> + * + * + * + * + *
+ * ); + * } + * ``` */ resolve: Resolve; } /** -Used to render promise values with automatic error handling. - -```tsx -import { Await, useLoaderData } from "react-router"; - -export function loader() { - // not awaited - const reviews = getReviews() - // awaited (blocks the transition) - const book = await fetch("https://redirect.github.com/api/book").then((res) => res.json()) - return { book, reviews } -} - -function Book() { - const { book, reviews } = useLoaderData(); - return ( -
-

{book.title}

-

{book.description}

- }> - Could not load reviews 😬
- } - children={(resolvedReviews) => ( - - )} - /> - - - ); -} -``` - -**Note:** `` expects to be rendered inside of a `` - -@category Components - -*/ + * Used to render promise values with automatic error handling. + * + * **Note:** `` expects to be rendered inside a [``](https://react.dev/reference/react/Suspense) + * + * @example + * import { Await, useLoaderData } from "react-router"; + * + * export async function loader() { + * // not awaited + * const reviews = getReviews(); + * // awaited (blocks the transition) + * const book = await fetch("https://redirect.github.com/api/book").then((res) => res.json()); + * return { book, reviews }; + * } + * + * function Book() { + * const { book, reviews } = useLoaderData(); + * return ( + *
+ *

{book.title}

+ *

{book.description}

+ * }> + * Could not load reviews 😬
+ * } + * children={(resolvedReviews) => ( + * + * )} + * /> + *
+ * + * ); + * } + * + * @public + * @category Components + * @mode framework + * @mode data + * @param props Props + * @param {AwaitProps.children} props.children n/a + * @param {AwaitProps.errorElement} props.errorElement n/a + * @param {AwaitProps.resolve} props.resolve n/a + * @returns React element for the rendered awaited value + */ export function Await({ children, errorElement, @@ -1059,7 +1365,7 @@ class AwaitErrorBoundary extends React.Component< console.error( " caught the following error during render", error, - errorInfo + errorInfo, ); } @@ -1089,8 +1395,8 @@ class AwaitErrorBoundary extends React.Component< "_error" in promise ? AwaitRenderStatus.error : "_data" in promise - ? AwaitRenderStatus.success - : AwaitRenderStatus.pending; + ? AwaitRenderStatus.success + : AwaitRenderStatus.pending; } else { // Raw (untracked) promise - track it status = AwaitRenderStatus.pending; @@ -1099,7 +1405,7 @@ class AwaitErrorBoundary extends React.Component< (data: any) => Object.defineProperty(resolve, "_data", { get: () => data }), (error: any) => - Object.defineProperty(resolve, "_error", { get: () => error }) + Object.defineProperty(resolve, "_error", { get: () => error }), ); } @@ -1123,10 +1429,7 @@ class AwaitErrorBoundary extends React.Component< } } -/** - * @private - * Indirection to leverage useAsyncValue for a render-prop API on `` - */ +// Indirection to leverage useAsyncValue for a render-prop API on `` function ResolveAwait({ children, }: { @@ -1147,10 +1450,14 @@ function ResolveAwait({ * `` to create a route config from its children. * * @category Utils + * @mode data + * @param children The React children to convert into a route config + * @param parentPath The path of the parent route, used to generate unique IDs. + * @returns An array of {@link RouteObject}s that can be used with a {@link DataRouter} */ export function createRoutesFromChildren( children: React.ReactNode, - parentPath: number[] = [] + parentPath: number[] = [], ): RouteObject[] { let routes: RouteObject[] = []; @@ -1167,7 +1474,7 @@ export function createRoutesFromChildren( // Transparently support React.Fragment and its children. routes.push.apply( routes, - createRoutesFromChildren(element.props.children, treePath) + createRoutesFromChildren(element.props.children, treePath), ); return; } @@ -1176,12 +1483,12 @@ export function createRoutesFromChildren( element.type === Route, `[${ typeof element.type === "string" ? element.type : element.type.name - }] is not a component. All component children of must be a or ` + }] is not a component. All component children of must be a or `, ); invariant( !element.props.index || !element.props.children, - "An index route cannot have child routes." + "An index route cannot have child routes.", ); let route: RouteObject = { @@ -1209,7 +1516,7 @@ export function createRoutesFromChildren( if (element.props.children) { route.children = createRoutesFromChildren( element.props.children, - treePath + treePath, ); } @@ -1220,17 +1527,45 @@ export function createRoutesFromChildren( } /** - * Create route objects from JSX elements instead of arrays of objects + * Create route objects from JSX elements instead of arrays of objects. + * + * @example + * const routes = createRoutesFromElements( + * <> + * + * + * + * + * ); + * + * const router = createBrowserRouter(routes); + * + * function App() { + * return ; + * } + * + * @name createRoutesFromElements + * @public + * @category Utils + * @mode data + * @param children The React children to convert into a route config + * @param parentPath The path of the parent route, used to generate unique IDs. + * This is used for internal recursion and is not intended to be used by the + * application developer. + * @returns An array of {@link RouteObject}s that can be used with a {@link DataRouter} */ -export let createRoutesFromElements = createRoutesFromChildren; +export const createRoutesFromElements = createRoutesFromChildren; /** - * Renders the result of `matchRoutes()` into a React element. + * Renders the result of {@link matchRoutes} into a React element. * + * @public * @category Utils + * @param matches The array of {@link RouteMatch | route matches} to render + * @returns A React element that renders the matched routes or `null` if no matches */ export function renderMatches( - matches: RouteMatch[] | null + matches: RouteMatch[] | null, ): React.ReactElement | null { return _renderMatches(matches); } diff --git a/packages/react-router/lib/context.ts b/packages/react-router/lib/context.ts index 9ff5c96891..21b9c380a4 100644 --- a/packages/react-router/lib/context.ts +++ b/packages/react-router/lib/context.ts @@ -73,7 +73,7 @@ export type DataRouteObject = RouteObject & { export interface RouteMatch< ParamKey extends string = string, - RouteObjectType extends RouteObject = RouteObject + RouteObjectType extends RouteObject = RouteObject, > extends AgnosticRouteMatch {} export interface DataRouteMatch extends RouteMatch {} @@ -128,7 +128,7 @@ ViewTransitionContext.displayName = "ViewTransition"; export type FetchersContextObject = Map; export const FetchersContext = React.createContext( - new Map() + new Map(), ); FetchersContext.displayName = "Fetchers"; @@ -178,7 +178,7 @@ interface NavigationContextObject { } export const NavigationContext = React.createContext( - null! + null!, ); NavigationContext.displayName = "Navigation"; @@ -188,7 +188,7 @@ interface LocationContextObject { } export const LocationContext = React.createContext( - null! + null!, ); LocationContext.displayName = "Location"; diff --git a/packages/react-router/lib/dom-export/hydrated-router.tsx b/packages/react-router/lib/dom-export/hydrated-router.tsx index 949af934b2..10215fab4a 100644 --- a/packages/react-router/lib/dom-export/hydrated-router.tsx +++ b/packages/react-router/lib/dom-export/hydrated-router.tsx @@ -56,7 +56,7 @@ function initSsrInfo(): void { if (importMap?.textContent) { try { window.__reactRouterManifest.sri = JSON.parse( - importMap.textContent + importMap.textContent, ).integrity; } catch (err) { console.error("Failed to parse import map", err); @@ -85,7 +85,7 @@ function createHydratedRouter({ if (!ssrInfo) { throw new Error( "You must be using the SSR features of React Router in order to skip " + - "passing a `router` prop to ``" + "passing a `router` prop to ``", ); } @@ -122,7 +122,7 @@ function createHydratedRouter({ ssrInfo.routeModules, ssrInfo.context.state, ssrInfo.context.ssr, - ssrInfo.context.isSpaMode + ssrInfo.context.isSpaMode, ); let hydrationData: HydrationState | undefined = undefined; @@ -152,7 +152,7 @@ function createHydratedRouter({ }), window.location, window.__reactRouterContext?.basename, - ssrInfo.context.isSpaMode + ssrInfo.context.isSpaMode, ); if (hydrationData && hydrationData.errors) { @@ -180,7 +180,7 @@ function createHydratedRouter({ ssrInfo.manifest, ssrInfo.routeModules, ssrInfo.context.ssr, - ssrInfo.context.basename + ssrInfo.context.basename, ), patchRoutesOnNavigation: getPatchRoutesOnNavigationFunction( ssrInfo.manifest, @@ -188,7 +188,7 @@ function createHydratedRouter({ ssrInfo.context.ssr, ssrInfo.context.routeDiscovery, ssrInfo.context.isSpaMode, - ssrInfo.context.basename + ssrInfo.context.basename, ), }); ssrInfo.router = router; @@ -218,10 +218,17 @@ interface HydratedRouterProps { } /** - * Framework-mode router component to be used in `entry.client.tsx` to hydrate a - * router from a `ServerRouter` + * Framework-mode router component to be used to to hydrate a router from a + * `ServerRouter`. See [`entry.client.tsx`](../api/framework-conventions/entry.client.tsx). * - * @category Component Routers + * @public + * @category Framework Routers + * @mode framework + * @param props Props + * @param props.unstable_getContext Context object to passed through to + * {@link createBrowserRouter} and made available to `clientLoader`/`clientAction` + * functions + * @returns A React element that represents the hydrated application. */ export function HydratedRouter(props: HydratedRouterProps) { if (!router) { @@ -236,7 +243,7 @@ export function HydratedRouter(props: HydratedRouterProps) { let [criticalCss, setCriticalCss] = React.useState( process.env.NODE_ENV === "development" ? ssrInfo?.context.criticalCss - : undefined + : undefined, ); React.useEffect(() => { if (process.env.NODE_ENV === "development") { @@ -289,7 +296,7 @@ export function HydratedRouter(props: HydratedRouterProps) { ssrInfo.routeModules, ssrInfo.context.ssr, ssrInfo.context.routeDiscovery, - ssrInfo.context.isSpaMode + ssrInfo.context.isSpaMode, ); // We need to include a wrapper RemixErrorBoundary here in case the root error diff --git a/packages/react-router/lib/dom/dom.ts b/packages/react-router/lib/dom/dom.ts index 24ec0a6945..82404939fb 100644 --- a/packages/react-router/lib/dom/dom.ts +++ b/packages/react-router/lib/dom/dom.ts @@ -33,7 +33,7 @@ function isModifiedEvent(event: LimitedMouseEvent) { export function shouldProcessLinkClick( event: LimitedMouseEvent, - target?: string + target?: string, ) { return ( event.button === 0 && // Ignore everything but left clicks @@ -77,7 +77,7 @@ export type URLSearchParamsInit = @category Utils */ export function createSearchParams( - init: URLSearchParamsInit = "" + init: URLSearchParamsInit = "", ): URLSearchParams { return new URLSearchParams( typeof init === "string" || @@ -87,15 +87,15 @@ export function createSearchParams( : Object.keys(init).reduce((memo, key) => { let value = init[key]; return memo.concat( - Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]] + Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]], ); - }, [] as ParamKeyValuePair[]) + }, [] as ParamKeyValuePair[]), ); } export function getSearchParamsForLocation( locationSearch: string, - defaultSearchParams: URLSearchParams | null + defaultSearchParams: URLSearchParams | null, ) { let searchParams = createSearchParams(locationSearch); @@ -143,7 +143,7 @@ function isFormDataSubmitterSupported() { new FormData( document.createElement("form"), // @ts-expect-error if FormData supports the submitter parameter, this will throw - 0 + 0, ); _formDataSupportsSubmitter = false; } catch (e) { @@ -242,7 +242,7 @@ function getFormEncType(encType: string | null) { warning( false, `"${encType}" is not a valid \`encType\` for \`
\`/\`\` ` + - `and will default to "${defaultEncType}"` + `and will default to "${defaultEncType}"`, ); return null; @@ -252,7 +252,7 @@ function getFormEncType(encType: string | null) { export function getFormSubmissionInfo( target: SubmitTarget, - basename: string + basename: string, ): { action: string | null; method: string; @@ -285,7 +285,7 @@ export function getFormSubmissionInfo( if (form == null) { throw new Error( - `Cannot submit a