Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6b72e78
Add RouterProvider and context for routing primitives
fzaninotto Dec 31, 2025
530f9ca
Add router-agnostic hooks and components
fzaninotto Dec 31, 2025
2323d0b
Replace react-router direct imports with router-agnostic utilities
fzaninotto Dec 31, 2025
c525720
Update tests and stories to use router agnostic utilities when relevant
fzaninotto Dec 31, 2025
f338f2b
Add ADR about router abstraction
fzaninotto Dec 31, 2025
dbd748e
Update documentation to use router abstraction
fzaninotto Dec 31, 2025
e87b9f6
Add TanStackRouterProvider
fzaninotto Dec 31, 2025
2dc6e0b
Fix yarn.lock
fzaninotto Jan 2, 2026
3b906c2
Fix potential ReDOS vulnerability
fzaninotto Jan 2, 2026
f4911fd
Add headless documentation
fzaninotto Jan 2, 2026
b6140c9
Fix doc
fzaninotto Jan 11, 2026
4f98d85
Improve stories
fzaninotto Jan 11, 2026
31f10cd
Fix corner case with basename
fzaninotto Jan 11, 2026
6a75c73
Fix multiple TanStack Router adapter issues
fzaninotto Jan 14, 2026
06edd91
Fix linter issues
fzaninotto Jan 14, 2026
40880cd
Move TanStack Router adapter to a standalone package
fzaninotto Jan 17, 2026
f726243
remove global dependency to tanstack router
fzaninotto Jan 17, 2026
a597df1
Fix Navigate doesn't handle location objects
fzaninotto Jan 21, 2026
9c02c93
Fix Link doesn't manage search-only to param
fzaninotto Jan 21, 2026
4b25ef5
Fix handling of routes with no path
fzaninotto Jan 21, 2026
fa50f65
Fix route specificity comparison by adding a catch-all detection
fzaninotto Jan 21, 2026
bd27c16
Merge branch 'next' into routerprovider
fzaninotto Jan 21, 2026
886df63
Fix warnings
fzaninotto Jan 21, 2026
9b65cab
Review
fzaninotto Jan 22, 2026
00340cb
Fix export
fzaninotto Jan 22, 2026
5fd97be
Fix Navigate when used with search-only location
fzaninotto Jan 22, 2026
38e1bb4
Avoid changing the order of search parameters
fzaninotto Jan 23, 2026
128c4f0
Fix formatting
fzaninotto Jan 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ build-ra-core:
@echo "Transpiling ra-core files...";
@cd ./packages/ra-core && yarn build

build-ra-router-tanstack:
@echo "Transpiling ra-router-tanstack files...";
@cd ./packages/ra-router-tanstack && yarn build

build-ra-ui-materialui:
@echo "Transpiling ra-ui-materialui files...";
@cd ./packages/ra-ui-materialui && yarn build
Expand Down Expand Up @@ -116,7 +120,7 @@ update-package-exports: ## Update the package.json "exports" field for all packa
@echo "Updating package exports..."
@yarn tsx ./scripts/update-package-exports.ts

build: build-ra-core build-ra-data-fakerest build-ra-ui-materialui build-ra-data-json-server build-ra-data-local-forage build-ra-data-local-storage build-ra-data-simple-rest build-ra-data-graphql build-ra-data-graphql-simple build-ra-input-rich-text build-data-generator build-ra-language-english build-ra-language-french build-ra-i18n-i18next build-ra-i18n-polyglot build-react-admin build-ra-no-code build-create-react-admin update-package-exports ## compile ES6 files to JS
build: build-ra-core build-ra-router-tanstack build-ra-data-fakerest build-ra-ui-materialui build-ra-data-json-server build-ra-data-local-forage build-ra-data-local-storage build-ra-data-simple-rest build-ra-data-graphql build-ra-data-graphql-simple build-ra-input-rich-text build-data-generator build-ra-language-english build-ra-language-french build-ra-i18n-i18next build-ra-i18n-polyglot build-react-admin build-ra-no-code build-create-react-admin update-package-exports ## compile ES6 files to JS

doc: ## compile doc as html and launch doc web server
@yarn doc
Expand Down
31 changes: 29 additions & 2 deletions docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ Here are all the props accepted by the component:
| `queryClient` | Optional | `QueryClient` | - | The react-query client |
| `ready` | Optional | `Component` | `Ready` | The content of the ready page |
| `requireAuth` | Optional | `boolean` | `false` | Flag to require authentication for all routes |
| `routerProvider` | Optional | `RouterProvider`| `reactRouterProvider`| The router provider for navigation |
| `store` | Optional | `Store` | - | The Store for managing user preferences |
| `theme` | Optional | `object` | `default LightTheme` | The main (light) theme configuration |
| `title` | Optional | `string` | - | The error page title |
Expand Down Expand Up @@ -1050,6 +1051,28 @@ const App = () => (
);
```

## `routerProvider`

React-admin uses a router abstraction layer that allows you to choose between different routing libraries. By default, it uses [react-router](https://reactrouter.com/), but you can also use [TanStack Router](./TanStackRouter.md).

To use TanStack Router, pass the `tanStackRouterProvider` to the `routerProvider` prop:

```tsx
import { Admin, Resource } from 'react-admin';
import { tanStackRouterProvider } from 'ra-router-tanstack';
import { dataProvider } from './dataProvider';

const App = () => (
<Admin dataProvider={dataProvider} routerProvider={tanStackRouterProvider}>
<Resource name="posts" list={PostList} />
</Admin>
);
```

See the [TanStack Router documentation](./TanStackRouter.md) for more details on using TanStack Router with react-admin.

**Tip**: When using `tanStackRouterProvider`, navigation blocking (used by `warnWhenUnsavedChanges`) works out of the box, without requiring a Data Router setup.

## `store`

The `<Admin>` component initializes a [Store](./Store.md) for user preferences using `localStorage` as the storage engine. You can override this by passing a custom `store` prop.
Expand Down Expand Up @@ -1156,9 +1179,13 @@ const App = () => (
export default App;
```

## Using A Custom Router
## Using A Different Router Library

React-admin supports multiple routing libraries through its [router abstraction](./Routing.md). By default, it uses react-router, but you can also use [TanStack Router](./TanStackRouter.md) via the [`routerProvider`](#routerprovider) prop.

## Using A Custom react-router Configuration

React-admin uses [the react-router library](https://reactrouter.com/) to handle routing, with a [HashRouter](https://reactrouter.com/en/6/router-components/hash-router#hashrouter). This means that the hash portion of the URL (i.e. `#/posts/123` in the example) contains the main application route. This strategy has the benefit of working without a server, and with legacy web browsers.
By default, react-admin uses react-router with a [HashRouter](https://reactrouter.com/en/6/router-components/hash-router#hashrouter). This means that the hash portion of the URL (i.e. `#/posts/123` in the example) contains the main application route. This strategy has the benefit of working without a server, and with legacy web browsers.

But you may want to use another routing strategy, e.g. to allow server-side rendering of individual pages. React-router offers various Router components to implement such routing strategies. If you want to use a different router, simply put your app in a create router function. React-admin will detect that it's already inside a router, and skip its own router.

Expand Down
21 changes: 13 additions & 8 deletions docs/AppBar.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,11 @@ export const MyAppBar = () => (
</AppBar>
);
```

If you omit `<TitlePortal>`, `<AppBar>` will no longer display the page title. This can be done on purpose, e.g. if you want to render something completely different in the AppBar, like a company logo and a search engine:

{% raw %}

```jsx
// in src/MyAppBar.js
import { AppBar } from 'react-admin';
Expand All @@ -137,6 +138,7 @@ const MyAppBar = () => (
</AppBar>
);
```

{% endraw %}

## `color`
Expand All @@ -159,6 +161,7 @@ export const MyAppBar = () => <AppBar color="primary" />;
Pass an `sx` prop to customize the style of the main component and the underlying elements (see [the `sx` documentation](./SX.md) for syntax and examples).

{% raw %}

```jsx
// in src/MyAppBar.js
import { AppBar } from 'react-admin';
Expand All @@ -172,6 +175,7 @@ export const MyAppBar = () => (
/>
);
```

{% endraw %}

This property accepts the following subclasses:
Expand All @@ -194,7 +198,7 @@ By default, the `<AppBar>` renders three buttons in addition to the user menu:

If you want to reorder or remove these buttons, you can customize the toolbar by passing a `toolbar` prop.

```jsx
```jsx
// in src/MyAppBar.js
import {
AppBar,
Expand Down Expand Up @@ -246,17 +250,15 @@ If your app uses [authentication](./Authentication.md), the `<AppBar>` component
Your browser does not support the video tag.
</video>


The content of the user menu depends on the return value of `authProvider.getIdentity()`. The user menu icon renders an anonymous avatar, or the `avatar` property of the identity object if present. If the identity object contains a `fullName` property, it is displayed after the avatar.
The content of the user menu depends on the return value of `authProvider.getIdentity()`. The user menu icon renders an anonymous avatar, or the `avatar` property of the identity object if present. If the identity object contains a `fullName` property, it is displayed after the avatar.

You can customize the user menu by passing a `userMenu` prop to the `<AppBar>` component.

```tsx
import * as React from 'react';
import { AppBar, Logout, UserMenu, useUserMenu } from 'react-admin';
import { AppBar, Logout, UserMenu, useUserMenu, LinkBase } from 'react-admin';
import { MenuItem, ListItemIcon, ListItemText } from '@mui/material';
import SettingsIcon from '@mui/icons-material/Settings';
import { Link } from "react-router-dom";

// It's important to pass the ref to allow Material UI to manage the keyboard navigation
const SettingsMenuItem = React.forwardRef<HTMLAnchorElement>((props, ref) => {
Expand All @@ -269,7 +271,7 @@ const SettingsMenuItem = React.forwardRef<HTMLAnchorElement>((props, ref) => {
<MenuItem
onClick={onClose}
ref={ref}
component={Link}
component={LinkBase}
to="/settings"
// It's important to pass the props to allow Material UI to manage the keyboard navigation
{...props}
Expand Down Expand Up @@ -299,6 +301,7 @@ Note that you still have to include the `<Logout>` component in the user menu, a
You can also customize the default icon by setting the `icon` prop to the `<UserMenu />` component.

{% raw %}

``` jsx
import { AppBar, UserMenu } from 'react-admin';
import Avatar from '@mui/material/Avatar';
Expand All @@ -317,6 +320,7 @@ const MyUserMenu = props => (<UserMenu {...props} icon={<MyCustomIcon />} />);

const MyAppBar = () => <AppBar userMenu={<MyUserMenu />} />;
```

{% endraw %}

Finally, you can hide the user menu by setting the `userMenu` prop to `false`.
Expand Down Expand Up @@ -454,6 +458,7 @@ export const MyAppbar = () => (
If react-admin's `<AppBar>` component doesn't meet your needs, you can build your own component using Material UI's `<AppBar>`. Here is an example:

{% raw %}

```jsx
// in src/MyAppBar.js
import { AppBar, Toolbar, Box } from '@mui/material';
Expand All @@ -469,6 +474,7 @@ export const MyAppBar = () => (
</AppBar>
);
```

{% endraw %}

Then, use your custom app bar in a custom `<Layout>` component:
Expand All @@ -494,4 +500,3 @@ By default, users can override the page title [in configurable mode](./Features.
<source src="./img/TitleConfigurable.mp4" type="video/mp4"/>
Your browser does not support the video tag.
</video>

2 changes: 1 addition & 1 deletion docs/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ React-admin is specifically designed to build [Single-Page Applications (SPA)](h

The SPA architecture ensures that react-admin apps are [exceptionally fast](./Features.md#fast), easy to host, and compatible with existing APIs without requiring a dedicated backend.

To achieve this, react-admin utilizes an internal router, powered by `react-router`, to display the appropriate screen when the user clicks on a link. Developers can define routes using the [`<Resource>`](./Resource.md) component for CRUD routes and the [`<CustomRoutes>`](./CustomRoutes.md) component for other routes.
To achieve this, react-admin utilizes an internal router to display the appropriate screen when the user clicks on a link. By default, this router is powered by [react-router](https://reactrouter.com/), but you can also use [TanStack Router](./TanStackRouter.md) through the `routerProvider` prop. Developers can define routes using the [`<Resource>`](./Resource.md) component for CRUD routes and the [`<CustomRoutes>`](./CustomRoutes.md) component for other routes.

For example, the following react-admin application:

Expand Down
10 changes: 5 additions & 5 deletions docs/Authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,13 @@ For instance, to add a "forgot password" link to the login page:

```jsx
import { Box, Link } from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import { Login, LoginForm } from 'react-admin';
import { Login, LoginForm, LinkBase } from 'react-admin';

const MyLogin = () => (
<Login>
<LoginForm />
<Box textAlign="center" mb={1}>
<Link component={RouterLink} to="/forgot-password">
<Link component={LinkBase} to="/forgot-password">
Forgot password?
</Link>
</Box>
Expand All @@ -272,7 +271,6 @@ const MyLogin = () => (
You can also customize the login form fields, by setting the `LoginForm` children:

```jsx
import { Link as RouterLink } from 'react-router-dom';
import { Login, LoginForm, TextInput, PasswordInput, required } from 'react-admin';

const MyLogin = () => (
Expand Down Expand Up @@ -431,6 +429,7 @@ export const authProvider = {
![Auth0 login flow diagram](./img/authProvider-OAuth-flow.png)
{% comment %}
Diagram source:

```mermaid
sequenceDiagram
autonumber
Expand All @@ -445,7 +444,8 @@ sequenceDiagram
Note over RA: handleCallback()<br/>Auth0Client.handleRedirectCallback()
RA->>U: Redirects to /posts
```
Edited with https://mermaid.live/edit

Edited with <https://mermaid.live/edit>
{% endcomment %}

**Tip:** You can choose when to redirect users to the third-party authentication service, such as directly in the `AuthProvider.checkAuth()` method or when they click a button on a [custom login page](#customizing-the-login-component).
Expand Down
3 changes: 1 addition & 2 deletions docs/AutoSave.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ The component renders nothing by default. It will save the current form values 3

```tsx
import { AutoSave } from '@react-admin/ra-form-layout';
import { Edit, PrevNextButton, SaveButton, SimpleForm, TextInput, Toolbar } from 'react-admin';
import { useParams } from 'react-router';
import { Edit, PrevNextButton, SaveButton, SimpleForm, TextInput, Toolbar, useParams } from 'react-admin';

const AutoSaveToolbar = () => (
<Toolbar>
Expand Down
6 changes: 2 additions & 4 deletions docs/Breadcrumb.md
Original file line number Diff line number Diff line change
Expand Up @@ -885,9 +885,8 @@ Let's see how the components for the songs list and detail pages define their ap
{% raw %}
```jsx
// in src/songs/SongList.js
import { useGetOne, List, SearchInput, DataTable, DateField } from 'react-admin';
import { useGetOne, List, SearchInput, DataTable, DateField, useParams } from 'react-admin';
import { useDefineAppLocation } from '@react-admin/ra-navigation';
import { useParams } from 'react-router-dom';

export const SongList = () => {
const { id } = useParams();
Expand Down Expand Up @@ -929,9 +928,8 @@ const EditSongButton = () => {

```jsx
// in src/songs/SongDetail.js
import { useGetOne, Edit, SimpleForm, TextInput, DateInput } from 'react-admin';
import { useGetOne, Edit, SimpleForm, TextInput, DateInput, useParams } from 'react-admin';
import { useDefineAppLocation } from '@react-admin/ra-navigation';
import { useParams } from 'react-router-dom';

export const SongDetail = () => {
const { id, songId } = useParams();
Expand Down
Loading
Loading