Add support for React router v8#11289
Conversation
|
Thanks for your PR, but U'm not fan of the extra compatibility layer & the partial reimplementation. I'd prefer if you created an entirely new adapter for react-router v8 (in a new package). We'd probably need to move the current routing adapter to a package, too. |
React Router v8 merged react-router-dom into react-router and requires React 19. Rather than adding a compatibility layer and partial reimplementation inside ra-core (per review feedback on marmelab#11289), v8 support ships as a standalone, opt-in adapter package mirroring ra-router-tanstack. The adapter is a thin pass-through over react-router v8's native API: the obsolete v6/v7 `future` flags are dropped (now defaults in v8) and imports come from `react-router` instead of `react-router-dom`. ra-core keeps its built-in react-router v6/v7 adapter as the default, so existing apps are unaffected. The package is ESM-only because react-router v8 is ESM-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After merging the ra-router-react-router-v8 package, the in-core react-router adapter no longer needs the compatibility shims: - reactRouterProvider imports everything from `react-router`, with only `Link` and `createHashRouter` from `react-router-dom` - Form.stories uses `HashRouter` from `react-router-dom` directly - Remove CompatHashRouter, CompatLink and their tests - Restore `react-router-dom` to the dependencies of ra-core, ra-ui-materialui and react-admin, and narrow the react-router range back to ^6.28.1 || ^7.1.1 (v8 is supported via the separate package) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No code in ra-ui-materialui imports react-router-dom; all routing imports come from react-router. ra-core declares its own react-router-dom peer for its router adapter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Individual zshy builds had rewritten the exports field to the flat pre-normalization form. Restore the nested import/require form that update-package-exports produces and that matches upstream. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-exports update-package-exports forces dual import/require exports on every package; the ESM-only v8 adapter must keep its ESM-only exports (it has no .cjs build). Also keep minor README/story/doc wording. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zshy's ESM build pass already uses Bundler resolution and cjs is disabled, so these compilerOptions are not needed for the build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Hi @fzaninotto The ra-router-react-router-v8 is pulled out to a separate package |
fzaninotto
left a comment
There was a problem hiding this comment.
Thanks. Several remarks:
- This is a new feature, so it must be PRed against
nextand notmaster - As RR v7 is still loaded by default by ra-core, users of RRv8 will end up with 2 versions of RR, leading to probable context conflicts. You also need to extract the default RRv7 adapter to a package in the monorepo
- The new RRv8 adapter needs unit tests in addition to user stories
- you also need to test the basename feature (check the tests of the tanstack router adapter)
| </li> | ||
| ))} | ||
| </ul> | ||
| <button onClick={() => navigate('/posts/create')}> |
There was a problem hiding this comment.
why not use test-ui's CreateButton?
There was a problem hiding this comment.
Ah previously this is mirroring the tanStackRoutherProvider.stories.tsx https://github.com/marmelab/react-admin/blob/master/packages/ra-router-tanstack/src/tanStackRouterProvider.stories.tsx#L80
Their code uses <button> instead of <CreateButton>
Will try to apply code review suggestions anyway.
| <ListBase | ||
| resource="comments" | ||
| render={({ data }) => ( | ||
| <ul> |
There was a problem hiding this comment.
<ul> and <li> are used previously because it is a mirror of
Can be updated to simpleList from test-ui
| * BasicStandalone: react-admin runs on its own hash router created by the | ||
| * react-router v8 provider (no surrounding router). | ||
| */ | ||
| export const BasicStandalone = () => ( |
There was a problem hiding this comment.
by convention, the most basic story is called Basic in ra
There was a problem hiding this comment.
Will change this to Basic story.
However the tanstack story does not follow the convention
The code was copied from there.
| dataProvider={dataProvider} | ||
| layout={Layout} | ||
| > | ||
| <Resource name="posts" list={PostList} show={PostShow} /> |
There was a problem hiding this comment.
same: use a customRoute to test this hook
There was a problem hiding this comment.
The stories will be rewritten
| * UseBlockerTest: blocks navigation while a form is dirty. | ||
| */ | ||
| const BlockerForm = () => { | ||
| const [dirty, setDirty] = React.useState(false); |
There was a problem hiding this comment.
I suggest you use useWarnWhenUnsavedChanges instead of reimplementing it.
|
Hi @fzaninotto Can you restore the I have already noted this in the additional checks. |
|
Sure, the |
Move the default react-router v6 provider out of ra-core into a new ra-router-react-router package, and rename the React Router v8 adapter from ra-router-react-router-v8 to ra-router-react-router-next. Why: - Decouples react-router from ra-core/ra-ui-materialui/react-admin: they now keep react-router only as a devDependency (for tests), while the adapter package carries the runtime dependency. This lets the router library evolve independently of the framework packages. - Sets up the v6 migration path: ra-router-react-router-next (v8) is the recommended choice for new projects and will be republished as ra-router-react-router in react-admin v6, while the v6 default is kept for backward compatibility. The new package is a dependency-free leaf (it mirrors ra-core's RouterProvider contract with local types instead of importing it) so it can build before ra-core, avoiding a circular build dependency; ra-core enforces interface conformance at the consumption site. ra-core and react-admin now depend on ra-router-react-router; ra-core re-exports reactRouterProvider for backward compatibility. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the reactRouterProvider re-export from the routing barrel and expose it once via `export * from 'ra-router-react-router'` at the end of ra-core's index. Internal consumers (CoreAdminContext) now import it directly from the package. Why: keeps a single, explicit source for the re-exported adapter instead of duplicating the re-export inside the routing module. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the react-router v8 opt-in provider (ra-router-react-router-next) to the Key Components tree so it matches the Key Files Reference table. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…EADME Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Declare react-router/react-router-dom as `^6.28.1 || ^7.1.1` in both the dependencies and peerDependencies of ra-router-react-router, while pinning the build to v6 via devDependencies. This restores the v6/v7 support range for consumers while keeping the monorepo on a single hoisted v6 instance (so the adapter builds against v6 and shares react-router with ra-core, avoiding the duplicate-instance breakage a bare `^6 || ^7` range caused). Also restore the "v6/v7" wording across the README and docs to match the declared range, and drop a now-obvious build-order comment in the Makefile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite the React Router v8 adapter stories to follow the ra-router-tanstack story set 1:1 (same scenarios, helpers, and sample data), adapting only the embedded-router story to React Router v8's createHashRouter/RouterProvider. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
React Router v8 is ESM-only and requires React 19, neither of which fits the default CommonJS jest project. Add a dedicated jest project for ra-router-react-router-next (its own jest.config.cjs, wired into the root config via `projects`): it runs in ESM mode, transforms react-router, and forces React 19 (a new devDependency of the package) as a single instance across the tree to avoid duplicate-React hook errors. The test scripts now pass `NODE_OPTIONS=--experimental-vm-modules`. The new spec mirrors the tanstack adapter tests: matchPath (including basename scenarios), RouterWrapper standalone/embedded with a basename, and the routing hooks/components exercised through the stories. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the PR review comments on the stories: - use test-ui components (SimpleList, SimpleShowLayout, SimpleForm, CreateButton) instead of hand-rolled markup - rename the basic story to `Basic` (ra convention) - test Link and the routing hooks (useParams/useMatch/useLocation/ useInRouterContext/useCanBlock) through CustomRoutes with no resource - drop the redundant stories (the duplicate "basic" variant and the redirect-to-first-resource story, which is already react-admin's default) - let EditBase read the route params instead of reading them manually - use the built-in useWarnWhenUnsavedChanges (via Form's warnWhenUnsavedChanges) instead of reimplementing navigation blocking The spec is updated to match the reworked stories. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the react-router-next basename fix in the default v6/v7 adapter so embedded react-admin (mounted under a basename) resolves internal links correctly. Because ra-core depends on this package, it cannot import ra-core's useBasename (circular build dependency); instead RouterWrapper publishes the basename via a provider-local context that Link, Navigate, and useNavigate read. Standalone mode keeps the basename on the hash router and leaves the context empty to avoid double-prepending. Adds the embedded nested-navigation and standalone-with-basename tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The story files must stay as mirrored (only the embeddedAdminRoute "/admin/*" tweak is allowed). Remove the StandaloneWithBasename story and its dedicated double-prepend test from both adapters; the basename provider fix and the embedded nested-navigation test remain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…apters Replace the module-level prependBasename helper with the same inline resolvePath helper the tanstack adapter defines inside Link/useNavigate (same body, same guard order, same comment), and use it in Link, Navigate, and useNavigate. Behavior is unchanged; this keeps the three router adapters structurally aligned for easier comparison. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The resolvePath helper inside useNavigate carries a two-line lead comment plus an inner "Don't prepend if path already includes basename" comment in the tanstack adapter, unlike the one-line form inside Link. Align both router adapters so each call site matches its tanstack counterpart exactly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pter Drop the extra basenameRef; useNavigate now reads basename from the hook and lists it in the useCallback dependency array, matching the tanstack adapter's shape. The navigateRef (react-router-specific, issue marmelab#7634) stays. Behavior unchanged; 107/107 in both adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ters Follow the tanstack adapter's useNavigate skeleton exactly: numeric (go back/forward) branch first, then the resolvePath helper, then the object branch, then the string path — with the same comments. Only the call differs (navigateRef.current(to) instead of router.history.go(to)) since react-router's navigate handles all three input shapes. Keeps the three router adapters diff-aligned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the tanstack adapter's Link: branch on the object form first (`if (typeof to === 'object' && to !== null)`) and fall back to `resolvePath(to as string)` in the else, instead of a nested ternary. Behavior unchanged; 107/107 in both adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build a resolvedPath string via the tanstack Navigate shape (string vs
object branch, append search/hash, then the inline basename-prepend
block) and render <Navigate to={resolvedPath} replace state />. Drops the
to.state merge since react-router's Navigate `to` (a Path) has no state
field — state stays a prop. 107/107 in both adapters.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pass through the rest of the Navigate props ({...rest}) instead of
listing replace/state explicitly, and drop the "(like react-router does)"
aside from the basename comment. 107/107 in both adapters.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the provider-local useProviderBasename to useBasename and publish the basename via BasenameContext in both RouterWrapper branches. Fix quote style to satisfy prettier. 107/107 in the react-router adapter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use fireEvent.click for navigation in both router adapter specs, matching the tanstack provider spec's convention, and keep userEvent.setup/user.type only in the two unsaved-changes form tests that need typing. 107/107 in both adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore the tanstack import ordering (PathlessLayoutRoutes before NestedResourcesPrecedence) in both router adapter specs. No behavior change; 107/107 in both adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… parity)
The earlier blanket fireEvent.click conversion wrongly flipped the one
test that tanstack drives with user.click ("navigate back to parent app").
Match tanstack per-test: user.click + userEvent.setup there, fireEvent.click
everywhere else. 107/107 in both adapters.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the "// Wait for data to load before clicking" comment that tanstack has between the two findByText calls in the navigate-within-nested-routes test, in both router adapter specs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the extra explanatory comment atop the matchPath block and reword the useCanBlock test to "should return true for React Router", matching the tanstack spec wording. 107/107 in both adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename to "should decode only path separator in URL-encoded splat values" in both router adapter specs. 107/107. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the test to "should decode only path separator in URL-encoded params", restore tanstack's "// UTF-8 characters: 衣類/衣類 encoded" comment, and drop the react-router-specific note. 107/107 in both adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the three tests to match the tanstack useBlocker wording: "should block navigation when form is dirty", "should allow navigation when clicking proceed", and "should not block navigation when form is not dirty". 107/107 in both adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "should block navigation when form is dirty" test was conflating the block (confirm fires) with the cancel outcome (declining keeps you on the form). Split into separate "should block navigation when form is dirty" and "should cancel navigation when clicking cancel" tests, ordered like tanstack (block, proceed, cancel, not-dirty). 108/108 in both adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the tanstack useBlocker tests, which have no inline comments in the test bodies. 108/108 in both adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er-dom Part of the React Router v8 migration: v8 merged react-router-dom into react-router. Move Route/Routes/RouterProvider/BrowserRouter doc imports to react-router, while keeping createBrowserRouter and Link on react-router-dom for v6 compatibility. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BrowserRouter is not exported from the react-router core package in v6 (it is DOM-only, like Link and createBrowserRouter). Revert the two BrowserRouter imports back to react-router-dom; Routes/Route stay on react-router. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`NODE_OPTIONS=--experimental-vm-modules` is process-global, so setting it on the shared `test-unit`/`test-unit-ci` scripts pushed the entire default (CommonJS) jest project into ESM mode. Jest then honored the `"type": "module"` field of transformed dependencies and refused to `require()` them, so every suite that transitively imports an ESM-only dep (e.g. react-hotkeys-hook, pulled in via the layout/button/form barrels) failed to load with "Must use import to load ES Module". This took out 93 suites across ra-ui-materialui, react-admin, ra-input-rich-text and ra-i18n-polyglot — the suites failed to run while their individual tests never executed. Keep the root config a single CommonJS project (no `projects` array, no flag) and run ra-router-react-router-next's ESM/React 19 tests as a separate jest invocation against its own jest.config.cjs, appended to the test scripts with the flag set only for that run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-next The package imports react-router at runtime, so it belongs in `dependencies` (matching the sibling ra-router-react-router), not only in dev/peer deps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ra-core is provided by the react-admin meta-package at runtime; the sibling ra-router-react-router does not declare it either. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore ra-core as a peerDependency of ra-router-react-router-next (it is provided by the host app, like the other ra-router adapters), and declare react-router as a peerDependency of ra-no-code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
react-admin imports from react-router (via ra-router-react-router), not react-router-dom directly, so the devDependency is unnecessary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem
react-router v8 is out
Solution
Follow #10440
Add support for React router v8
Find a way to avoid dependency resolution issue after dropping
react-router-domHow To Test
Build RA packages locally, then link with apps in /examples folder
Additional Checks
masterfor a bugfix or a documentation fix, ornextfor a featurenextbranch seems is disappeared, I assumemasterbranch can be used for new features.Needs to be end-2-end tested with /examples folder.
Stories using the routers are updated.
Also, please make sure to read the contributing guidelines.