UNPKG

11 kB Markdown View Raw
1---
2title: Upgrading from v6
3order: 2
4---
5
6# Upgrading from v6
7
8<docs-info>
9
10React Router v7 requires the following minimum versions:
11
12- `node@20`
13- `react@18`
14- `react-dom@18`
15
16</docs-info>
17
18The v7 upgrade has no breaking changes if you have enabled all future flags. These flags allow you to update your app one change at a time. We highly recommend you make a commit after each step and ship it instead of doing everything all at once.
19
20## Update to latest v6.x
21
22First update to the latest minor version of v6.x to have the latest future flags and console warnings.
23
24👉 **Update to latest v6**
25
26```shellscript nonumber
27npm install react-router-dom@6
28```
29
30### v7_relativeSplatPath
31
32**Background**
33
34Changes 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/CHANGELOG.md#futurev7_relativesplatpath) for more information.
35
36👉 **Enable the flag**
37
38Enabling the flag depends on the type of router:
39
40```tsx
41<BrowserRouter
42 future={{
43 v7_relativeSplatPath: true,
44 }}
45/>
46```
47
48```tsx
49createBrowserRouter(routes, {
50 future: {
51 v7_relativeSplatPath: true,
52 },
53});
54```
55
56**Update your Code**
57
58If you have any routes with a path + a splat like `<Route path="dashboard/*">` that have relative links like `<Link to="relative">` or `<Link to="../relative">` beneath them, you will need to update your code.
59
60👉 **Split the `<Route>` into two**
61
62Split any multi-segment splat `<Route>` into a parent route with the path and a child route with the splat:
63
64```diff
65<Routes>
66 <Route path="/" element={<Home />} />
67- <Route path="dashboard/*" element={<Dashboard />} />
68+ <Route path="dashboard">
69+ <Route path="*" element={<Dashboard />} />
70+ </Route>
71</Routes>
72
73// or
74createBrowserRouter([
75 { path: "/", element: <Home /> },
76 {
77- path: "dashboard/*",
78- element: <Dashboard />,
79+ path: "dashboard",
80+ children: [{ path: "*", element: <Dashboard /> }],
81 },
82]);
83```
84
85👉 **Update relative links**
86
87Update any `<Link>` elements within that route tree to include the extra `..` relative segment to continue linking to the same place:
88
89```diff
90function Dashboard() {
91 return (
92 <div>
93 <h2>Dashboard</h2>
94 <nav>
95 <Link to="/">Dashboard Home</Link>
96- <Link to="team">Team</Link>
97- <Link to="projects">Projects</Link>
98+ <Link to="../team">Team</Link>
99+ <Link to="../projects">Projects</Link>
100 </nav>
101
102 <Routes>
103 <Route path="/" element={<DashboardHome />} />
104 <Route path="team" element={<DashboardTeam />} />
105 <Route
106 path="projects"
107 element={<DashboardProjects />}
108 />
109 </Routes>
110 </div>
111 );
112}
113```
114
115### v7_startTransition
116
117**Background**
118
119This 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#futurev7_starttransition) for more information.
120
121👉 **Enable the flag**
122
123```tsx
124<BrowserRouter
125 future={{
126 v7_startTransition: true,
127 }}
128/>
129
130// or
131<RouterProvider
132 future={{
133 v7_startTransition: true,
134 }}
135/>
136```
137
138👉 **Update your Code**
139
140You don't need to update anything unless you are using `React.lazy` _inside_ of a component.
141
142Using `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.
143
144<docs-info>We added a flag to opt-out of `React.startTransition` in v7 so you can use that to upgrade to v7 without adopting React transition-enabled navigations if needed. See the [transition docs][transitions] for more information.</docs-info>
145
146### v7_fetcherPersist
147
148<docs-warning>If you are not using a `<RouterProvider>` you can skip this</docs-warning>
149
150**Background**
151
152The 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.
153
154**Enable the Flag**
155
156```tsx
157createBrowserRouter(routes, {
158 future: {
159 v7_fetcherPersist: true,
160 },
161});
162```
163
164**Update your Code**
165
166It'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.
167
168### v7_normalizeFormMethod
169
170<docs-warning>If you are not using a `<RouterProvider>` you can skip this</docs-warning>
171
172This 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.
173
174👉 **Enable the Flag**
175
176```tsx
177createBrowserRouter(routes, {
178 future: {
179 v7_normalizeFormMethod: true,
180 },
181});
182```
183
184**Update your Code**
185
186If 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).
187
188👉 **Compare `formMethod` to UPPERCASE**
189
190```diff
191-useNavigation().formMethod === "post"
192-useFetcher().formMethod === "get";
193+useNavigation().formMethod === "POST"
194+useFetcher().formMethod === "GET";
195```
196
197### v7_partialHydration
198
199<docs-warning>If you are not using a `<RouterProvider>` you can skip this</docs-warning>
200
201This enables partial hydration of a data router which is primarily used for SSR frameworks, but it is also useful if you are using `lazy` to load your route modules. 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.
202
203👉 **Enable the Flag**
204
205```tsx
206createBrowserRouter(routes, {
207 future: {
208 v7_partialHydration: true,
209 },
210});
211```
212
213**Update your Code**
214
215With 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`.
216
217👉 **Replace `fallbackElement` with `HydrateFallback`**
218
219```diff
220const router = createBrowserRouter(
221 [
222 {
223 path: "/",
224 Component: Layout,
225+ HydrateFallback: Fallback,
226 // or
227+ hydrateFallbackElement: <Fallback />,
228 children: [],
229 },
230 ],
231);
232
233
234<RouterProvider
235 router={router}
236- fallbackElement={<Fallback />}
237/>
238```
239
240### v7_skipActionErrorRevalidation
241
242<docs-warning>If you are not using a `createBrowserRouter` you can skip this</docs-warning>
243
244When 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.
245
246👉 **Enable the Flag**
247
248```tsx
249createBrowserRouter(routes, {
250 future: {
251 v7_skipActionErrorRevalidation: true,
252 },
253});
254```
255
256**Update your Code**
257
258In 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:
259
260👉 **Option 1: Change the `action` to avoid mutations in error scenarios**
261
262```js
263// Before
264async function action() {
265 await mutateSomeData();
266 if (detectError()) {
267 throw new Response(error, { status: 400 });
268 }
269 await mutateOtherData();
270 // ...
271}
272
273// After
274async function action() {
275 if (detectError()) {
276 throw new Response(error, { status: 400 });
277 }
278 // All data is now mutated after validations
279 await mutateSomeData();
280 await mutateOtherData();
281 // ...
282}
283```
284
285👉 **Option 2: Opt-into revalidation via `shouldRevalidate` and `actionStatus`**
286
287```js
288async function action() {
289 await mutateSomeData();
290 if (detectError()) {
291 throw new Response(error, { status: 400 });
292 }
293 await mutateOtherData();
294}
295
296async function loader() { ... }
297
298function shouldRevalidate({ actionStatus, defaultShouldRevalidate }) {
299 if (actionStatus != null && actionStatus >= 400) {
300 // Revalidate this loader when actions return a 4xx/5xx status
301 return true;
302 }
303 return defaultShouldRevalidate;
304}
305```
306
307## Deprecations
308
309The `json` and `defer` methods are deprecated in favor of returning raw objects.
310
311```diff
312async function loader() {
313- return json({ data });
314+ return { data };
315```
316
317If you were using `json` to serialize your data to JSON, you can use the native [Response.json()][response-json] method instead.
318
319## Upgrade to v7
320
321Now that your app is caught up, you can simply update to v7 (theoretically!) without issue.
322
323👉 **Install v7**
324
325```shellscript nonumber
326npm install react-router-dom@latest
327```
328
329👉 **Replace react-router-dom with react-router**
330
331In v7 we no longer need `"react-router-dom"` as the packages have been simplified. You can import everything from `"react-router"`:
332
333```shellscript nonumber
334npm uninstall react-router-dom
335npm install react-router@latest
336```
337
338Note you only need `"react-router"` in your package.json.
339
340👉 **Update imports**
341
342Now you should update your imports to use `react-router`:
343
344```diff
345-import { useLocation } from "react-router-dom";
346+import { useLocation } from "react-router";
347```
348
349Instead of manually updating imports, you can use this command. Make sure your git working tree is clean though so you can revert if it doesn't work as expected.
350
351```shellscript nonumber
352find ./path/to/src \( -name "*.tsx" -o -name "*.ts" -o -name "*.js" -o -name "*.jsx" \) -type f -exec sed -i '' 's|from "react-router-dom"|from "react-router"|g' {} +
353```
354
355If you have GNU `sed` installed (most Linux distributions), use this command instead:
356
357```shellscript nonumber
358find ./path/to/src \( -name "*.tsx" -o -name "*.ts" -o -name "*.js" -o -name "*.jsx" \) -type f -exec sed -i 's|from "react-router-dom"|from "react-router"|g' {} +
359```
360
361👉 **Update DOM-specific imports**
362
363`RouterProvider` and `HydratedRouter` come from a deep import because they depend on `"react-dom"`:
364
365```diff
366-import { RouterProvider } from "react-router-dom";
367+import { RouterProvider } from "react-router/dom";
368```
369
370Note you should use a top-level import for non-DOM contexts, such as Jest tests:
371
372```diff
373-import { RouterProvider } from "react-router-dom";
374+import { RouterProvider } from "react-router";
375```
376
377Congratulations, you're now on v7!
378
379[react-flushsync]: https://react.dev/reference/react-dom/flushSync
380[response-json]: https://developer.mozilla.org/en-US/docs/Web/API/Response/json
381[data-util]: https://api.reactrouter.com/v7/functions/react-router.data.html
382[transitions]: ../explanation/react-transitions