ra-core
Version:
Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React
153 lines (116 loc) • 5.39 kB
Markdown
# Routing Abstraction Layer
This directory contains react-admin's router abstraction layer, which enables using different routing libraries (react-router, TanStack Router, etc.) without changing application code.
## Architecture
### Provider Pattern
The abstraction uses a Provider pattern similar to `dataProvider` and `authProvider`:
```tsx
// Default: react-router (no configuration needed)
<Admin dataProvider={dataProvider}>
<Resource name="posts" list={PostList} />
</Admin>
// Alternative: TanStack Router
import { tanStackRouterProvider } from 'react-admin';
<Admin dataProvider={dataProvider} routerProvider={tanStackRouterProvider}>
<Resource name="posts" list={PostList} />
</Admin>
```
### Key Components
```
RouterProviderContext
│
├── RouterProvider (interface)
│ ├── Hooks: useLocation, useNavigate, useParams, useBlocker, useMatch, etc.
│ ├── Components: Link, Navigate, Route, Routes, Outlet
│ └── Utilities: matchPath, RouterWrapper
│
├── reactRouterProvider (default implementation)
│
└── tanStackRouterProvider (alternative implementation)
```
### Context Flow
```
Admin Component
└─ AdminRouter (basename setup)
└─ RouterWrapper (creates router if not in context)
└─ RouterProviderContext.Provider
└─ Application components
└─ Hooks delegate to provider via useRouterProvider()
```
## The RouterProvider Interface
The `RouterProvider` interface defines all routing primitives react-admin needs:
### Hooks
| Hook | Description | react-router equivalent |
|------|-------------|------------------------|
| `useLocation()` | Returns current location object | `useLocation()` |
| `useNavigate()` | Returns navigation function | `useNavigate()` |
| `useParams()` | Returns URL parameters | `useParams()` |
| `useBlocker()` | Blocks navigation (unsaved changes) | `useBlocker()` |
| `useMatch()` | Matches current location to pattern | `useMatch()` |
| `useInRouterContext()` | Checks if inside router | `useInRouterContext()` |
| `useCanBlock()` | Checks if blocking is supported | N/A (data router check) |
### Components
| Component | Description | react-router equivalent |
|-----------|-------------|------------------------|
| `Link` | Navigation link | `<Link>` |
| `Navigate` | Declarative redirect | `<Navigate>` |
| `Route` | Route definition | `<Route>` |
| `Routes` | Routes container | `<Routes>` |
| `Outlet` | Nested route outlet | `<Outlet>` |
### Utilities
| Utility | Description |
|---------|-------------|
| `matchPath()` | Match a pattern against a pathname |
| `RouterWrapper` | Creates router if not in context |
## Implementing a New Router Adapter
To add support for a new routing library, implement the `RouterProvider` interface:
```typescript
import type { RouterProvider } from './RouterProvider';
export const myRouterProvider: RouterProvider = {
// Hooks
useLocation: () => { /* ... */ },
useNavigate: () => { /* ... */ },
useParams: () => { /* ... */ },
useBlocker: (shouldBlock) => { /* ... */ },
useMatch: (pattern) => { /* ... */ },
useInRouterContext: () => { /* ... */ },
useCanBlock: () => { /* ... */ },
// Components
Link: MyLink,
Navigate: MyNavigate,
Route: MyRoute,
Routes: MyRoutes,
Outlet: MyOutlet,
// Utilities
matchPath: (pattern, pathname) => { /* ... */ },
RouterWrapper: MyRouterWrapper,
};
```
### Key Implementation Details
1. **RouterWrapper must support two modes**:
- **Standalone mode**: Create a router when none exists
- **Embedded mode**: Pass through when already in a router context
2. **Route/Routes translation**: If your router uses configuration-based routing (like TanStack Router), implement a translation layer that accepts JSX-based `<Route>` elements.
3. **Duck-typing for Route detection**: The Routes component should use duck-typing to detect Route elements, not strict type checking. This allows users to import Route from react-router-dom.
```typescript
// Good: duck-typing
const isRouteElement = (child) => {
return child.props.path !== undefined ||
child.props.index !== undefined ||
child.props.element !== undefined;
};
// Bad: strict type checking (breaks with react-router's Route)
const isRouteElement = (child) => child.type === Route;
```
## Backward Compatibility
The abstraction maintains full backward compatibility with react-admin's existing routing behavior (react-router based):
1. **Default provider**: `reactRouterProvider` is the default, so existing apps work without changes
2. **Import from react-admin**: Hooks like `useNavigate`, `useLocation`, `useParams` can be imported from `react-admin`
3. **react-router imports still work**: Users can still import directly from react-router-dom if they prefer
## Key Files Reference
| File | Purpose |
|------|---------|
| `RouterProvider.ts` | The interface contract all adapters must implement |
| `RouterProviderContext.tsx` | Context and `useRouterProvider` hook |
| `adapters/reactRouterProvider.tsx` | Default implementation using react-router |
| `adapters/tanStackRouterProvider.tsx` | Alternative implementation using TanStack Router |
| `AdminRouter.tsx` | High-level component that sets up routing for Admin |