react-router-dom
unstable_patchRoutesOnNavigation
(#11973)PatchRoutesOnNavigationFunctionArgs
type for convenience (#11967)unstable_dataStrategy
(#11974)unstable_flushSync
option for navigations and fetchers (#11989)unstable_viewTransition
option for navigations and the corresponding unstable_useViewTransitionState
hook (#11989)?index
param already exists from a prior submission (#12003)useFormAction
bug - when removing ?index
param it would not keep other non-Remix index
params (#12003)Fix types for RouteObject
within PatchRoutesOnNavigationFunction
's patch
method so it doesn't expect agnostic route objects passed to patch
(#11967)
Updated dependencies:
react-router@6.27.0
@remix-run/router@1.20.0
@remix-run/router@1.19.2
react-router@6.26.2
unstable_patchRoutesOnMiss
to unstable_patchRoutesOnNavigation
to match new behavior (#11888)@remix-run/router@1.19.1
react-router@6.26.1
replace(url, init?)
alternative to redirect(url, init?)
that performs a history.replaceState
instead of a history.pushState
on client-side navigation redirects (#11811)future.v7_partialHydration
along with unstable_patchRoutesOnMiss
(#11838)router.state.matches
will now include any partial matches so that we can render ancestor HydrateFallback
components@remix-run/router@1.19.0
react-router@6.26.0
RouterProvider
internals to reduce unnecessary re-renders (#11803)react-router@6.25.1
future.unstable_skipActionErrorRevalidation
as future.v7_skipActionErrorRevalidation
(#11769)Response
with a 4xx
/5xx
status codeshouldRevalidate
shouldRevalidate
's unstable_actionStatus
parameter to actionStatus
react-router@6.25.0
@remix-run/router@1.18.0
polyfill.io
reference from warning message because the domain was sold and has since been determined to serve malware (#11741)NavLinkRenderProps
type for easier typing of custom NavLink
callback (#11553)@remix-run/router@1.17.1
react-router@6.24.1
unstable_patchRoutesOnMiss
docs: https://reactrouter.com/en/main/routers/create-browser-routerfetcher.submit
types - remove incorrect navigate
/fetcherKey
/unstable_viewTransition
options because they are only relevant for useSubmit
(#11631)location.state
values passed to <StaticRouter>
(#11495)react-router@6.24.0
@remix-run/router@1.17.0
document
existence when checking startViewTransition
(#11544)react-router-dom/server
import back to react-router-dom
instead of index.ts
(#11514)@remix-run/router@1.16.1
react-router@6.23.1
unstable_dataStrategy
configuration option (#11098)@remix-run/router@1.16.0
react-router@6.23.0
@remix-run/router@1.15.3
react-router@6.22.3
@remix-run/router@1.15.2
react-router@6.22.2
react-router@6.22.1
@remix-run/router@1.15.1
window__reactRouterVersion
tag for CWV Report detection (#11222)@remix-run/router@1.15.0
react-router@6.22.0
NavLink
isPending
when a basename
is used (#11195)unstable_
prefix from Blocker
/BlockerFunction
types (#11187)react-router@6.21.3
useId
for internal fetcher keys when available (#11166)@remix-run/router@1.14.2
react-router@6.21.2
react-router@6.21.1
@remix-run/router@1.14.1
future.v7_relativeSplatPath
flag to implement a breaking bug fix to relative routing when inside a splat route. (#11087)This fix was originally added in #10983 and was later reverted in #11078 because it was determined that a large number of existing applications were relying on the buggy behavior (see #11052)
The Bug
The buggy behavior is that without this flag, the default behavior when resolving relative paths is to ignore any splat (*
) portion of the current route path.
The Background
This decision was originally made thinking that it would make the concept of nested different sections of your apps in <Routes>
easier if relative routing would replace the current splat:
jsx
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="dashboard/*" element={<Dashboard />} />
</Routes>
</BrowserRouter>
Any paths like /dashboard
, /dashboard/team
, /dashboard/projects
will match the Dashboard
route. The dashboard component itself can then render nested <Routes>
:
```jsx function Dashboard() { return (
<Routes>
<Route path="/" element={<DashboardHome />} />
<Route path="team" element={<DashboardTeam />} />
<Route path="projects" element={<DashboardProjects />} />
</Routes>
</div>
);
} ```
Now, all links and route paths are relative to the router above them. This makes code splitting and compartmentalizing your app really easy. You could render the Dashboard
as its own independent app, or embed it into your large app without making any changes to it.
The Problem
The problem is that this concept of ignoring part of a path breaks a lot of other assumptions in React Router - namely that "."
always means the current location pathname for that route. When we ignore the splat portion, we start getting invalid paths when using "."
:
```jsx // If we are on URL /dashboard/team, and we want to link to /dashboard/team: function DashboardTeam() { // ❌ This is broken and results in return A broken link to the Current URL;
// ✅ This is fixed but super unintuitive since we're already at /dashboard/team!
return <Link to="./team">A broken link to the Current URL</Link>;
} ```
We've also introduced an issue that we can no longer move our DashboardTeam
component around our route hierarchy easily - since it behaves differently if we're underneath a non-splat route, such as /dashboard/:widget
. Now, our "."
links will, properly point to ourself inclusive of the dynamic param value so behavior will break from it's corresponding usage in a /dashboard/*
route.
Even worse, consider a nested splat route configuration:
jsx
<BrowserRouter>
<Routes>
<Route path="dashboard">
<Route path="*" element={<Dashboard />} />
</Route>
</Routes>
</BrowserRouter>
Now, a <Link to=".">
and a <Link to="..">
inside the Dashboard
component go to the same place! That is definitely not correct!
Another common issue arose in Data Routers (and Remix) where any <Form>
should post to it's own route action
if you the user doesn't specify a form action:
jsx
let router = createBrowserRouter({
path: "/dashboard",
children: [
{
path: "*",
action: dashboardAction,
Component() {
// ❌ This form is broken! It throws a 405 error when it submits because
// it tries to submit to /dashboard (without the splat value) and the parent
// `/dashboard` route doesn't have an action
return <Form method="post">...</Form>;
},
},
],
});
This is just a compounded issue from the above because the default location for a Form
to submit to is itself ("."
) - and if we ignore the splat portion, that now resolves to the parent route.
The Solution
If you are leveraging this behavior, it's recommended to enable the future flag, move your splat to it's own route, and leverage ../
for any links to "sibling" pages:
```jsx
function Dashboard() { return (
<Routes>
<Route path="/" element={<DashboardHome />} />
<Route path="team" element={<DashboardTeam />} />
<Route path="projects" element={<DashboardProjects />} />
</Router>
</div>
);
} ```
This way, .
means "the full current pathname for my route" in all cases (including static, dynamic, and splat routes) and ..
always means "my parents pathname".
@remix-run/router@1.14.0
react-router@6.21.0
useResolvedPath
fix for splat routes due to a large number of applications that were relying on the buggy behavior (see https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329). We plan to re-introduce this fix behind a future flag in the next minor version. (#11078)react-router@6.20.1
@remix-run/router@1.13.1
PathParam
type from the public API (#10719)react-router@6.20.0
@remix-run/router@1.13.0
unstable_flushSync
option to useNavigate
/useSumbit
/fetcher.load
/fetcher.submit
to opt-out of React.startTransition
and into ReactDOM.flushSync
for state updates (#11005)unstable_usePrompt
to accept a BlockerFunction
in addition to a boolean
(#10991)key
in a useFetcher
that remains mounted wasn't getting picked up (#11009)useFormAction
which was incorrectly inheriting the ?index
query param from child route action
submissions (#11025)NavLink
active
logic when to
location has a trailing slash (#10734)react-router@6.19.0
@remix-run/router@1.12.0
Add support for manual fetcher key specification via useFetcher({ key: string })
so you can access the same fetcher instance from different components in your application without prop-drilling (RFC) (#10960)
Fetcher keys are now also exposed on the fetchers returned from useFetchers
so that they can be looked up by key
Add navigate
/fetcherKey
params/props to useSumbit
/Form
to support kicking off a fetcher submission under the hood with an optionally user-specified key
(#10960)
Invoking a fetcher in this way is ephemeral and stateless
useFetcher({ key })
to look it up elsewhereRouterProvider
that holds completed fetcher data, in preparation for the upcoming future flag that will change the fetcher persistence/cleanup behavior (#10961)future
prop on BrowserRouter
, HashRouter
and MemoryRouter
so that it accepts a Partial<FutureConfig>
instead of requiring all flags to be included. (#10962)@remix-run/router@1.11.0
react-router@6.18.0
document.startViewTransition
to enable CSS animated transitions on SPA navigations in your application. (#10916)The simplest approach to enabling a View Transition in your React Router app is via the new <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.
If you need to apply more fine-grained styles for your animations, you can leverage the unstable_useViewTransitionState
hook which will tell you when a transition is in progress and you can use that to apply classes or styles:
jsx
function ImageLink(to, src, alt) {
let isTransitioning = unstable_useViewTransitionState(to);
return (
<Link to={to} unstable_viewTransition>
<img
src={src}
alt={alt}
style={{
viewTransitionName: isTransitioning ? "image-expand" : "",
}}
/>
</Link>
);
}
You can also use the <NavLink unstable_viewTransition>
shorthand which will manage the hook usage for you and automatically add a transitioning
class to the <a>
during the transition:
css
a.transitioning img {
view-transition-name: "image-expand";
}
jsx
<NavLink to={to} unstable_viewTransition>
<img src={src} alt={alt} />
</NavLink>
For an example usage of View Transitions with React Router, check out our fork of the Astro Records demo.
For more information on using the View Transitions API, please refer to the Smooth and simple transitions with the View Transitions API guide from the Google Chrome team.
Please note, that because the ViewTransition
API is a DOM API, we now export a specific RouterProvider
from react-router-dom
with this functionality. If you are importing RouterProvider
from react-router
, then it will not support view transitions. (#10928
ScrollRestoration
when sessionStorage
is unavailable (#10848)@remix-run/router@1.10.0
react-router@6.17.0
@remix-run/router@1.9.0
react-router@6.16.0
redirectDocument()
function which allows users to specify that a redirect from a loader
/action
should trigger a document reload (via window.location
) instead of attempting to navigate to the redirected location via React Router (#10705)URLSearchParams
and the useSearchParams
hook. (#10620)useFormAction()
for unspecified actions since it cannot be determined on the server and causes hydration issues (#10758)unstable_usePrompt
to avoid throwing an exception if the prompt is unblocked and a navigation is performed synchronously (#10687, #10718)@remix-run/router@1.8.0
react-router@6.15.0
<ScrollRestoration>
(#10682)<Form state>
prop to populate history.state
on submission navigations (#10630)Error
subclasses such as ReferenceError
/TypeError
(#10633)@remix-run/router@1.7.2
react-router@6.14.2
react-router@6.14.1
@remix-run/router@1.7.1
application/json
and text/plain
encodings for useSubmit
/fetcher.submit
. To reflect these additional types, useNavigation
/useFetcher
now also contain navigation.json
/navigation.text
and fetcher.json
/fetcher.text
which include the json/text submission if applicable (#10413)```jsx // The default behavior will still serialize as FormData function Component() { let navigation = useNavigation(); let submit = useSubmit(); submit({ key: "value" }, { method: "post" }); // navigation.formEncType => "application/x-www-form-urlencoded" // navigation.formData => FormData instance }
async function action({ request }) { // request.headers.get("Content-Type") => "application/x-www-form-urlencoded" // await request.formData() => FormData instance } ```
``js
// Opt-into JSON encoding with
encType: "application/json"`
function Component() {
let navigation = useNavigation();
let submit = useSubmit();
submit({ key: "value" }, { method: "post", encType: "application/json" });
// navigation.formEncType => "application/json"
// navigation.json => { key: "value" }
}
async function action({ request }) { // request.headers.get("Content-Type") => "application/json" // await request.json() => { key: "value" } } ```
``js
// Opt-into text encoding with
encType: "text/plain"`
function Component() {
let navigation = useNavigation();
let submit = useSubmit();
submit("Text submission", { method: "post", encType: "text/plain" });
// navigation.formEncType => "text/plain"
// navigation.text => "Text submission"
}
async function action({ request }) { // request.headers.get("Content-Type") => "text/plain" // await request.text() => "Text submission" } ```
submitter
element, prefer the built-in new FormData(form, submitter)
instead of the previous manual approach in modern browsers (those that support the new submitter
parameter) (#9865, #10627)type="image"
buttonsformdata-submitter-polyfill
window.history.pushState/replaceState
before updating React Router state (instead of after) so that window.location
matches useLocation
during synchronous React 17 rendering (#10448)window.location
and should always reference useLocation
when possible, as window.location
will not be in sync 100% of the time (due to popstate
events, concurrent mode, etc.)tsc --skipLibCheck:false
issues on React 17 (#10622)typescript
to 5.1 (#10581)react-router@6.14.0
@remix-run/router@1.7.0
React.startTransition
usage behind a future flag to avoid issues with existing incompatible Suspense
usages. We recommend folks adopting this flag to be better compatible with React concurrent mode, but if you run into issues you can continue without the use of startTransition
until v7. Issues usually boils down to creating net-new promises during the render cycle, so if you run into issues you should either lift your promise creation out of the render cycle or put it behind a useMemo
. (#10596)Existing behavior will no longer include React.startTransition
:
```jsx
If you wish to enable React.startTransition
, pass the future flag to your component:
```jsx
React.startTransition
minification bug in production mode (#10588)react-router@6.13.0
[!WARNING] Please use version
6.13.0
or later instead of6.12.1
. This version suffers from awebpack
/terser
minification issue resulting in invalid minified code in your resulting production bundles which can cause issues in your application. See #10579 for more details.
React.startTransition
to fix webpack + react 17 compilation error (#10569)react-router@6.12.1
React.startTransition
if it exists (#10438)DOMException
(DataCloneError
) when attempting to perform a PUSH
navigation with non-serializable state. (#10427)@remix-run/router@1.6.3
react-router@6.12.0
SetURLSearchParams
type (#10444)react-router@6.11.2
@remix-run/router@1.6.2
react-router@6.11.1
@remix-run/router@1.6.1
basename
support in useFetcher
(#10336)basename
then you will need to remove the manually prepended basename
from your fetcher
calls (fetcher.load('/basename/route') -> fetcher.load('/route')
)Component
instead of element
on a route definition (#10287)<Link to="//">
and other invalid URL values (#10367)useSyncExternalStore
to useState
for internal @remix-run/router
router state syncing in <RouterProvider>
. We found some subtle bugs where router state updates got propagated before other normal useState
updates, which could lead to footguns in useEffect
calls. (#10377, #10409)StaticRouterProvider
's internal Router
component (#10401)RouterProvider
, useNavigate
/useSubmit
/fetcher.submit
are now stable across location changes, since we can handle relative routing via the @remix-run/router
instance and get rid of our dependence on useLocation()
. When using BrowserRouter
, these hooks remain unstable across location changes because they still rely on useLocation()
. (#10336)react-router@6.11.0
@remix-run/router@1.6.0
Added support for Future Flags in React Router. The first 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()
behavior. (#10207)
When future.v7_normalizeFormMethod === false
(default v6 behavior),
useNavigation().formMethod
is lowercaseuseFetcher().formMethod
is lowercasefuture.v7_normalizeFormMethod === true
:useNavigation().formMethod
is uppercaseuseFetcher().formMethod
is uppercasecreateStaticHandler
to also check for ErrorBoundary
on routes in addition to errorElement
(#10190)@remix-run/router@1.5.0
react-router@6.10.0
element
and errorElement
fields as React Components instead of React Elements. You can instead pass a React Component to the new Component
and ErrorBoundary
fields if you choose. There is no functional difference between the two, so use whichever approach you prefer 😀. You shouldn't be defining both, but if you do Component
/ErrorBoundary
will "win". (#10045)Example JSON Syntax
```jsx
// Both of these work the same:
const elementRoutes = [{
path: '/',
element:
const componentRoutes = [{ path: '/', Component: Home, ErrorBoundary: HomeError, }]
function Home() { ... } function HomeError() { ... } ```
Example JSX Syntax
```jsx
// Both of these work the same:
const elementRoutes = createRoutesFromElements(
const componentRoutes = createRoutesFromElements(
function Home() { ... } function HomeError() { ... } ```
In order to keep your application bundles small and support code-splitting of your routes, we've introduced a new lazy()
route property. This is an async function that resolves the non-route-matching portions of your route definition (loader
, action
, element
/Component
, errorElement
/ErrorBoundary
, shouldRevalidate
, handle
).
Lazy routes are resolved on initial load and during the loading
or submitting
phase of a navigation or fetcher call. You cannot lazily define route-matching properties (path
, index
, children
) since we only execute your lazy route functions after we've matched known routes.
Your lazy
functions will typically return the result of a dynamic import.
jsx
// In this example, we assume most folks land on the homepage so we include that
// in our critical-path bundle, but then we lazily load modules for /a and /b so
// they don't load until the user navigates to those routes
let routes = createRoutesFromElements(
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="a" lazy={() => import("./a")} />
<Route path="b" lazy={() => import("./b")} />
</Route>
);
Then in your lazy route modules, export the properties you want defined for the route:
```jsx export async function loader({ request }) { let data = await fetchData(request); return json(data); }
// Export a Component
directly instead of needing to create a React Element from it
export function Component() {
let data = useLoaderData();
return (
<>
<h1>You made it!</h1>
<p>{data}</p>
);
}
// Export an ErrorBoundary
directly instead of needing to create a React Element from it
export function ErrorBoundary() {
let error = useRouteError();
return isRouteErrorResponse(error) ? (
An example of this in action can be found in the examples/lazy-loading-router-provider
directory of the repository.
🙌 Huge thanks to @rossipedia for the Initial Proposal and POC Implementation.
react-router@6.9.0
@remix-run/router@1.4.0
<Link to>
as external if they are outside of the router basename
(#10135)useBlocker
to return IDLE_BLOCKER
during SSR (#10046)<Link to>
urls (#10112)StaticRouterProvider
serialized hydration data (#10068)@remix-run/router@1.3.3
react-router@6.8.2
Link
component (now also supports mailto:
urls) (#9994)react-router@6.8.1
@remix-run/router@1.3.2
<Link to>
. If the URL is for the current origin, it will still do a client-side navigation. If the URL is for a different origin then it will do a fresh document request for the new origin. (#9900)tsx
<Link to="https://neworigin.com/some/path"> {/* Document request */}
<Link to="//neworigin.com/some/path"> {/* Document request */}
<Link to="https://www.currentorigin.com/path"> {/* Client-side navigation */}
useSearchParams
(#9969)preventScrollReset
on <fetcher.Form>
(#9963)pagehide
instead of beforeunload
for <ScrollRestoration>
. This has better cross-browser support, specifically on Mobile Safari. (#9945)@remix-run/router@1.3.1
react-router@6.8.0
unstable_useBlocker
hook for blocking navigations within the app's location origin (#9709)unstable_usePrompt
hook for blocking navigations within the app's location origin (#9932)preventScrollReset
prop to <Form>
(#9886)useBeforeUnload
(#9709)@remix-run/router@1.3.0
react-router@6.7.0
useId
consistency during SSR (#9805)react-router@6.6.2
@remix-run/router@1.2.1
react-router@6.6.1
useBeforeUnload()
hook (#9664)unstable_
prefix from createStaticHandler
/createStaticRouter
/StaticRouterProvider
(#9738)Error
objects from StaticRouterProvider
(#9664)<Form method>
and useSubmit
method values (#9664)hydrationData
(#9664)<button formmethod>
form submission overriddes (#9664)@remix-run/router@1.2.0
react-router@6.6.0
react-router@6.5.0
@remix-run/router@1.1.0
@remix-run/router@1.0.5
react-router@6.4.5
NavLink
and descendant <Routes>
(#9589, #9647)ErrorResponse
instances when using built-in hydration (#9593)basename
in static data routers (#9591)@remix-run/router@1.0.4
react-router@6.4.4
createHashRouter
(#9409)index
routes with a path
in useResolvedPath
(#9486)relative=path
prop on NavLink
(#9453)NavLink
behavior for root urls (#9497)@remix-run/router@1.0.3
react-router@6.4.3
basename
in useFormAction
(#9352)RouteObject
/RouteProps
types to surface the error in TypeScript. (#9366)react-router@6.4.2
@remix-run/router@1.0.2
react-router@6.4.1
@remix-run/router@1.0.1
Whoa this is a big one! 6.4.0
brings all the data loading and mutation APIs over from Remix. Here's a quick high level overview, but it's recommended you go check out the docs, especially the feature overview and the tutorial.
New APIs
createMemoryRouter
/createBrowserRouter
/createHashRouter
<RouterProvider>
loader
and mutate with a Route action
errorElement
<Form>
componentuseFetcher()
defer
and Await
<ScrollRestoration>
New Features
<Link relative="path">
(#9160)Bug Fixes
useLocation
returns the scoped location inside a <Routes location>
component (#9094)<Link replace>
prop if it is defined (#8779)Updated Dependencies
react-router@6.4.0