Skip to content

Commit

Permalink
Merge pull request #8616 from marmelab/doc-explain-nested-resources
Browse files Browse the repository at this point in the history
[Doc] Fix nested routes explanation and add more examples
  • Loading branch information
djhi authored Feb 3, 2023
2 parents 445980d + 6294338 commit b25283a
Show file tree
Hide file tree
Showing 3 changed files with 474 additions and 28 deletions.
183 changes: 157 additions & 26 deletions docs/CustomRoutes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@ title: "The CustomRoutes Component"

# `<CustomRoutes>`

Lets you define custom pages in your react-admin application, using [react-router-dom](https://reactrouter.com/docs/en/v6/api#routes-and-route) `<Routes>` elements.

## Usage

To register your own routes, pass one or several `<CustomRoutes>` elements as children of `<Admin>`. Declare as many [react-router-dom](https://reactrouter.com/docs/en/v6/api#routes-and-route) `<Route>` as you want inside them.
Alternatively, you can add your custom routes to resources. They will be available under the resource prefix.

```jsx
// in src/App.js
import * as React from "react";
import { Admin, Resource, CustomRoutes } from 'react-admin';
import { Route } from "react-router-dom";
import simpleRestProvider from 'ra-data-simple-rest';
import posts, { PostAnalytics } from './posts';

import dataProvider from './dataProvider';
import posts from './posts';
import comments from './comments';
import Settings from './Settings';
import Profile from './Profile';

const App = () => (
<Admin dataProvider={simpleRestProvider('http://path.to.my.api')}>
<Resource name="posts" {...posts}>
<Route path="analytics" element={<PostAnalytics/>} />
</Resource>
<Admin dataProvider={dataProvider}>
<Resource name="posts" {...posts} />
<Resource name="comments" {...comments} />
<CustomRoutes>
<Route path="/settings" element={<Settings />} />
Expand All @@ -37,7 +39,63 @@ export default App;

Now, when a user browses to `/settings` or `/profile`, the components you defined will appear in the main part of the screen.

**Tip**: To look like other react-admin pages, your custom pages should have the following structure:
## `children`

`children` of the `<CustomRoutes>` component must be `<Route>` elements from [react-router-dom](https://reactrouter.com/docs/en/v6/api#routes-and-route), and map a path with a custom element.

```jsx
// in src/App.js
import { Admin, Resource, CustomRoutes } from 'react-admin';
import { Route } from "react-router-dom";

import dataProvider from './dataProvider';
import Settings from './Settings';
import Profile from './Profile';

const App = () => (
<Admin dataProvider={dataProvider}>
<CustomRoutes>
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
</CustomRoutes>
</Admin>
);

export default App;
```

## `noLayout`

By default, custom routes render within the application layout (with the menu and the app bar). If you want a custom route to render without the layout, e.g. for registration screens, then provide the `noLayout` prop on the `<CustomRoutes>` element:

```jsx
// in src/App.js
import { Admin, CustomRoutes } from 'react-admin';
import { Route } from "react-router-dom";

import dataProvider from './dataProvider';
import Register from './Register';
import Settings from './Settings';
import Profile from './Profile';

const App = () => (
<Admin dataProvider={dataProvider}>
<CustomRoutes noLayout>
<Route path="/register" element={<Register />} />
</CustomRoutes>
<CustomRoutes>
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
</CustomRoutes>
</Admin>
);
```

As illustrated above, there can be more than one `<CustomRoutes>` element inside an `<Admin>` component.

## Custom Page Title

To define the page title (displayed in the app bar), your custom pages can use the `<Title>` component from react-admin:

```jsx
// in src/Settings.js
Expand All @@ -57,34 +115,107 @@ const Settings = () => (
export default Settings;
```

**Tip**: It's up to you to create a [custom menu](./Theming.md#using-a-custom-menu) entry, or custom buttons, that lead to your custom pages.
`<Title>` uses a [React Portal](https://reactjs.org/docs/portals.html), so it doesn't matter *where* you put it in your component. The title will always be rendered in the app bar.

## Linking To Custom Routes

If you want a custom route to render without the layout (without the menu and the appBar), e.g. for registration screens, then provide the `noLayout` prop on the `<CustomRoutes>` element:
You can link to your pages using [react-router's Link component](https://reactrouter.com/en/main/components/link):

```jsx
// in src/App.js
import * as React from "react";
import { Admin, CustomRoutes } from 'react-admin';
import simpleRestProvider from 'ra-data-simple-rest';
import { Link as RouterLink } from 'react-router-dom';
import { Link } from '@mui/material';

const SettingsButton = () => (
<Link component={RouterLink} to="/settings">
Settings
</Link>
);
```

Alternately, create a [custom menu](./Menu.md) with entries for the custom pages.

```jsx
// in src/MyMenu.js
import { Menu } from 'react-admin';
import SettingsIcon from '@mui/icons-material/Settings';
import PeopleIcon from '@mui/icons-material/People';

export const MyMenu = () => (
<Menu>
<Menu.DashboardItem />
<Menu.ResourceItem to="/posts" />
<Menu.ResourceItem to="/comments" />
<Menu.Item to="/settings" primaryText="Users" leftIcon={<SettingsIcon />}/>
<Menu.Item to="/profile" primaryText="Miscellaneous" leftIcon={<PeopleIcon />}/>
</Menu>
);
```

## Sub-Routes

If you want to add sub-routes to a resource, add the `<Route>` elements as [children of the `<Resource>` element](./Resource.md#children):

```jsx
import { Admin, Resource } from 'react-admin';
import { Route } from "react-router-dom";
import Settings from './Settings';
import Register from './register';

import dataProvider from './dataProvider';
import posts from './posts';

const App = () => (
<Admin dataProvider={simpleRestProvider('http://path.to.my.api')}>
<CustomRoutes noLayout>
<Route path="/register" element={<Register />} />
</CustomRoutes>
<CustomRoutes noLayout>
<Route path="/settings" element={<Settings />} />
</CustomRoutes>
<Admin dataProvider={dataProvider}>
<Resource name="posts" {...posts}>
<Route path="analytics" element={<PostAnalytics/>} />
</Resource>
</Admin>
);

export default App;
// is equivalent to
const App = () => (
<Admin dataProvider={dataProvider}>
<Resource name="posts" {...posts} />
<CustomRoutes>
<Route path="/posts/analytics" element={<PostAnalytics />} />
</CustomRoutes>
</Admin>
);
```

When a user browses to `/register`, the `<Register>` component will appear outside the defined Layout, leaving you the freedom to design the screen the way you want.
This is usually useful for nested resources, such as books on authors:

{% raw %}
```jsx
// in src/App.jss
import { Admin, Resource, ListGuesser, EditGuesser } from 'react-admin';
import { Route } from "react-router-dom";

const App = () => (
<Admin dataProvider={dataProvider}>
<Resource name="authors" list={ListGuesser} edit={EditGuesser}>
<Route path=":authorId/books" element={<BookList />} />
</Resource>
</Admin>
);

// in src/BookList.jss
import { useParams } from 'react-router-dom';
import { List, Datagrid, TextField } from 'react-admin';

const BookList = () => {
const { authorId } = useParams();
return (
<List resource="books" filter={{ authorId }}>
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="title" />
<TextField source="year" />
</Datagrid>
</List>
);
};
```
{% endraw %}

**Tip**: Custom routes can be [a `<Navigate>` route](https://reactrouter.com/docs/en/v6/api#navigate), too.
**Tip**: In the above example, the `resource="books"` prop is required in `<List>` because the `ResourceContext` defaults to `authors` inside the `<Resource name="authors">`.

Check [the `<Resource>` element documentation](./Resource.md#children) for more information.
Loading

0 comments on commit b25283a

Please sign in to comment.