diff --git a/.changeset/fog-of-war.md b/.changeset/fog-of-war.md deleted file mode 100644 index 1eea19ad1d..0000000000 --- a/.changeset/fog-of-war.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"react-router-dom": minor -"react-router": minor -"@remix-run/router": minor ---- - -Add support for Lazy Route Discovery (a.k.a. Fog of War) - -- RFC: https://github.com/remix-run/react-router/discussions/11113 -- `unstable_patchRoutesOnMiss` docs: https://reactrouter.com/en/main/routers/create-browser-router diff --git a/.changeset/gorgeous-geese-sit.md b/.changeset/gorgeous-geese-sit.md deleted file mode 100644 index c38daf1ce8..0000000000 --- a/.changeset/gorgeous-geese-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"react-router-dom": patch ---- - -Fix `fetcher.submit` types - remove incorrect `navigate`/`fetcherKey`/`unstable_viewTransition` options because they are only relevant for `useSubmit` diff --git a/.changeset/smooth-sloths-exist.md b/.changeset/smooth-sloths-exist.md deleted file mode 100644 index 6c6a91dcbc..0000000000 --- a/.changeset/smooth-sloths-exist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"react-router-dom": patch -"react-router-dom-v5-compat": patch ---- - -Allow falsy `location.state` values passed to `` diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml new file mode 100644 index 0000000000..a33fcd0796 --- /dev/null +++ b/.github/workflows/release-nightly.yml @@ -0,0 +1,95 @@ +name: ๐ŸŒ’ Nightly Release + +on: + workflow_dispatch: + schedule: + - cron: "0 7 * * *" # every day at 12AM PST + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CI: true + +jobs: + # HEADS UP! this "nightly" job will only ever run on the `main` branch due to + # it being a cron job, and the last commit on main will be what github shows + # as the trigger however in the checkout below we specify the `v7` branch, + # so all the scripts in this job will be ran from that, confusing i know, so + # in some cases we'll need to create multiple PRs when modifying nightly + # release processes + nightly: + name: ๐ŸŒ’ Nightly Release + if: github.repository == 'remix-run/react-router' + runs-on: ubuntu-latest + outputs: + # allows this to be used in the `comment` job below - will be undefined + # if there's no release necessary + NEXT_VERSION: ${{ steps.version.outputs.NEXT_VERSION }} + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v4 + with: + ref: v7 + # checkout using a custom token so that we can push later on + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: ๐Ÿ“ฆ Setup pnpm + uses: pnpm/action-setup@v3.0.0 + + - name: โŽ” Setup node + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + cache: "pnpm" + + - name: ๐Ÿ“ฅ Install deps + run: pnpm install --frozen-lockfile + + - name: ๐Ÿ•ต๏ธ Check for changes + id: version + run: | + SHORT_SHA=$(git rev-parse --short HEAD) + + # get latest nightly tag + LATEST_NIGHTLY_TAG=$(git tag -l v0.0.0-nightly-\* --sort=-creatordate | head -n 1) + + # check if last commit to v7 starts with the nightly tag we're about + # to create (minus the date) + # if it is, we'll skip the nightly creation + # if not, we'll create a new nightly tag + if [[ ${LATEST_NIGHTLY_TAG} == v0.0.0-nightly-${SHORT_SHA}-* ]]; then + echo "๐Ÿ›‘ Latest nightly tag is the same as the latest commit sha, skipping nightly release" + else + # yyyyMMdd format (e.g. 20221207) + DATE=$(date '+%Y%m%d') + # v0.0.0-nightly-- + NEXT_VERSION=0.0.0-nightly-${SHORT_SHA}-${DATE} + # set output so it can be used in other jobs + echo "NEXT_VERSION=${NEXT_VERSION}" >> $GITHUB_OUTPUT + fi + + - name: โคด๏ธ Update version + if: steps.version.outputs.NEXT_VERSION + run: | + git config --local user.email "hello@remix.run" + git config --local user.name "Remix Run Bot" + git checkout -b nightly/${{ steps.version.outputs.NEXT_VERSION }} + pnpm run version ${{steps.version.outputs.NEXT_VERSION}} + git push origin --tags + + - name: ๐Ÿ— Build + if: steps.version.outputs.NEXT_VERSION + run: pnpm build + + - name: ๐Ÿ” Setup npm auth + if: steps.version.outputs.NEXT_VERSION + run: | + echo "registry=https://registry.npmjs.org" >> ~/.npmrc + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc + + - name: ๐Ÿš€ Publish + if: steps.version.outputs.NEXT_VERSION + run: pnpm run publish diff --git a/CHANGELOG.md b/CHANGELOG.md index 365d2eade9..d2e56030d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,154 +13,161 @@ We manage release notes in this file instead of the paginated Github Releases Pa Table of Contents - [React Router Releases](#react-router-releases) - - [v6.23.1](#v6231) + - [v6.24.0](#v6240) + - [What's Changed](#whats-changed) + - [Lazy Route Discovery (a.k.a. "Fog of War")](#lazy-route-discovery-aka-fog-of-war) + - [Minor Changes](#minor-changes) - [Patch Changes](#patch-changes) + - [v6.23.1](#v6231) + - [Patch Changes](#patch-changes-1) - [v6.23.0](#v6230) - - [What's Changed](#whats-changed) + - [What's Changed](#whats-changed-1) - [Data Strategy (unstable)](#data-strategy-unstable) - [Skip Action Error Revalidation (unstable)](#skip-action-error-revalidation-unstable) - - [Minor Changes](#minor-changes) + - [Minor Changes](#minor-changes-1) - [v6.22.3](#v6223) - - [Patch Changes](#patch-changes-1) - - [v6.22.2](#v6222) - [Patch Changes](#patch-changes-2) - - [v6.22.1](#v6221) + - [v6.22.2](#v6222) - [Patch Changes](#patch-changes-3) + - [v6.22.1](#v6221) + - [Patch Changes](#patch-changes-4) - [v6.22.0](#v6220) - - [What's Changed](#whats-changed-1) + - [What's Changed](#whats-changed-2) - [Core Web Vitals Technology Report Flag](#core-web-vitals-technology-report-flag) - - [Minor Changes](#minor-changes-1) - - [Patch Changes](#patch-changes-4) - - [v6.21.3](#v6213) + - [Minor Changes](#minor-changes-2) - [Patch Changes](#patch-changes-5) - - [v6.21.2](#v6212) + - [v6.21.3](#v6213) - [Patch Changes](#patch-changes-6) - - [v6.21.1](#v6211) + - [v6.21.2](#v6212) - [Patch Changes](#patch-changes-7) + - [v6.21.1](#v6211) + - [Patch Changes](#patch-changes-8) - [v6.21.0](#v6210) - - [What's Changed](#whats-changed-2) + - [What's Changed](#whats-changed-3) - [`future.v7_relativeSplatPath`](#futurev7_relativesplatpath) - [Partial Hydration](#partial-hydration) - - [Minor Changes](#minor-changes-2) - - [Patch Changes](#patch-changes-8) - - [v6.20.1](#v6201) - - [Patch Changes](#patch-changes-9) - - [v6.20.0](#v6200) - [Minor Changes](#minor-changes-3) + - [Patch Changes](#patch-changes-9) + - [v6.20.1](#v6201) - [Patch Changes](#patch-changes-10) - - [v6.19.0](#v6190) - - [What's Changed](#whats-changed-3) - - [`unstable_flushSync` API](#unstable_flushsync-api) + - [v6.20.0](#v6200) - [Minor Changes](#minor-changes-4) - [Patch Changes](#patch-changes-11) - - [v6.18.0](#v6180) + - [v6.19.0](#v6190) - [What's Changed](#whats-changed-4) - - [New Fetcher APIs](#new-fetcher-apis) - - [Persistence Future Flag (`future.v7_fetcherPersist`)](#persistence-future-flag-futurev7_fetcherpersist) + - [`unstable_flushSync` API](#unstable_flushsync-api) - [Minor Changes](#minor-changes-5) - [Patch Changes](#patch-changes-12) - - [v6.17.0](#v6170) + - [v6.18.0](#v6180) - [What's Changed](#whats-changed-5) - - [View Transitions ๐Ÿš€](#view-transitions-) + - [New Fetcher APIs](#new-fetcher-apis) + - [Persistence Future Flag (`future.v7_fetcherPersist`)](#persistence-future-flag-futurev7_fetcherpersist) - [Minor Changes](#minor-changes-6) - [Patch Changes](#patch-changes-13) - - [v6.16.0](#v6160) + - [v6.17.0](#v6170) + - [What's Changed](#whats-changed-6) + - [View Transitions ๐Ÿš€](#view-transitions-) - [Minor Changes](#minor-changes-7) - [Patch Changes](#patch-changes-14) - - [v6.15.0](#v6150) + - [v6.16.0](#v6160) - [Minor Changes](#minor-changes-8) - [Patch Changes](#patch-changes-15) - - [v6.14.2](#v6142) + - [v6.15.0](#v6150) + - [Minor Changes](#minor-changes-9) - [Patch Changes](#patch-changes-16) - - [v6.14.1](#v6141) + - [v6.14.2](#v6142) - [Patch Changes](#patch-changes-17) - - [v6.14.0](#v6140) - - [What's Changed](#whats-changed-6) - - [JSON/Text Submissions](#jsontext-submissions) - - [Minor Changes](#minor-changes-9) + - [v6.14.1](#v6141) - [Patch Changes](#patch-changes-18) - - [v6.13.0](#v6130) + - [v6.14.0](#v6140) - [What's Changed](#whats-changed-7) + - [JSON/Text Submissions](#jsontext-submissions) - [Minor Changes](#minor-changes-10) - [Patch Changes](#patch-changes-19) - - [v6.12.1](#v6121) - - [Patch Changes](#patch-changes-20) - - [v6.12.0](#v6120) + - [v6.13.0](#v6130) - [What's Changed](#whats-changed-8) - - [`React.startTransition` support](#reactstarttransition-support) + - [v7\_startTransition](#v7_starttransition) - [Minor Changes](#minor-changes-11) + - [Patch Changes](#patch-changes-20) + - [v6.12.1](#v6121) - [Patch Changes](#patch-changes-21) - - [v6.11.2](#v6112) + - [v6.12.0](#v6120) + - [What's Changed](#whats-changed-9) + - [`React.startTransition` support](#reactstarttransition-support) + - [Minor Changes](#minor-changes-12) - [Patch Changes](#patch-changes-22) - - [v6.11.1](#v6111) + - [v6.11.2](#v6112) - [Patch Changes](#patch-changes-23) - - [v6.11.0](#v6110) - - [Minor Changes](#minor-changes-12) + - [v6.11.1](#v6111) - [Patch Changes](#patch-changes-24) - - [v6.10.0](#v6100) - - [What's Changed](#whats-changed-9) + - [v6.11.0](#v6110) - [Minor Changes](#minor-changes-13) - [Patch Changes](#patch-changes-25) - - [v6.9.0](#v690) + - [v6.10.0](#v6100) - [What's Changed](#whats-changed-10) - - [`Component`/`ErrorBoundary` route properties](#componenterrorboundary-route-properties) - - [Introducing Lazy Route Modules](#introducing-lazy-route-modules) - [Minor Changes](#minor-changes-14) + - [future.v7\_normalizeFormMethod](#futurev7_normalizeformmethod) - [Patch Changes](#patch-changes-26) - - [v6.8.2](#v682) + - [v6.9.0](#v690) + - [What's Changed](#whats-changed-11) + - [`Component`/`ErrorBoundary` route properties](#componenterrorboundary-route-properties) + - [Introducing Lazy Route Modules](#introducing-lazy-route-modules) + - [Minor Changes](#minor-changes-15) - [Patch Changes](#patch-changes-27) - - [v6.8.1](#v681) + - [v6.8.2](#v682) - [Patch Changes](#patch-changes-28) - - [v6.8.0](#v680) - - [Minor Changes](#minor-changes-15) + - [v6.8.1](#v681) - [Patch Changes](#patch-changes-29) - - [v6.7.0](#v670) + - [v6.8.0](#v680) - [Minor Changes](#minor-changes-16) - [Patch Changes](#patch-changes-30) - - [v6.6.2](#v662) + - [v6.7.0](#v670) + - [Minor Changes](#minor-changes-17) - [Patch Changes](#patch-changes-31) - - [v6.6.1](#v661) + - [v6.6.2](#v662) - [Patch Changes](#patch-changes-32) - - [v6.6.0](#v660) - - [What's Changed](#whats-changed-11) - - [Minor Changes](#minor-changes-17) + - [v6.6.1](#v661) - [Patch Changes](#patch-changes-33) - - [v6.5.0](#v650) + - [v6.6.0](#v660) - [What's Changed](#whats-changed-12) - [Minor Changes](#minor-changes-18) - [Patch Changes](#patch-changes-34) - - [v6.4.5](#v645) + - [v6.5.0](#v650) + - [What's Changed](#whats-changed-13) + - [Minor Changes](#minor-changes-19) - [Patch Changes](#patch-changes-35) - - [v6.4.4](#v644) + - [v6.4.5](#v645) - [Patch Changes](#patch-changes-36) - - [v6.4.3](#v643) + - [v6.4.4](#v644) - [Patch Changes](#patch-changes-37) - - [v6.4.2](#v642) + - [v6.4.3](#v643) - [Patch Changes](#patch-changes-38) - - [v6.4.1](#v641) + - [v6.4.2](#v642) - [Patch Changes](#patch-changes-39) + - [v6.4.1](#v641) + - [Patch Changes](#patch-changes-40) - [v6.4.0](#v640) - - [What's Changed](#whats-changed-13) + - [What's Changed](#whats-changed-14) - [Remix Data APIs](#remix-data-apis) - - [Patch Changes](#patch-changes-40) + - [Patch Changes](#patch-changes-41) - [v6.3.0](#v630) - - [Minor Changes](#minor-changes-19) + - [Minor Changes](#minor-changes-20) - [v6.2.2](#v622) - - [Patch Changes](#patch-changes-41) - - [v6.2.1](#v621) - [Patch Changes](#patch-changes-42) - - [v6.2.0](#v620) - - [Minor Changes](#minor-changes-20) + - [v6.2.1](#v621) - [Patch Changes](#patch-changes-43) - - [v6.1.1](#v611) - - [Patch Changes](#patch-changes-44) - - [v6.1.0](#v610) + - [v6.2.0](#v620) - [Minor Changes](#minor-changes-21) + - [Patch Changes](#patch-changes-44) + - [v6.1.1](#v611) - [Patch Changes](#patch-changes-45) - - [v6.0.2](#v602) + - [v6.1.0](#v610) + - [Minor Changes](#minor-changes-22) - [Patch Changes](#patch-changes-46) - - [v6.0.1](#v601) + - [v6.0.2](#v602) - [Patch Changes](#patch-changes-47) + - [v6.0.1](#v601) + - [Patch Changes](#patch-changes-48) - [v6.0.0](#v600) @@ -184,6 +191,53 @@ Date: YYYY-MM-DD **Full Changelog**: [`v6.X.Y...v6.X.Y`](https://github.com/remix-run/react-router/compare/react-router@6.X.Y...react-router@6.X.Y) --> +## v6.24.0 + +Date: 2024-06-24 + +### What's Changed + +#### Lazy Route Discovery (a.k.a. "Fog of War") + +We're really excited to release our new API for "Lazy Route Discovery" in `v6.24.0`! For some background information, please check out the original [RFC](https://github.com/remix-run/react-router/discussions/11113). The **tl;dr;** is that ever since we introduced the Data APIs in v6.4 via ``, we've been a little bummed that one of the tradeoffs was the lack of a compelling code-splitting story mirroring what we had in the ``/`` apps. We took a baby-step towards improving that story with `route.lazy` in `v6.9.0`, but with `v6.24.0` we've gone the rest of the way. + +With "Fog of War", you can now load portions of the route tree lazily via the new `unstable_patchRoutesOnMiss` option passed to `createBrowserRouter` (and it's memory/hash counterparts). This gives you a way to hook into spots where React Router is unable to match a given path and patch new routes into the route tree during the navigation (or fetcher call). + +Here's a very small example, but please refer to the [documentation](https://reactrouter.com/en/main/routers/create-browser-router#unstable_patchroutesonmiss) for more information and use cases: + +```js +const router = createBrowserRouter( + [ + { + id: "root", + path: "/", + Component: RootComponent, + }, + ], + { + async unstable_patchRoutesOnMiss({ path, patch }) { + if (path === "/a") { + // Load the `a` route (`{ path: 'a', Component: A }`) + let route = await getARoute(); + // Patch the `a` route in as a new child of the `root` route + patch("root", [route]); + } + }, + } +); +``` + +### Minor Changes + +- Add support for Lazy Route Discovery (a.k.a. "Fog of War") ([#11626](https://github.com/remix-run/react-router/pull/11626)) + +### Patch Changes + +- Fix `fetcher.submit` types - remove incorrect `navigate`/`fetcherKey`/`unstable_viewTransition` options because they are only relevant for `useSubmit` ([#11631](https://github.com/remix-run/react-router/pull/11631)) +- Allow falsy `location.state` values passed to `` ([#11495](https://github.com/remix-run/react-router/pull/11495)) + +**Full Changelog**: [`v6.23.1...v6.24.0`](https://github.com/remix-run/react-router/compare/react-router@6.23.1...react-router@6.24.0) + ## v6.23.1 Date: 2024-05-10 @@ -491,7 +545,7 @@ Date: 2023-10-16 #### View Transitions ๐Ÿš€ -We're excited to release experimental support for the the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition) in React Router! You can now trigger navigational DOM updates to be wrapped in `document.startViewTransition` to enable CSS animated transitions on SPA navigations in your application. +We're excited to release experimental support for the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition) in React Router! You can now trigger navigational DOM updates to be wrapped in `document.startViewTransition` to enable CSS animated transitions on SPA navigations in your application. The simplest approach to enabling a View Transition in your React Router app is via the new [``](https://reactrouter.com/components/link#unstable_viewtransition) prop. This will cause the navigation DOM update to be wrapped in `document.startViewTransition` which will enable transitions for the DOM update. Without any additional CSS styles, you'll get a basic cross-fade animation for your page. @@ -711,6 +765,8 @@ Date: 2023-06-14 `6.13.0` is really a patch release in spirit but comes with a SemVer minor bump since we added a new future flag. +#### v7_startTransition + The **tl;dr;** is that `6.13.0` is the same as [`6.12.0`](https://github.com/remix-run/react-router/releases/tag/react-router%406.12.0) bue we've moved the usage of `React.startTransition` behind an opt-in `future.v7_startTransition` [future flag](https://reactrouter.com/en/main/guides/api-development-strategy) because we found that there are applications in the wild that are currently using `Suspense` in ways that are incompatible with `React.startTransition`. Therefore, in `6.13.0` the default behavior will no longer leverage `React.startTransition`: @@ -862,14 +918,16 @@ You can also check out the docs [here](https://reactrouter.com/en/dev/guides/api ### Minor Changes -- The first future flag being introduced is `future.v7_normalizeFormMethod` which will normalize the exposed `useNavigation()/useFetcher()` `formMethod` fields as uppercase HTTP methods to align with the `fetch()` (and some Remix) behavior. ([#10207](https://github.com/remix-run/react-router/pull/10207)) +#### future.v7_normalizeFormMethod + +The first future flag being introduced is `future.v7_normalizeFormMethod` which will normalize the exposed `useNavigation()/useFetcher()` `formMethod` fields as uppercase HTTP methods to align with the `fetch()` (and some Remix) behavior. ([#10207](https://github.com/remix-run/react-router/pull/10207)) - - When `future.v7_normalizeFormMethod` is unspecified or set to `false` (default v6 behavior), - - `useNavigation().formMethod` is lowercase - - `useFetcher().formMethod` is lowercase - - When `future.v7_normalizeFormMethod === true`: - - `useNavigation().formMethod` is UPPERCASE - - `useFetcher().formMethod` is UPPERCASE +- When `future.v7_normalizeFormMethod` is unspecified or set to `false` (default v6 behavior), + - `useNavigation().formMethod` is lowercase + - `useFetcher().formMethod` is lowercase +- When `future.v7_normalizeFormMethod === true`: + - `useNavigation().formMethod` is UPPERCASE + - `useFetcher().formMethod` is UPPERCASE ### Patch Changes @@ -1046,7 +1104,7 @@ Support absolute URLs in ``. If the URL is for the current origin, it w - Fixes 2 separate issues for revalidating fetcher `shouldRevalidate` calls ([#9948](https://github.com/remix-run/react-router/pull/9948)) - The `shouldRevalidate` function was only being called for _explicit_ revalidation scenarios (after a mutation, manual `useRevalidator` call, or an `X-Remix-Revalidate` header used for cookie setting in Remix). It was not properly being called on _implicit_ revalidation scenarios that also apply to navigation `loader` revalidation, such as a change in search params or clicking a link for the page we're already on. It's now correctly called in those additional scenarios. - - The parameters being passed were incorrect and inconsistent with one another since the `current*`/`next*` parameters reflected the static `fetcher.load` URL (and thus were identical). Instead, they should have reflected the the navigation that triggered the revalidation (as the `form*` parameters did). These parameters now correctly reflect the triggering navigation. + - The parameters being passed were incorrect and inconsistent with one another since the `current*`/`next*` parameters reflected the static `fetcher.load` URL (and thus were identical). Instead, they should have reflected the navigation that triggered the revalidation (as the `form*` parameters did). These parameters now correctly reflect the triggering navigation. - Fix bug with search params removal via `useSearchParams` ([#9969](https://github.com/remix-run/react-router/pull/9969)) - Respect `preventScrollReset` on `` ([#9963](https://github.com/remix-run/react-router/pull/9963)) - Fix navigation for hash routers on manual URL changes ([#9980](https://github.com/remix-run/react-router/pull/9980)) diff --git a/contributors.yml b/contributors.yml index 9cd652050e..ebe165a6c7 100644 --- a/contributors.yml +++ b/contributors.yml @@ -22,6 +22,7 @@ - appden - arjunyel - arka1002 +- Armanio - arnassavickas - aroyan - ashusnapx @@ -53,6 +54,7 @@ - christowiz - codeape2 - coryhouse +- ctnelson1997 - cvbuelow - damianstasik - danielberndt @@ -81,6 +83,9 @@ - frontsideair - fyzhu - fz6m +- gaspard +- Geist5000 +- gesposito - gianlucca - gijo-varghese - goldins @@ -193,6 +198,8 @@ - noisypigeon - Nurai1 - Obi-Dann +- OlegDev1 +- omahs - omar-moquete - p13i - parched @@ -207,6 +214,7 @@ - rimian - robbtraister - RobHannay +- robinvdvleuten - rtmann - rubeonline - ryanflorence @@ -244,6 +252,7 @@ - tlinhart - tom-sherman - tomasr8 +- tony-sn - TooTallNate - triangularcube - trungpv1601 diff --git a/docs/components/form.md b/docs/components/form.md index d5626d753a..b095b62634 100644 --- a/docs/components/form.md +++ b/docs/components/form.md @@ -92,7 +92,7 @@ function ProjectsPage() { ; ``` -If the the current URL is `"/projects/123"`, the form inside the child +If the current URL is `"/projects/123"`, the form inside the child route, `ProjectsPage`, will have a default action as you might expect: `"/projects/123"`. In this case, where the route is the deepest matching route, both `
` and plain HTML forms have the same result. But the form inside of `ProjectsLayout` will point to `"/projects"`, not the full URL. In other words, it points to the matching segment of the URL for the route in which the form is rendered. diff --git a/docs/guides/api-development-strategy.md b/docs/guides/api-development-strategy.md index 13c25cfd85..6070ae2a9b 100644 --- a/docs/guides/api-development-strategy.md +++ b/docs/guides/api-development-strategy.md @@ -5,34 +5,33 @@ new: true # API Development Strategy -Let's cut to the chase - major version upgrades can be a _pain_. Especially for something as foundational to your application as the framework or router it's built on. For Remix and React Router, we want to do our best to give you the smoothest upgrade experience possible. +React Router is foundational to your application. We want to make sure that upgrading to new major versions is as smooth as possible while still allowing us to adjust and enhance the behavior and API as the React ecosystem advances. -This strategy is discussed in more detail in our [Future Flags][future-flags-blog-post] blog post, so give that a read if you want any more info at the end of this doc! +Our strategy and motivations are discussed in more detail in our [Future Flags][future-flags-blog-post] blog post. -## Goals +## Future Flags -Our goals for major Remix and React Router releases are: +When an API changes in a breaking way, it is introduced in a future flag. This allows you to opt-in to one change a time before it becomes the default in the next major version. -- Developers can opt-into SemVer-major features individually _as they are released_ instead of having to wait to adopt them all at once when a new major version hits NPM -- Having opted into features ahead-of-time, developers can upgrade to new major versions in a single short-lived branch/commit (hours, not weeks) +- Without enabling the future flag, nothing changes about your app +- Enabling the flag changes the behavior for that feature -## Implementation +All current future flags are documented in the [Future Flags Guide](../upgrading/future) to help you stay up-to-date. -We plan to do this via what we're calling **Future Flags** that you'll provide when you initialize your [Data Router][picking-a-router]. Think of these as **feature flags for future features**. As we implement new features, we always try to do them in a backwards-compatible way. But when a breaking change is warranted, we don't table that feature up for an _eventual_ v7 release. Instead, we add a **Future Flag** and implement the new feature alongside the current behavior in a v6 minor release. This allows users to start using the feature, providing feedback, and reporting bugs _immediately_. +## Unstable Flags -That way, not only can you adopt features incrementally (and eagerly without a major version bump), we can also work out any kinks incrementally _before_ releasing v7. Eventually we also then add deprecation warnings to the v6 releases to nudge users to the new behavior. Then in v7 we remove the old v6 approach, remove the deprecations, and remove the flag - thus making the flagged behavior the new default in v7. If at the time v6 is released, an application has opted into _all_ future flags and updated their code - then they should just be able to update their dependency to v7, delete the future flags, and be running on v7 in a matter of minutes. +Unstable flags are for features still being designed and developed and made available to our users to help us get it right. -## Unstable vs. V7 Flags +Unstable flags are not recommended for production: -Future flags come in 2 forms: +- they will change without warning and without upgrade paths +- they will have bugs +- they aren't documented +- they may be scrapped completely -**`future.unstable_feature`** +When you opt-in to an unstable flag you are becoming a contributor to the project, rather than a user. We appreciate your help, but please be aware of the new role! -`unstable_` flags allow us to iterate on the API with early adopters as if we're in `v0.x.x` versions, but for a specific feature. This avoids churning the API for all users and arriving at better APIs in the final release. This _does not mean_ that we think the feature is bug-ridden! We _absolutely_ want early adopters to start using these features so we can iterate on (and/or gain confidence in) the API. - -**`future.v7_feature`** - -`v7_` indicates a breaking change from v6 behavior and implies (1) that the API is considered stable and will not under any more breaking changes and (2) that the API will become the default behavior in v7. A `v7_` flag _does not_ mean the feature is bug-free - no software is! Our recommendation is to upgrade to v7 flags as you have the time, as it will make your v7 upgrade _much_ smoother. +To learn about current unstable flags, keep an eye on the [CHANGELOG](../start/changelog). ### Example New Feature Flow @@ -40,80 +39,6 @@ The decision flow for a new feature looks something like this (note this diagram ![Flowchart of the decision process for how to introduce a new feature][feature-flowchart] -The lifecycle is thus either: - -- Non-Breaking + Stable API Feature -> Lands in v6 -- Non-Breaking + Unstable API -> `future.unstable_` flag -> Lands in v6 -- Breaking + Stable API Feature -> `future.v7_` flag -> Lands in v7 -- Breaking + Unstable API -> `future.unstable_` flag -> `future.v7_` flag -> Lands in v7 - -## Current Future Flags - -Here's the current future flags in React Router v6 today. - -### `@remix-run/router` Future Flags - -These flags are only applicable when using a [Data Router][picking-a-router] and are passed when creating the `router` instance: - -```js -const router = createBrowserRouter(routes, { - future: { - v7_normalizeFormMethod: true, - }, -}); -``` - -| Flag | Description | -| ------------------------------------------- | --------------------------------------------------------------------- | -| `v7_fetcherPersist` | Delay active fetcher cleanup until they return to an `idle` state | -| `v7_normalizeFormMethod` | Normalize `useNavigation().formMethod` to be an uppercase HTTP Method | -| [`v7_partialHydration`][partialhydration] | Support partial hydration for Server-rendered apps | -| `v7_prependBasename` | Prepend the router basename to navigate/fetch paths | -| [`v7_relativeSplatPath`][relativesplatpath] | Fix buggy relative path resolution in splat routes | - -#### `createStaticHandler` Future Flags - -These flags are only applicable when [SSR][ssr]-ing a React Router app: - -```js -const handler = createStaticHandler(routes, { - future: { - v7_throwAbortReason: true, - }, -}); -``` - -| Flag | Description | -| ------------------------------------------- | ----------------------------------------------------------------------- | -| [`v7_relativeSplatPath`][relativesplatpath] | Fix buggy relative path resolution in splat routes | -| [`v7_throwAbortReason`][abortreason] | Throw `request.signal.reason` if a `query`/`queryRoute` call is aborted | - -### React Router Future Flags - -These flags apply to both Data and non-Data Routers and are passed to the rendered React component: - -```jsx - - {/*...*/} - -``` - -```jsx - -``` - -| Flag | Description | -| -------------------- | --------------------------------------------------------------------------- | -| `v7_startTransition` | Wrap all router state updates in [`React.startTransition`][starttransition] | - [future-flags-blog-post]: https://remix.run/blog/future-flags [feature-flowchart]: https://remix.run/docs-images/feature-flowchart.png [picking-a-router]: ../routers/picking-a-router -[starttransition]: https://react.dev/reference/react/startTransition -[partialhydration]: ../routers/create-browser-router#partial-hydration-data -[relativesplatpath]: ../hooks/use-resolved-path#splat-paths -[ssr]: ../guides/ssr -[abortreason]: ../routers/create-static-handler#handlerqueryrequest-opts diff --git a/docs/hooks/use-navigate.md b/docs/hooks/use-navigate.md index 3a80fc692d..978d3bf173 100644 --- a/docs/hooks/use-navigate.md +++ b/docs/hooks/use-navigate.md @@ -93,7 +93,7 @@ function EditContact() { } ``` -Please note that `relative: "path"` only impacts the resolution of a relative path. It does not change the the "starting" location for that relative path resolution. This resolution is always relative to the current location in the Route hierarchy (i.e., the route `useNavigate` is called in). +Please note that `relative: "path"` only impacts the resolution of a relative path. It does not change the "starting" location for that relative path resolution. This resolution is always relative to the current location in the Route hierarchy (i.e., the route `useNavigate` is called in). If you wish to use path-relative routing against the current URL instead of the route hierarchy, you can do that with the current [`location`][use-location] and the `URL` constructor (note the trailing slash behavior): diff --git a/docs/routers/create-browser-router.md b/docs/routers/create-browser-router.md index b5ebeb34a5..4935a044ca 100644 --- a/docs/routers/create-browser-router.md +++ b/docs/routers/create-browser-router.md @@ -262,10 +262,12 @@ let router = createBrowserRouter(routes, { console.log(`Processing route ${match.route.id}`); // Don't override anything - just resolve route.lazy + call loader let result = await match.resolve(); - console.log(`Done processing route ${match.route.id}`); - return result. + console.log( + `Done processing route ${match.route.id}` + ); + return result; }) - ) + ); }, }); ``` @@ -275,11 +277,13 @@ let router = createBrowserRouter(routes, { Let's define a middleware on each route via `handle` and call middleware sequentially first, then call all loaders in parallel - providing any data made available via the middleware: ```ts -const routes [ +const routes = [ { id: "parent", path: "/parent", - loader({ request }, context) { /*...*/ }, + loader({ request }, context) { + /*...*/ + }, handle: { async middleware({ request }, context) { context.parent = "PARENT MIDDLEWARE"; @@ -289,7 +293,9 @@ const routes [ { id: "child", path: "child", - loader({ request }, context) { /*...*/ }, + loader({ request }, context) { + /*...*/ + }, handle: { async middleware({ request }, context) { context.child = "CHILD MIDDLEWARE"; @@ -301,12 +307,19 @@ const routes [ ]; let router = createBrowserRouter(routes, { - async unstable_dataStrategy({ request, params, matches }) { + async unstable_dataStrategy({ + request, + params, + matches, + }) { // Run middleware sequentially and let them add data to `context` let context = {}; for (const match of matches) { if (match.route.handle?.middleware) { - await match.route.handle.middleware({ request, params }, context); + await match.route.handle.middleware( + { request, params }, + context + ); } } @@ -330,13 +343,17 @@ let router = createBrowserRouter(routes, { It's also possible you don't even want to define a loader implementation at the route level. Maybe you want to just determine the routes and issue a single GraphQL request for all of your data? You can do that by setting your `route.loader=true` so it qualifies as "having a loader", and then store GQL fragments on `route.handle`: ```ts -const routes [ +const routes = [ { id: "parent", path: "/parent", loader: true, handle: { - gql: gql`fragment Parent on Whatever { parentField }` + gql: gql` + fragment Parent on Whatever { + parentField + } + `, }, children: [ { @@ -344,7 +361,11 @@ const routes [ path: "child", loader: true, handle: { - gql: gql`fragment Child on Whatever { childField }` + gql: gql` + fragment Child on Whatever { + childField + } + `, }, }, ], @@ -409,7 +430,8 @@ const router = createBrowserRouter( async unstable_patchRoutesOnMiss({ path, patch }) { if (path === "/a") { // Load/patch the `a` route as a child of the route with id `root` - let route = await getARoute(); // { path: 'a', Component: A } + let route = await getARoute(); + // ^ { path: 'a', Component: A } patch("root", [route]); } }, @@ -435,8 +457,9 @@ const router = createBrowserRouter( { async unstable_patchRoutesOnMiss({ path, patch }) { if (path === "/root-sibling") { - // Load/patch the `/sibling` route at the top - let route = await getRootSiblingRoute(); // { path: '/sibling', Component: Sibling } + // Load/patch the `/root-sibling` route as a sibling of the root route + let route = await getRootSiblingRoute(); + // ^ { path: '/root-sibling', Component: RootSibling } patch(null, [route]); } }, diff --git a/docs/routers/picking-a-router.md b/docs/routers/picking-a-router.md index 089a368a4e..8fe0d9f32c 100644 --- a/docs/routers/picking-a-router.md +++ b/docs/routers/picking-a-router.md @@ -135,6 +135,7 @@ The following APIs are introduced in React Router 6.4 and will only work when us [useactiondata]: ../hooks/use-action-data [useasyncerror]: ../hooks/use-async-error [useasyncvalue]: ../hooks/use-async-value +[useblocker]: ../hooks/use-blocker [usefetcher]: ../hooks/use-fetcher [usefetchers]: ../hooks/use-fetchers [useloaderdata]: ../hooks/use-loader-data diff --git a/docs/start/concepts.md b/docs/start/concepts.md index 2bffc74a04..efad4ca7e6 100644 --- a/docs/start/concepts.md +++ b/docs/start/concepts.md @@ -568,7 +568,7 @@ The `Outlet` component will always render the next match. That means `` a If the URL were `/contact-us`, the element tree would change to: ```jsx - + ``` Because the contact form is not under the main `` route. diff --git a/docs/start/overview.md b/docs/start/overview.md index 6af2e9573a..67f44c0dc7 100644 --- a/docs/start/overview.md +++ b/docs/start/overview.md @@ -383,7 +383,7 @@ See: Instead of waiting for the data for the next page, you can [`defer`][defer] data so the UI flips over to the next screen with placeholder UI immediately while the data loads. -```jsx lines=[12,22-29,32-35,42] +```jsx lines=[12,22-29,32-35] } diff --git a/docs/start/tutorial.md b/docs/start/tutorial.md index a115a7d289..8e8a1b61d9 100644 --- a/docs/start/tutorial.md +++ b/docs/start/tutorial.md @@ -25,7 +25,8 @@ We'll be using [Vite][vite] for our bundler and dev server for this tutorial. Yo npm create vite@latest name-of-your-project -- --template react # follow prompts cd -npm install react-router-dom localforage match-sorter sort-by +npm install react-router-dom # always need this! +npm install localforage match-sorter sort-by # only for this tutorial. npm run dev ``` @@ -270,7 +271,7 @@ export default function Contact() { const contact = { first: "Your", last: "Name", - avatar: "https://placekitten.com/g/200/200", + avatar: "https://robohash.org/you.png?size=200x200", twitter: "your_handle", notes: "Some notes", favorite: true, @@ -281,7 +282,10 @@ export default function Contact() {
@@ -336,8 +340,7 @@ export default function Contact() { } function Favorite({ contact }) { - // yes, this is a `let` for later - let favorite = contact.favorite; + const favorite = contact.favorite; return (