Current Future Flags
On this page

Future Flags

The following future flags are stable and ready to adopt. To read more about future flags see 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

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 for more information.

👉 Enable the flag

Enabling the flag depends on the type of router:

<BrowserRouter
  future={{
    v7_relativeSplatPath: true,
  }}
/>
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:

<Routes>
  <Route path="/" element={<Home />} />
-  <Route path="dashboard/*" element={<Dashboard />} />
+  <Route path="dashboard">
+    <Route index element={<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:

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 for more information.

👉 Enable the flag

<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

If you are not using a createBrowserRouter you can skip this

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 for more information.

Enable the Flag

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

If you are not using a createBrowserRouter you can skip this

This normalizes formMethod fields as uppercase HTTP methods to align with the fetch() behavior. View the CHANGELOG for more information.

👉 Enable the Flag

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

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

v7_partialHydration

If you are not using a createBrowserRouter you can skip this

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 for more information.

👉 Enable the Flag

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

Update your Code

With partial hydration, you need to provide a HydrateFallback component to render during initial hydration. Additionally, if you were using fallbackElement before, you need to remove it as it is now deprecated. In most cases, you will want to reuse the fallbackElement as the HydrateFallback.

👉 Replace fallbackElement with HydrateFallback

const router = createBrowserRouter(
  [
    {
      path: "/",
      Component: Layout,
+      HydrateFallback: Fallback,
      // or
+      hydrateFallbackElement: <Fallback />,
      children: [],
    },
  ],
);


<RouterProvider
  router={router}
-  fallbackElement={<Fallback />}
/>

v7_skipActionErrorRevalidation

If you are not using a createBrowserRouter you can skip this

When this flag is enabled, loaders will no longer revalidate by default after an action throws/returns a Response with a 4xx/5xx status code. You may opt-into revalidation in these scenarios via shouldRevalidate and the actionStatus parameter.

👉 Enable the Flag

createBrowserRouter(routes, {
  future: {
    v7_skipActionErrorRevalidation: true,
  },
});

Update your Code

In most cases, you probably won't have to make changes to your app code. Usually, if an action errors, it's unlikely data was mutated and needs revalidation. If any of your code does mutate data in action error scenarios you have 2 options:

👉 Option 1: Change the action to avoid mutations in error scenarios

// Before
async function action() {
  await mutateSomeData();
  if (detectError()) {
    throw new Response(error, { status: 400 });
  }
  await mutateOtherData();
  // ...
}

// After
async function action() {
  if (detectError()) {
    throw new Response(error, { status: 400 });
  }
  // All data is now mutated after validations
  await mutateSomeData();
  await mutateOtherData();
  // ...
}

👉 Option 2: Opt-into revalidation via shouldRevalidate and actionStatus

async function action() {
  await mutateSomeData();
  if (detectError()) {
    throw new Response(error, { status: 400 });
  }
  await mutateOtherData();
}

async function loader() { ... }

function shouldRevalidate({ actionStatus, defaultShouldRevalidate }) {
  if (actionStatus != null && actionStatus >= 400) {
    // Revalidate this loader when actions return a 4xx/5xx status
    return true;
  }
  return defaultShouldRevalidate;
}
Docs and examples CC 4.0