Skip to content

Commit

Permalink
add future flags guide (#11603)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanflorence committed May 30, 2024
1 parent 1260952 commit c41d6ec
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 91 deletions.
107 changes: 16 additions & 91 deletions docs/guides/api-development-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,115 +5,40 @@ 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.

<docs-info>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!</docs-info>
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

The decision flow for a new feature looks something like this (note this diagram is in relation to Remix v1/v2 but applies to React Router v6/v7 as well):

![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
<BrowserRouter future={{ v7_startTransition: true }}>
<Routes>{/*...*/}</Routes>
</BrowserRouter>
```

```jsx
<RouterProvider
router={router}
future={{ v7_startTransition: true }}
/>
```

| 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
201 changes: 201 additions & 0 deletions docs/upgrading/future.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
---
title: Current Future Flags
order: 1
new: true
---

# Future Flags

The following future flags are stable and ready to adopt. To read more about future flags see [Development Strategy](../guides/api-development-strategy)

## Update to latest v6.x

First update to the latest minor version of v6.x to have the latest future flags.

πŸ‘‰ **Update to latest v6**

```shellscript nonumber
npm install react-router-dom@6
```

## v7_relativeSplatPath

**Background**

Changes the relative path matching and linking for multi-segment splats paths like `dashboard/*` (vs. just `*`). [View the CHANGELOG](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md#minor-changes-2) for more information.

πŸ‘‰ **Enable the flag**

Enabling the flag depends on the type of router:

```tsx
<BrowserRouter
future={{
v7_relativeSplatPath: true,
}}
/>
```

```tsx
createBrowserRouter(routes, {
future: {
v7_relativeSplatPath: true,
},
});
```

**Update your Code**

If you have any routes with a path + a splat like `<Route path="dashboard/*">` and has relative links like `<Link to="relative">` or `<Link to="../relative">` beneath it, you will need to update your code.

πŸ‘‰ **Split the `<Route>` into two**

Split any multi-segment splat `<Route>` into a parent route with the path and a child route with the splat:

```diff
<Routes>
<Route path="/" element={<Home />} />
- <Route path="dashboard/*" element={<Dashboard />} />
+ <Route path="dashboard">
+ <Route path="*" element={<Dashboard />} />
+ </Route>
</Routes>

// or
createBrowserRouter([
{ path: "/", element: <Home /> },
{
- path: "dashboard/*",
- element: <Dashboard />,
+ path: "dashboard",
+ children: [{ path: "*", element: <Dashboard /> }],
},
]);
```

πŸ‘‰ **Update relative links**

Update any `<Link>` elements within that route tree to include the extra `..` relative segment to continue linking to the same place:

```diff
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
<nav>
- <Link to="/">Dashboard Home</Link>
- <Link to="team">Team</Link>
- <Link to="projects">Projects</Link>
+ <Link to="../">Dashboard Home</Link>
+ <Link to="../team">Team</Link>
+ <Link to="../projects">Projects</Link>
</nav>

<Routes>
<Route path="/" element={<DashboardHome />} />
<Route path="team" element={<DashboardTeam />} />
<Route
path="projects"
element={<DashboardProjects />}
/>
</Routes>
</div>
);
}
```

## v7_startTransition

**Background**

This uses `React.useTransition` instead of `React.useState` for Router state updates. View the [CHANGELOG](https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v7_starttransition) for more information.

πŸ‘‰ **Enable the flag**

```tsx
<BrowserRouter
future={{
v7_startTransition: true,
}}
/>

// or
<RouterProvider
future={{
v7_startTransition: true,
}}
/>
```

πŸ‘‰ **Update your Code**

You don't need to update anything unless you are using `React.lazy` _inside_ of a component.

Using `React.lazy` inside of a component is incompatible with `React.useTransition` (or other code that makes promises inside of components). Move `React.lazy` to the module scope and stop making promises inside of components. This is not a limitation of React Router but rather incorrect usage of React.

## v7_fetcherPersist

<docs-warning>If you are not using a `createBrowserRouter` you can skip this</docs-warning>

**Background**

The fetcher lifecycle is now based on when it returns to an idle state rather than when its owner component unmounts: [View the CHANGELOG](https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#persistence-future-flag-futurev7_fetcherpersist) for more information.

**Enable the Flag**

```tsx
createBrowserRouter(routes, {
future: {
v7_fetcherPersist: true,
},
});
```

**Update your Code**

It's unlikely to affect your app. You may want to check any usage of `useFetchers` as they may persist longer than they did before. Depending on what you're doing, you may render something longer than before.

## v7_normalizeFormMethod

<docs-warning>If you are not using a `createBrowserRouter` you can skip this</docs-warning>

This normalizes `formMethod` fields as uppercase HTTP methods to align with the `fetch()` behavior. [View the CHANGELOG](https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#futurev7_normalizeformmethod) for more information.

πŸ‘‰ **Enable the Flag**

```tsx
createBrowserRouter(routes, {
future: {
v7_normalizeFormMethod: true,
},
});
```

**Update your Code**

If any of your code is checking for lowercase HTTP methods, you will need to update it to check for uppercase HTTP methods (or call `toLowerCase()` on it).

πŸ‘‰ **Compare `formMethod` to UPPERCASE**

```diff
-useNavigation().formMethod === "post"
-useFetcher().formMethod === "get";
+useNavigation().formMethod === "POST"
+useFetcher().formMethod === "GET";
```

## v7_partialHydration

<docs-warning>If you are not using a `createBrowserRouter` you can skip this</docs-warning>

This allows SSR frameworks to provide only partial hydration data. It's unlikely you need to worry about this, just turn the flag on. [View the CHANGELOG](https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#partial-hydration) for more information.

πŸ‘‰ **Enable the Flag**

```tsx
createBrowserRouter(routes, {
future: {
v7_partialHydration: true,
},
});
```

0 comments on commit c41d6ec

Please sign in to comment.