react-admin ≥ 4.3.0

ra-navigation

Complex admins with many resources need to organize their pages in a tree structure, and provide navigation widgets to help their users find their way in that structure. ra-navigation offers specialized React components to implement a breadcrumb, menu hierarchies, and sliding menu panels, together with hooks to handle the user location.

A video showing the MenuItem component in action

Test it live in the Enterprise Edition Storybook and in the e-commerce demo.

Installation

npm install --save @react-admin/ra-navigation
# or
yarn add @react-admin/ra-navigation

Tip: ra-navigation is part of the React-Admin Enterprise Edition, and hosted in a private npm registry. You need to subscribe to one of the Enterprise Edition plans to access this package.

Concepts

ra-navigation introduces the concept of application location (AppLocation) which is distinct from the browser location. This distinction is important as it allows to structure the navigation UI independently of the routes (e.g. having multiple resources under the same section, like Catalog / Products and Catalog / Categories).

Each page in a react-admin application can define its app location as a string via a custom hook. ra-navigation stores this string in a React context. UI components use that context to display navigation information and controls (menu, breadcrumb, or your custom components) in a consistent way. Pages can also provide data related to the location, such as the current record for example.

By default, standard react-admin views such as List, Edit, Create and Show define their app location:

  • List: [resource].list
  • Create: [resource].create
  • Edit: [resource].edit. It will also provide the current record
  • Show: [resource].show. It will also provide the current record

Developers can customize these defaults. For instance, a Categories List page can define its location as 'catalog.categories.list' (instead of the default 'categories.list'), i.e. as a child of the Catalog location in the app tree structure. That way, when users visit the Categories List Page, they see a <Breadcrumb> looking like the following:

Dashboard > Catalog > Categories

And they should see a <MultiLevelMenu> looking like the following:

> Dashboard
> Catalog
   > Categories
   > Products

Usage

Setting Up The <AppLocationContext>

To expose the app location to all react-admin components, wrap the <Layout> component inside an <AppLocationContext>:

import { AppLocationContext } from "@react-admin/ra-navigation";
import { Admin, Resource, Layout } from "react-admin";

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} />
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
  </Admin>
);
import { AppLocationContext } from "@react-admin/ra-navigation";
import { Admin, Resource, Layout } from "react-admin";

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} />
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
  </Admin>
);

You can now either leverage the provided components such as the <Breadcrumb> or <MultiLevelMenu>, or build your own.

Adding a Breadcrumb Path to Every Page

A breadcrumb

The <Breadcrumb> component renders a breadcrumb path. It must be a descendent of <AppLocationContext>, as it reads the app location from it.

To add a breadcrumb path to every page, add a <Breadcrumb> component in the layout:

import { AppLocationContext, Breadcrumb } from "@react-admin/ra-navigation";
import { Admin, Resource, Layout } from "react-admin";

const MyLayout = ({ children, ...rest }) => (
  <AppLocationContext>
    <Layout {...rest}>
      <Breadcrumb />
      {children}
    </Layout>
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
  </Admin>
);
import { AppLocationContext, Breadcrumb } from "@react-admin/ra-navigation";
import { Admin, Resource, Layout } from "react-admin";

const MyLayout = ({ children, ...rest }) => (
  <AppLocationContext>
    <Layout {...rest}>
      <Breadcrumb />
      {children}
    </Layout>
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
  </Admin>
);

By default, the <Breadcrumb /> item does not render anything. It's the job of its children (<BreadcrumbItem> elements) to render the breadcrumb path according to the app location, a bit like <Route> components render their children when they match the current browser location.

For instance, here is a component capable of rendering the breadcrumb path for two different resources:

import {
  AppLocationContext,
  Breadcrumb,
  BreadcrumbItem,
} from "@react-admin/ra-navigation";

const MyBreadcrumb = () => (
  <Breadcrumb>
    <BreadcrumbItem name="posts" label="Posts" to="/posts">
      <BreadcrumbItem
        name="edit"
        label={({ record }) => `Edit #${record.id}`}
        to={({ record }) => `/posts/${record.id}`}
      />
      <BreadcrumbItem
        name="show"
        label={({ record }) => `Show #${record.id}`}
        to={({ record }) => `/posts/${record.id}/show`}
      />
      <BreadcrumbItem name="create" label="Create" to="/posts/create" />
    </BreadcrumbItem>
    <BreadcrumbItem name="comments" label="Comments" to="/comments">
      <BreadcrumbItem
        name="edit"
        label={({ record }) => `Edit #${record.id}`}
        to={({ record }) => `/comments/${record.id}`}
      />
      <BreadcrumbItem
        name="show"
        label={({ record }) => `Show #${record.id}`}
        to={({ record }) => `/comments/${record.id}/show`}
      />
      <BreadcrumbItem name="create" label="Create" to="/comments/create" />
    </BreadcrumbItem>
  </Breadcrumb>
);

const MyLayout = ({ children, ...rest }) => (
  <AppLocationContext>
    <Layout {...rest}>
      <MyBreadcrumb />
      {children}
    </Layout>
  </AppLocationContext>
);
import {
  AppLocationContext,
  Breadcrumb,
  BreadcrumbItem,
} from "@react-admin/ra-navigation";

const MyBreadcrumb = () => (
  <Breadcrumb>
    <BreadcrumbItem name="posts" label="Posts" to="/posts">
      <BreadcrumbItem
        name="edit"
        label={({ record }) => `Edit #${record.id}`}
        to={({ record }) => `/posts/${record.id}`}
      />
      <BreadcrumbItem
        name="show"
        label={({ record }) => `Show #${record.id}`}
        to={({ record }) => `/posts/${record.id}/show`}
      />
      <BreadcrumbItem name="create" label="Create" to="/posts/create" />
    </BreadcrumbItem>
    <BreadcrumbItem name="comments" label="Comments" to="/comments">
      <BreadcrumbItem
        name="edit"
        label={({ record }) => `Edit #${record.id}`}
        to={({ record }) => `/comments/${record.id}`}
      />
      <BreadcrumbItem
        name="show"
        label={({ record }) => `Show #${record.id}`}
        to={({ record }) => `/comments/${record.id}/show`}
      />
      <BreadcrumbItem name="create" label="Create" to="/comments/create" />
    </BreadcrumbItem>
  </Breadcrumb>
);

const MyLayout = ({ children, ...rest }) => (
  <AppLocationContext>
    <Layout {...rest}>
      <MyBreadcrumb />
      {children}
    </Layout>
  </AppLocationContext>
);

It will display respectively:

  • "Posts" on Post List
  • "Posts / Show #1" on Post Show with id = 1
  • "Posts / Edit #1" on Post Edit with id = 1
  • "Posts / Create" on Post Create

As building breadcrumbs for resources is a common use case, ra-navigation provides a component that does the same for every resource registered as child of the <Admin> component. It's called <ResourceBreadcrumbItems>:

import {
  Breadcrumb,
  ResourceBreadcrumbItems,
} from "@react-admin/ra-navigation";

const MyBreadcrumb = () => (
  <Breadcrumb>
    <ResourceBreadcrumbItems />
  </Breadcrumb>
);
import {
  Breadcrumb,
  ResourceBreadcrumbItems,
} from "@react-admin/ra-navigation";

const MyBreadcrumb = () => (
  <Breadcrumb>
    <ResourceBreadcrumbItems />
  </Breadcrumb>
);

The ra-enterprise package exports an alternative <Layout>, which contains a pre-configured <Breadcrumb> that renders breacrumb paths for all resources.

Setting the Dashboard Page as the Root Item

If the app has a home page, you can automatically set the root of the Breadcrumb to this page in three possible ways:

  1. By passing the hasDashboard prop to the <AppLocationContext>
const MyLayout = (props) => (
  <AppLocationContext hasDashboard>
    <Layout {...props} />
  </AppLocationContext>
);
const MyLayout = (props) => (
  <AppLocationContext hasDashboard>
    <Layout {...props} />
  </AppLocationContext>
);
  1. By passing the dashboard prop to the <Breadcrumb> component:
const MyBreadcrumb = () => (
  <Breadcrumb dashboard={dashboard}>
    <ResourceBreadcrumbItems />
  </Breadcrumb>
);
const MyBreadcrumb = () => (
  <Breadcrumb dashboard={dashboard}>
    <ResourceBreadcrumbItems />
  </Breadcrumb>
);
  1. By passing a hasDashboard prop to the <Breadcrumb> component:
const MyBreadcrumb = () => (
  <Breadcrumb hasDashboard>
    <ResourceBreadcrumbItems />
  </Breadcrumb>
);
const MyBreadcrumb = () => (
  <Breadcrumb hasDashboard>
    <ResourceBreadcrumbItems />
  </Breadcrumb>
);

By doing this, the breadcrumb will now show respectively:

  • "Dashboard / Posts" on Post List
  • "Dashboard / Posts / Show #1" on Post Show with id = 1
  • "Dashboard / Posts / Edit #1" on Post Edit with id = 1
  • "Dashboard / Posts / Create" on Post Create

Adding Custom Breadcrumb Items With The <BreadcrumbItem> Component

It's also possible to define a breadcrumb path manually, by adding <BreadcrumbItem> children to <Breadcrumb>. This allows custom routes to have their own breacrumb path, too.

import React from "react";
import {
  AppLocationContext,
  Breadcrumb,
  ResourceBreadcrumbItems,
  useDefineAppLocation,
} from "@react-admin/ra-navigation";
import { Admin, Resource, Layout, List } from "react-admin";
import { Route } from "react-router-dom";

const UserPreferences = () => {
  useDefineAppLocation("myhome.user.preferences");
  return <span>My Preferences</span>;
};

const routes = [
  <Route exact path="/user/preferences" component={UserPreferences} />,
];

const MyBreadcrumb = () => (
  <Breadcrumb>
    <ResourceBreadcrumbItems />
    <BreadcrumbItem name="myhome" label="Home">
      <BreadcrumbItem name="user" label="User">
        <BreadcrumbItem name="preferences" label="Preferences" />
        {/* // It also supports translation keys */}
        <BreadcrumbItem
          name="preferences"
          label="myapp.breadcrumb.preferences"
        />
      </BreadcrumbItem>
    </BreadcrumbItem>
  </Breadcrumb>
);

const MyLayout = ({ children, ...rest }) => (
  <AppLocationContext hasDashboard={!!props.dashboard}>
    <Layout {...rest}>
      <MyBreadcrumb />
      {children}
    </Layout>
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} customRoutes={routes} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
  </Admin>
);
import React from "react";
import {
  AppLocationContext,
  Breadcrumb,
  ResourceBreadcrumbItems,
  useDefineAppLocation,
} from "@react-admin/ra-navigation";
import { Admin, Resource, Layout } from "react-admin";
import { Route } from "react-router-dom";

const UserPreferences = () => {
  useDefineAppLocation("myhome.user.preferences");
  return <span>My Preferences</span>;
};

const routes = [
  <Route exact path="/user/preferences" component={UserPreferences} />,
];

const MyBreadcrumb = () => (
  <Breadcrumb>
    <ResourceBreadcrumbItems />
    <BreadcrumbItem name="myhome" label="Home">
      <BreadcrumbItem name="user" label="User">
        <BreadcrumbItem name="preferences" label="Preferences" />
        {/* // It also supports translation keys */}
        <BreadcrumbItem
          name="preferences"
          label="myapp.breadcrumb.preferences"
        />
      </BreadcrumbItem>
    </BreadcrumbItem>
  </Breadcrumb>
);

const MyLayout = ({ children, ...rest }) => (
  <AppLocationContext hasDashboard={!!props.dashboard}>
    <Layout {...rest}>
      <MyBreadcrumb />
      {children}
    </Layout>
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} customRoutes={routes} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
  </Admin>
);

The displayed path on the custom page rendered at "/user/preferences" will be "Home / User / Preferences".

Tip:: We used myhome in this exemple and not home because it is a reserved word used for the Dashboard page when it exists.

Overriding the Resource Breadcrumb Items

In some cases, it's useful to override the default resource breadcrumb path, e.g. to add a custom label instead of "Show #1", "Edit #1", ...

This can be done by disabling the concerned resources in the <ResourceBreadcrumbItems resources> prop, and declaring them manually.

import React from "react";
import {
  AppLocationContext,
  Breadcrumb,
  ResourceBreadcrumbItems,
} from "@react-admin/ra-navigation";
import { Admin, Resource, Layout, useCreatePath, List } from "react-admin";

const MyBreadcrumb = ({ children, ...props }) => {
  const createPath = useCreatePath();

  return (
    <Breadcrumb>
      {/* no ResourceBreadcrumbItem for the 'posts' resource */}
      <ResourceBreadcrumbItems resources={["comments", "tags"]} />
      {/* we define it manually */}
      <BreadcrumbItem name="posts" label="Posts">
        <BreadcrumbItem
          name="edit"
          label={({ record }) => `Edit "${record.title}"`}
          to={({ record }) =>
            record &&
            createPath({
              type: "edit",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem
          name="show"
          label={({ record }) => record.title}
          to={({ record }) =>
            record &&
            createPath({
              type: "show",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem name="list" label="My Post List" />
        <BreadcrumbItem name="create" label="Let's write a Post!" />
      </BreadcrumbItem>
    </Breadcrumb>
  );
};

const MyLayout = ({ children, ...rest }) => (
  <AppLocationContext hasDashboard={!!props.dashboard}>
    <Layout {...rest}>
      <MyBreadcrumb />
      {children}
    </Layout>
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
    <Resource name="comments" list={CommentList} />
    <Resource name="tags" list={TagList} />
  </Admin>
);
import React from "react";
import {
  AppLocationContext,
  Breadcrumb,
  ResourceBreadcrumbItems,
} from "@react-admin/ra-navigation";
import { Admin, Resource, Layout, useCreatePath } from "react-admin";

const MyBreadcrumb = ({ children, ...props }) => {
  const createPath = useCreatePath();

  return (
    <Breadcrumb>
      {/* no ResourceBreadcrumbItem for the 'posts' resource */}
      <ResourceBreadcrumbItems resources={["comments", "tags"]} />
      {/* we define it manually */}
      <BreadcrumbItem name="posts" label="Posts">
        <BreadcrumbItem
          name="edit"
          label={({ record }) => `Edit "${record.title}"`}
          to={({ record }) =>
            record &&
            createPath({
              type: "edit",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem
          name="show"
          label={({ record }) => record.title}
          to={({ record }) =>
            record &&
            createPath({
              type: "show",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem name="list" label="My Post List" />
        <BreadcrumbItem name="create" label="Let's write a Post!" />
      </BreadcrumbItem>
    </Breadcrumb>
  );
};

const MyLayout = ({ children, ...rest }) => (
  <AppLocationContext hasDashboard={!!props.dashboard}>
    <Layout {...rest}>
      <MyBreadcrumb />
      {children}
    </Layout>
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
    <Resource name="comments" list={CommentList} />
    <Resource name="tags" list={TagList} />
  </Admin>
);

Grouping Menus Into Submenus

MultiLevelMenu

When a React-admin application grows significantly, the default menu might not be the best solution. The <MultiLevelMenu> can help unclutter the navigation: it renders a menu with an infinite number of levels and sub menus. Menu Items that are not at the top level are rendered inside a collapsible panel.

A video showing the MenuItem component in action

The <MultiLevelMenu> accepts <MultiLevelMenu.Item> components as its children. They are very similar to the default <MenuItemLink> from react-admin, except that they accept other <MultiLevelMenu.Item> as their children. In order to use it, the layout of the app must be inside a <AppLocationContext>.

The <MultiLevelMenu.Item> component accepts a name, a label and an optional icon prop.

import { Admin, Layout, Resource } from "react-admin";
import { AppLocationContext, MultiLevelMenu } from "@react-admin/ra-navigation";
import { Dashboard } from "./Dashboard";
import { dataProvider } from "./dataProvider";
import { SongList } from "./songs";
import { ArtistList } from "./artists";

const MyMenu = () => (
  <MultiLevelMenu>
    <MultiLevelMenu.Item name="dashboard" to="/" exact label="Dashboard" />
    <MultiLevelMenu.Item name="songs" to="/songs" label="Songs" />
    {/* The empty filter is required to avoid falling back to the previously set filter */}
    <MultiLevelMenu.Item
      name="artists"
      to={"/artists?filter={}"}
      label="Artists"
    >
      <MultiLevelMenu.Item
        name="artists.rock"
        to={'/artists?filter={"type":"Rock"}'}
        label="Rock"
      >
        <MultiLevelMenu.Item
          name="artists.rock.pop"
          to={'/artists?filter={"type":"Pop Rock"}'}
          label="Pop Rock"
        />
        <MultiLevelMenu.Item
          name="artists.rock.folk"
          to={'/artists?filter={"type":"Folk Rock"}'}
          label="Folk Rock"
        />
      </MultiLevelMenu.Item>
      <MultiLevelMenu.Item
        name="artists.jazz"
        to={'/artists?filter={"type":"Jazz"}'}
        label="Jazz"
      >
        <MultiLevelMenu.Item
          name="artists.jazz.rb"
          to={'/artists?filter={"type":"RB"}'}
          label="R&B"
        />
      </MultiLevelMenu.Item>
    </MultiLevelMenu.Item>
  </MultiLevelMenu>
);

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} menu={MyMenu} />
  </AppLocationContext>
);

export const Basic = () => (
  <Admin dashboard={Dashboard} dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="songs" list={SongList} />
    <Resource name="artists" list={ArtistList} />
  </Admin>
);
import { Admin, Layout, Resource } from "react-admin";
import { AppLocationContext, MultiLevelMenu } from "@react-admin/ra-navigation";
import { Dashboard } from "./Dashboard";
import { dataProvider } from "./dataProvider";
import { SongList } from "./songs";
import { ArtistList } from "./artists";

const MyMenu = () => (
  <MultiLevelMenu>
    <MultiLevelMenu.Item name="dashboard" to="/" exact label="Dashboard" />
    <MultiLevelMenu.Item name="songs" to="/songs" label="Songs" />
    {/* The empty filter is required to avoid falling back to the previously set filter */}
    <MultiLevelMenu.Item
      name="artists"
      to={"/artists?filter={}"}
      label="Artists"
    >
      <MultiLevelMenu.Item
        name="artists.rock"
        to={'/artists?filter={"type":"Rock"}'}
        label="Rock"
      >
        <MultiLevelMenu.Item
          name="artists.rock.pop"
          to={'/artists?filter={"type":"Pop Rock"}'}
          label="Pop Rock"
        />
        <MultiLevelMenu.Item
          name="artists.rock.folk"
          to={'/artists?filter={"type":"Folk Rock"}'}
          label="Folk Rock"
        />
      </MultiLevelMenu.Item>
      <MultiLevelMenu.Item
        name="artists.jazz"
        to={'/artists?filter={"type":"Jazz"}'}
        label="Jazz"
      >
        <MultiLevelMenu.Item
          name="artists.jazz.rb"
          to={'/artists?filter={"type":"RB"}'}
          label="R&B"
        />
      </MultiLevelMenu.Item>
    </MultiLevelMenu.Item>
  </MultiLevelMenu>
);

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} menu={MyMenu} />
  </AppLocationContext>
);

export const Basic = () => (
  <Admin dashboard={Dashboard} dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="songs" list={SongList} />
    <Resource name="artists" list={ArtistList} />
  </Admin>
);

Using a Mega Menu

A video showing the MenuItem component in action

Sometimes, even menu with sub menus are not enough to organize the navigation. ra-navigation offers an alternative UI for that case: a vertical bar with small items, where the menu label renders underneath the icon. Clicking on any of those items opens a panel containing as many navigation links as you like, laid out as you wish.

To create a menu with that UI, use the <IconMenu> component. As children, use <IconMenu.Item> elements to define the icon, label and target of the element. To define the content of the panel, add children to the <IconMenu.Item> element.

import { Admin, Layout, Resource } from "react-admin";
import {
  AppLocationContext,
  MenuItemList,
  MenuItemNode,
  IconMenu,
  theme,
} from "@react-admin/ra-navigation";

import { Dashboard } from "./Dashboard";
import { dataProvider } from "./dataProvider";
import { SongList } from "./songs";
import { ArtistList } from "./artists";

const MyMenu = () => (
  <IconMenu variant="categories">
    <IconMenu.Item
      name="dashboard"
      to="/"
      exact
      label="Dashboard"
      icon={<DashboardIcon />}
    />
    <IconMenu.Item
      name="songs"
      icon={<MusicIcon />}
      to="/songs"
      label="Songs"
    />
    {/* The empty filter is required to avoid falling back to the previously set filter */}
    <IconMenu.Item
      name="artists"
      to={"/artists?filter={}"}
      label="Artists"
      icon={<PeopleIcon />}
    >
      <CardContent>
        {/* to get consistent spacing */}
        <Typography variant="h3" gutterBottom>
          Artist Categories
        </Typography>
        {/* Note that we must wrap our MenuItemNode components in a MenuItemList */}
        <MenuItemList>
          <MenuItemNode
            name="artists.rock"
            to={'/artists?filter={"type":"rock"}'}
            label="Rock"
          />
          <MenuItemNode
            name="artists.jazz"
            to={'/artists?filter={"type":"jazz"}'}
            label="Jazz"
          />
          <MenuItemNode
            name="artists.classical"
            to={'/artists?filter={"type":"classical"}'}
            label="Rock"
          />
        </MenuItemList>
      </CardContent>
    </IconMenu.Item>
    <IconMenu.Item
      name="configuration"
      to="/"
      exact
      label="Configuration"
      icon={<SettingsIcon />}
      sx={{ marginTop: "auto" }}
    />
  </IconMenu>
);

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} menu={MyMenu} />
  </AppLocationContext>
);

export const MyApp = () => (
  <Admin
    dataProvider={dataProvider}
    layout={MyLayout}
    dashboard={Dashboard}
    /* Apply the theme provided by ra-navigation */
    theme={theme}
  >
    <Resource name="songs" list={SongList} />
    <Resource name="artists" list={ArtistList} />
  </Admin>
);
import { Admin, Layout, Resource } from "react-admin";
import {
  AppLocationContext,
  MenuItemList,
  MenuItemNode,
  IconMenu,
  theme,
} from "@react-admin/ra-navigation";

import { Dashboard } from "./Dashboard";
import { dataProvider } from "./dataProvider";
import { SongList } from "./songs";
import { ArtistList } from "./artists";

const MyMenu = () => (
  <IconMenu variant="categories">
    <IconMenu.Item
      name="dashboard"
      to="/"
      exact
      label="Dashboard"
      icon={<DashboardIcon />}
    />
    <IconMenu.Item
      name="songs"
      icon={<MusicIcon />}
      to="/songs"
      label="Songs"
    />
    {/* The empty filter is required to avoid falling back to the previously set filter */}
    <IconMenu.Item
      name="artists"
      to={"/artists?filter={}"}
      label="Artists"
      icon={<PeopleIcon />}
    >
      <CardContent>
        {/* to get consistent spacing */}
        <Typography variant="h3" gutterBottom>
          Artist Categories
        </Typography>
        {/* Note that we must wrap our MenuItemNode components in a MenuItemList */}
        <MenuItemList>
          <MenuItemNode
            name="artists.rock"
            to={'/artists?filter={"type":"rock"}'}
            label="Rock"
          />
          <MenuItemNode
            name="artists.jazz"
            to={'/artists?filter={"type":"jazz"}'}
            label="Jazz"
          />
          <MenuItemNode
            name="artists.classical"
            to={'/artists?filter={"type":"classical"}'}
            label="Rock"
          />
        </MenuItemList>
      </CardContent>
    </IconMenu.Item>
    <IconMenu.Item
      name="configuration"
      to="/"
      exact
      label="Configuration"
      icon={<SettingsIcon />}
      sx={{ marginTop: "auto" }}
    />
  </IconMenu>
);

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} menu={MyMenu} />
  </AppLocationContext>
);

export const MyApp = () => (
  <Admin
    dataProvider={dataProvider}
    layout={MyLayout}
    dashboard={Dashboard}
    /* Apply the theme provided by ra-navigation */
    theme={theme}
  >
    <Resource name="songs" list={SongList} />
    <Resource name="artists" list={ArtistList} />
  </Admin>
);

In order to adjust the size of the React-Admin <Sidebar> component according to the categories, you should either apply the theme provided by the @react-admin/ra-navigation package (as above), or merge it in your own custom theme.

import merge from "lodash/merge";
import { defaultTheme } from "react-admin";
import { ThemeOptions } from "@react-admin/ra-navigation";

export const theme: ThemeOptions = merge({}, defaultTheme, {
  sidebar: {
    width: 96,
    closedWidth: 48,
  },
  overrides: {
    RaSidebar: {
      fixed: {
        zIndex: 1200,
      },
    },
  },
});
import merge from "lodash/merge";
import { defaultTheme } from "react-admin";

export const theme = merge({}, defaultTheme, {
  sidebar: {
    width: 96,
    closedWidth: 48,
  },
  overrides: {
    RaSidebar: {
      fixed: {
        zIndex: 1200,
      },
    },
  },
});

Tip: With <IconMenu />, labels may disappear when the sidebar is in reduced mode. This is because of the internal workings of react-admin. That's why we recommend implementing your own <AppBar />, and hide the Hamburger Button. <IconMenu /> is thin enough not to interfere with the navigation anyway.

<AppLocationContext>

To define or retrieve the current App Location, your React components must be located inside an <AppLocationContext>. This component creates a React context dedicated to the user App Location. It must be contained in the <Admin> component (in order to read the registered resources), but it must also wrap all the UI components (that may require access to the app location). So the best place to put it is as a wrapper to the Layout.

import { AppLocationContext } from "@react-admin/ra-navigation";
import { Admin, Resource, Layout } from "react-admin";

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} />
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
  </Admin>
);
import { AppLocationContext } from "@react-admin/ra-navigation";
import { Admin, Resource, Layout } from "react-admin";

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} />
  </AppLocationContext>
);

const App = () => (
  <Admin dataProvider={dataProvider} layout={MyLayout}>
    <Resource name="posts" list={PostList} />
  </Admin>
);

useAppLocationState

The useAppLocationState hook lets you access the current App Location. It also lets you define a new App Location. Its API is similar to that of the React.useState() hook.

Here is how to read the current app location:

import { useAppLocationState } from "@react-admin/ra-navigation";

const MySubComponent = (props) => {
  const [location] = useAppLocationState();

  return <span>{`Hey! You're on the ${location.path} location!`}</span>;
};
import { useAppLocationState } from "@react-admin/ra-navigation";

const MySubComponent = (props) => {
  const [location] = useAppLocationState();

  return <span>{`Hey! You're on the ${location.path} location!`}</span>;
};

To define the current app location:

const DensityTab = () => {
  const [_, setLocation] = useAppLocationState();

  useEffect(() => {
    setLocation("experiences.parameters.density");
  }, []);

  // return (/* ... */);
};
const DensityTab = () => {
  const [_, setLocation] = useAppLocationState();

  useEffect(() => {
    setLocation("experiences.parameters.density");
  }, []);

  // return (/* ... */);
};

You can also pass a context object as second argument to setLocation. This can be useful e.g to display the label of the current resource in a breadcrumb path.

const DensityTab = () => {
  const [_, setLocation] = useAppLocationState();

  useEffect(() => {
    setLocation("experiences.parameters.density", { density: 0.1 });
  }, []);

  // return (/* ... */);
};
const DensityTab = () => {
  const [_, setLocation] = useAppLocationState();

  useEffect(() => {
    setLocation("experiences.parameters.density", { density: 0.1 });
  }, []);

  // return (/* ... */);
};

So, you can retrieve it in another place...

const Breadcrumb = () => {
  const [location] = useAppLocationState();
  // location = { path: 'experiences.parameters.density', density: 0.1 }

  // return (/* ... */);
};
const Breadcrumb = () => {
  const [location] = useAppLocationState();
  // location = { path: 'experiences.parameters.density', density: 0.1 }

  // return (/* ... */);
};

Warning: The dashboard location is a reserved word used for the Dashboard page when it exists.

When no component in the page defined a custom app location, useAppLocationState() resolves the current App Location based on the current React-Admin path.

For instance, in a posts List page:

import { useAppLocationState } from "@react-admin/ra-navigation";
import { Admin, Resource } from "react-admin";

const PostList = () => {
  const [location] = useAppLocationState();

  // location is automatically resolved to "posts.list"
  // location = { path: "posts.list", values: {} }

  // return (/* ... */);
};

const App = () => (
  <Admin dataProvider={dataProvider} layout={LayoutWithAppLocationContext}>
    <Resource name="posts" list={PostList} />
  </Admin>
);
import { useAppLocationState } from "@react-admin/ra-navigation";
import { Admin, Resource } from "react-admin";

const PostList = () => {
  const [location] = useAppLocationState();

  // location is automatically resolved to "posts.list"
  // location = { path: "posts.list", values: {} }

  // return (/* ... */);
};

const App = () => (
  <Admin dataProvider={dataProvider} layout={LayoutWithAppLocationContext}>
    <Resource name="posts" list={PostList} />
  </Admin>
);

This also works for Edit and Show pages:

import {
  AppLocationContext,
  useAppLocationState,
} from "@react-admin/ra-navigation";
import { Admin, Resource } from "react-admin";

const AnotherComponent = () => {
  const [location] = useAppLocationState();

  // On Edit view
  // location = { path: "post.edit", values: { record: { id: 1, ...} } };

  // On Show view
  // location = { path: "post.show", values: { record: { id: 1, ...} } };
};

// const PostShow = () => (/* ... */);
// const PostEdit= () => (/* ... */);

const App = () => (
  <Admin dataProvider={dataProvider} layout={LayoutWithAppLocationContext}>
    <Resource name="posts" edit={PostEdit} show={PostShow} />
  </Admin>
);
import { useAppLocationState } from "@react-admin/ra-navigation";
import { Admin, Resource } from "react-admin";

const AnotherComponent = () => {
  const [location] = useAppLocationState();

  // On Edit view
  // location = { path: "post.edit", values: { record: { id: 1, ...} } };

  // On Show view
  // location = { path: "post.show", values: { record: { id: 1, ...} } };
};

// const PostShow = () => (/* ... */);
// const PostEdit= () => (/* ... */);

const App = () => (
  <Admin dataProvider={dataProvider} layout={LayoutWithAppLocationContext}>
    <Resource name="posts" edit={PostEdit} show={PostShow} />
  </Admin>
);

The record attribute is always passed for show and edit actions. This way, you can display the title of your post outside the show or edit component itself.

If any component in the tree defines a different location than the default, it will override that default.

useDefineAppLocation

Using useAppLocationState only to define the current location can be tedious. So, to avoid using a specific useEffect, you can rely on the useDefineAppLocation() which does exactly the same thing in less code.

import { useDefineAppLocation } from "@react-admin/ra-navigation";

const DensityTab = () => {
  useDefineAppLocation("experiences.parameters.density", { density: 0.1 });
  // return (/* ... */);
};
import { useDefineAppLocation } from "@react-admin/ra-navigation";

const DensityTab = () => {
  useDefineAppLocation("experiences.parameters.density", { density: 0.1 });
  // return (/* ... */);
};

useAppLocationMatcher

The useAppLocationMatcher hook returns a function that checks if the path that is passed as argument matches the current location path.

If the path matches, the match function returns the current location. If not, it returns null.

import { useAppLocationMatcher } from "@react-admin/ra-navigation";

function PostBreadcrumbItem() {
  const matcher = useAppLocationMatcher();
  const match = matcher("post.edit");
  if (!match) return null;
  return <>Post "{match.record.title}"</>;
}
import { useAppLocationMatcher } from "@react-admin/ra-navigation";

function PostBreadcrumbItem() {
  const matcher = useAppLocationMatcher();
  const match = matcher("post.edit");
  if (!match) return null;
  return <>Post "{match.record.title}"</>;
}

useResourceAppLocation

While the useAppLocationState let you access the current App Location, useResourceAppLocation gives you the app location from the react-admin "resource" perspective only.

That means it returns an AppLocation only if the current routes matches a React-Admin one (eg: "/songs/1" for song edition, "/songs" for songs listing, etc.). In other cases, it returns null.

This hook can be useful to override some "native" routes.

Example:

import React, { useEffect } from "react";

import {
  AppLocationContext,
  useAppLocationState,
  useResourceAppLocation,
} from "@react-admin/ra-navigation";

const SongsGrid = (props) => {
  const [, setLocation] = useAppLocationState();
  const resourceLocation = useResourceAppLocation();

  useEffect(() => {
    const { artist_id: artistId } = props.filterValues;
    if (typeof artistId !== "undefined") {
      // It'll change location and display "Filtered by artist X" in the breadcrumb
      setLocation("songs_by_artist.filter", { artistId });
    }
  }, [JSON.stringify({ resourceLocation, filters: props.filterValues })]);

  return (
    <Datagrid {...props}>
      <TextField source="title" />
      <ReferenceField source="artist_id" reference="artists">
        <TextField source="name" />
      </ReferenceField>
    </Datagrid>
  );
};

const songFilter = [
  <ReferenceInput alwaysOn source="artist_id" reference="artists">
    <SelectInput optionText="name" />
  </ReferenceInput>,
];

const SongList = () => (
  <List filters={songFilter}>
    <SongsGrid />
  </List>
);
import React, { useEffect } from "react";

import {
  useAppLocationState,
  useResourceAppLocation,
} from "@react-admin/ra-navigation";

const SongsGrid = (props) => {
  const [, setLocation] = useAppLocationState();
  const resourceLocation = useResourceAppLocation();

  useEffect(() => {
    const { artist_id: artistId } = props.filterValues;
    if (typeof artistId !== "undefined") {
      // It'll change location and display "Filtered by artist X" in the breadcrumb
      setLocation("songs_by_artist.filter", { artistId });
    }
  }, [JSON.stringify({ resourceLocation, filters: props.filterValues })]);

  return (
    <Datagrid {...props}>
      <TextField source="title" />
      <ReferenceField source="artist_id" reference="artists">
        <TextField source="name" />
      </ReferenceField>
    </Datagrid>
  );
};

const songFilter = [
  <ReferenceInput alwaysOn source="artist_id" reference="artists">
    <SelectInput optionText="name" />
  </ReferenceInput>,
];

const SongList = () => (
  <List filters={songFilter}>
    <SongsGrid />
  </List>
);

A component displaying a breadcumb based on the current AppLocation.

A breadcrumb

Add the breadcrumb component directly to your custom layout or through the extended <Layout> available in the ra-enterprise module using its breadcrumb property.

Accepts the following props:

  • className: Optional. The class name to apply to its root element
  • separator: Optional. A custom sepator to put inside the items. Either a string or a function returning a string.
  • dashboard: Optional. The component you might have specified on the <Admin> component. Used to determine whether a dashboard is present. If it is, the dashboard item will be added in the breadcrumb on every page.
  • hasDashboard: Optional. A boolean indicating whether a dashboard is present. If it is, the dashboard item will be added in the breadcrumb on every page.
import { useCreatePath } from "react-admin";
import {
  Breadcrumb,
  ResourceBreadcrumbItems,
  BreadcrumbItem,
} from "@react-admin/ra-navigation";

// default breadcrumb based on resources, using the dashboard as root
const MyBreadcrumb = ({ children, dashboard, ...props }) => (
  <Breadcrumb dashboard={dashboard}>
    <ResourceBreadcrumbItems />
  </Breadcrumb>
);

// custom breadcrumb
const MyCustomBreadcrumb = ({ children, ...props }) => {
  const createPath = useCreatePath();

  return (
    <Breadcrumb>
      <ResourceBreadcrumbItems resources={["otherResources"]} />
      <BreadcrumbItem name="posts" label="Posts">
        <BreadcrumbItem
          name="edit"
          label={({ record }) => `Edit "${record.title}"`}
          to={({ record }) =>
            record &&
            createPath({
              type: "edit",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem
          name="show"
          label={({ record }) => record.title}
          to={({ record }) =>
            record &&
            createPath({
              type: "show",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem name="list" label="My Post List" />
        <BreadcrumbItem name="create" label="Let's write a Post!" />
      </BreadcrumbItem>
    </Breadcrumb>
  );
};
import { useCreatePath } from "react-admin";
import {
  Breadcrumb,
  ResourceBreadcrumbItems,
  BreadcrumbItem,
} from "@react-admin/ra-navigation";

// default breadcrumb based on resources, using the dashboard as root
const MyBreadcrumb = ({ children, dashboard, ...props }) => (
  <Breadcrumb dashboard={dashboard}>
    <ResourceBreadcrumbItems />
  </Breadcrumb>
);

// custom breadcrumb
const MyCustomBreadcrumb = ({ children, ...props }) => {
  const createPath = useCreatePath();

  return (
    <Breadcrumb>
      <ResourceBreadcrumbItems resources={["otherResources"]} />
      <BreadcrumbItem name="posts" label="Posts">
        <BreadcrumbItem
          name="edit"
          label={({ record }) => `Edit "${record.title}"`}
          to={({ record }) =>
            record &&
            createPath({
              type: "edit",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem
          name="show"
          label={({ record }) => record.title}
          to={({ record }) =>
            record &&
            createPath({
              type: "show",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem name="list" label="My Post List" />
        <BreadcrumbItem name="create" label="Let's write a Post!" />
      </BreadcrumbItem>
    </Breadcrumb>
  );
};

A component displaying a single breadcumb item if the app loaction matches its name.

A breadcrumb item

Accepts the following props:

  • name: Required. The name of this item which will be used to infer its full path.
  • label: Required. The label to display for this item. It accepts translation keys.
  • path: Internal prop used to build the item full path.
  • to: Optional. The react-router path to redirect to.
import { useCreatePath } from "react-admin";
import {
  Breadcrumb,
  ResourceBreadcrumbItems,
  BreadcrumbItem,
} from "@react-admin/ra-navigation";

// custom breadcrumb
const MyBreadcrumb = ({ children, ...props }) => {
  const createPath = useCreatePath();

  return (
    <Breadcrumb>
      <ResourceBreadcrumbItems resources={["otherResources"]} />
      <BreadcrumbItem name="posts" label="Posts">
        <BreadcrumbItem
          name="edit"
          label={({ record }) => `Edit "${record.title}"`}
          to={({ record }) =>
            record &&
            createPath({
              type: "edit",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem
          name="show"
          label={({ record }) => record.title}
          to={({ record }) =>
            record &&
            createPath({
              type: "show",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem name="list" label="My Post List" />
        <BreadcrumbItem name="create" label="Let's write a Post!" />
      </BreadcrumbItem>
    </Breadcrumb>
  );
};
import { useCreatePath } from "react-admin";
import {
  Breadcrumb,
  ResourceBreadcrumbItems,
  BreadcrumbItem,
} from "@react-admin/ra-navigation";

// custom breadcrumb
const MyBreadcrumb = ({ children, ...props }) => {
  const createPath = useCreatePath();

  return (
    <Breadcrumb>
      <ResourceBreadcrumbItems resources={["otherResources"]} />
      <BreadcrumbItem name="posts" label="Posts">
        <BreadcrumbItem
          name="edit"
          label={({ record }) => `Edit "${record.title}"`}
          to={({ record }) =>
            record &&
            createPath({
              type: "edit",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem
          name="show"
          label={({ record }) => record.title}
          to={({ record }) =>
            record &&
            createPath({
              type: "show",
              resource: "songs",
              id: record.id,
            })
          }
        />
        <BreadcrumbItem name="list" label="My Post List" />
        <BreadcrumbItem name="create" label="Let's write a Post!" />
      </BreadcrumbItem>
    </Breadcrumb>
  );
};

<DashboardBreadcrumbItem>

A version of the <BreadcrumbItem> dedicated to the dashboard.

Accepts the following props:

  • name: Optional. The name of this item which will be used to infer its full path.
  • label: Optional. The label to display for this item. It accepts translation keys.
  • path: Internal prop used to build the item full path.
  • to: Optional. The react-router path to redirect to.

<ResourceBreadcrumbItems>

A component that renders <BreadcumbItem> elements for List, Edit, Show and Create views for every registered resource.

Accepts the following props:

  • resources: Optional. The resources for which to infer the breadcrumb items. You don't need to specify this prop if you want all resources to be handled.

<MultiLevelMenu>

A menu to use instead of the default react-admin menu, which supports multi level items. Users can show/hide child menu items like a classic collapsible menu.

MultiLevelMenu

The <MultiLevelMenu> accepts the following props:

  • initialOpen: Whether the menu items with sub menus should be open initialy. Has no effect if using the categories variant. Defaults to false.

  • openItemList : Optional. List of names of menu items that should be opened by default. If the menu item to be opened is nested, you have to fill in the name of all the parent items. Ex: ['artists', 'artists.rock', 'artists.rock.pop']

import { MultiLevelMenu } from "@react-admin/ra-navigation";

import { Dashboard } from "./Dashboard";
import { dataProvider } from "./dataProvider";
import { SongList } from "./songs";
import { ArtistList } from "./artists";

const MyMenu = () => (
  <MultiLevelMenu>
    <MultiLevelMenu.Item name="dashboard" to="/" exact label="Dashboard" />
    <MultiLevelMenu.Item name="songs" to="/songs" label="Songs" />
    {/* The empty filter is required to avoid falling back to the previously set filter */}
    <MultiLevelMenu.Item
      name="artists"
      to={"/artists?filter={}"}
      label="Artists"
    >
      <MultiLevelMenu.Item
        name="artists.rock"
        to={'/artists?filter={"type":"Rock"}'}
        label="Rock"
      >
        <MultiLevelMenu.Item
          name="artists.rock.pop"
          to={'/artists?filter={"type":"Pop Rock"}'}
          label="Pop Rock"
        />
        <MultiLevelMenu.Item
          name="artists.rock.folk"
          to={'/artists?filter={"type":"Folk Rock"}'}
          label="Folk Rock"
        />
      </MultiLevelMenu.Item>
      <MultiLevelMenu.Item
        name="artists.jazz"
        to={'/artists?filter={"type":"Jazz"}'}
        label="Jazz"
      >
        <MultiLevelMenu.Item
          name="artists.jazz.rb"
          to={'/artists?filter={"type":"RB"}'}
          label="R&B"
        />
      </MultiLevelMenu.Item>
    </MultiLevelMenu.Item>
  </MultiLevelMenu>
);
import { MultiLevelMenu } from "@react-admin/ra-navigation";

const MyMenu = () => (
  <MultiLevelMenu>
    <MultiLevelMenu.Item name="dashboard" to="/" exact label="Dashboard" />
    <MultiLevelMenu.Item name="songs" to="/songs" label="Songs" />
    {/* The empty filter is required to avoid falling back to the previously set filter */}
    <MultiLevelMenu.Item
      name="artists"
      to={"/artists?filter={}"}
      label="Artists"
    >
      <MultiLevelMenu.Item
        name="artists.rock"
        to={'/artists?filter={"type":"Rock"}'}
        label="Rock"
      >
        <MultiLevelMenu.Item
          name="artists.rock.pop"
          to={'/artists?filter={"type":"Pop Rock"}'}
          label="Pop Rock"
        />
        <MultiLevelMenu.Item
          name="artists.rock.folk"
          to={'/artists?filter={"type":"Folk Rock"}'}
          label="Folk Rock"
        />
      </MultiLevelMenu.Item>
      <MultiLevelMenu.Item
        name="artists.jazz"
        to={'/artists?filter={"type":"Jazz"}'}
        label="Jazz"
      >
        <MultiLevelMenu.Item
          name="artists.jazz.rb"
          to={'/artists?filter={"type":"RB"}'}
          label="R&B"
        />
      </MultiLevelMenu.Item>
    </MultiLevelMenu.Item>
  </MultiLevelMenu>
);

In addition to the props of react-router <NavLink> and those of material-ui <ListItem>, <MultiLevelMenu.Item> accepts the following props:

  • icon: Optional. An icon element to display in front of the item
  • name: Required: The name of the item. Used to manage its open/closed state.
  • label: Optional. The label to display for this item. Accepts translation keys.

Tip: You can omit the to property for <MultiLevelMenu.Item> that have a child menu item.

<IconMenu>

An alternative menu component rendering a reduced menu bar with a sliding panel for second-level menu items. This menu UI saves a lot of screen real estate, and allows for sub menus of any level of complexity.

A video showing the MenuItem component in action

import { Admin, Layout, Resource } from "react-admin";
import {
  AppLocationContext,
  IconMenu,
  MenuItemList,
  MenuItemNode,
  theme,
} from "@react-admin/ra-navigation";

import { Dashboard } from "./Dashboard";
import { dataProvider } from "./dataProvider";
import { SongList } from "./songs";
import { ArtistList } from "./artists";

const MyMenu = () => (
  <IconMenu>
    <IconMenu.Item
      name="dashboard"
      to="/"
      exact
      label="Dashboard"
      icon={<DashboardIcon />}
    />
    <IconMenu.Item
      name="songs"
      icon={<MusicIcon />}
      to="/songs"
      label="Songs"
    />
    <IconMenu.Item
      name="artists"
      to={"/artists?filter={}"}
      label="Artists"
      icon={<PeopleIcon />}
    >
      <CardContent>
        {/* to get consistent spacing */}
        <Typography variant="h3" gutterBottom>
          Artist Categories
        </Typography>
        {/* Note that we must wrap our MenuItemNode components in a MenuItemList */}
        <MenuItemList>
          <MenuItemNode
            name="artists.rock"
            to={'/artists?filter={"type":"rock"}'}
            label="Rock"
          />
          <MenuItemNode
            name="artists.jazz"
            to={'/artists?filter={"type":"jazz"}'}
            label="Jazz"
          />
          <MenuItemNode
            name="artists.classical"
            to={'/artists?filter={"type":"classical"}'}
            label="Rock"
          />
        </MenuItemList>
      </CardContent>
    </IconMenu.Item>
    <IconMenu.Item
      name="configuration"
      to="/"
      exact
      label="Configuration"
      icon={<SettingsIcon />}
      sx={{ marginTop: "auto" }}
    />
  </IconMenu>
);

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} menu={MyMenu} />
  </AppLocationContext>
);

export const MyApp = () => (
  <Admin
    dataProvider={dataProvider}
    layout={MyLayout}
    dashboard={Dashboard}
    /* Apply the theme provided by ra-navigation */
    theme={theme}
  >
    <Resource name="songs" list={SongList} />
    <Resource name="artists" list={ArtistList} />
  </Admin>
);
import { Admin, Layout, Resource } from "react-admin";
import {
  AppLocationContext,
  IconMenu,
  MenuItemList,
  MenuItemNode,
  theme,
} from "@react-admin/ra-navigation";

import { Dashboard } from "./Dashboard";
import { dataProvider } from "./dataProvider";
import { SongList } from "./songs";
import { ArtistList } from "./artists";

const MyMenu = () => (
  <IconMenu>
    <IconMenu.Item
      name="dashboard"
      to="/"
      exact
      label="Dashboard"
      icon={<DashboardIcon />}
    />
    <IconMenu.Item
      name="songs"
      icon={<MusicIcon />}
      to="/songs"
      label="Songs"
    />
    <IconMenu.Item
      name="artists"
      to={"/artists?filter={}"}
      label="Artists"
      icon={<PeopleIcon />}
    >
      <CardContent>
        {/* to get consistent spacing */}
        <Typography variant="h3" gutterBottom>
          Artist Categories
        </Typography>
        {/* Note that we must wrap our MenuItemNode components in a MenuItemList */}
        <MenuItemList>
          <MenuItemNode
            name="artists.rock"
            to={'/artists?filter={"type":"rock"}'}
            label="Rock"
          />
          <MenuItemNode
            name="artists.jazz"
            to={'/artists?filter={"type":"jazz"}'}
            label="Jazz"
          />
          <MenuItemNode
            name="artists.classical"
            to={'/artists?filter={"type":"classical"}'}
            label="Rock"
          />
        </MenuItemList>
      </CardContent>
    </IconMenu.Item>
    <IconMenu.Item
      name="configuration"
      to="/"
      exact
      label="Configuration"
      icon={<SettingsIcon />}
      sx={{ marginTop: "auto" }}
    />
  </IconMenu>
);

const MyLayout = (props) => (
  <AppLocationContext>
    <Layout {...props} menu={MyMenu} />
  </AppLocationContext>
);

export const MyApp = () => (
  <Admin
    dataProvider={dataProvider}
    layout={MyLayout}
    dashboard={Dashboard}
    /* Apply the theme provided by ra-navigation */
    theme={theme}
  >
    <Resource name="songs" list={SongList} />
    <Resource name="artists" list={ArtistList} />
  </Admin>
);

In addition to the props of react-router <NavLink> and those of material-ui <ListItem>, <IconMenu.Item> accepts the following props:

  • icon: Optional. An icon element to display in front of the item
  • name: Required: The name of the item. Used to manage its open/closed state.
  • label: Optional. The label to display for this item. Accepts translation keys.

<IconMenuResources>

A <IconMenu> configured to render one menu item per resource, plus an item for the dashboard if it is present.

This component doesn't need any configuration as it reads the resources from the <Admin> context.

import { Layout } from 'react-admin';
import {
    IconMenuResources,
    AppLocationContext,
} from '@react-admin/ra-navigation';

export const MyLayout = props => (
    <AppLocationContext>
        <Layout {...props} menu={IconMenuResources} />
    </AppLocationContext>
);

This menu item component is the same as <MultiLevelMenu.Item>. When used in standalone, it must be a child of a <MenuItemList> component. It can have other <MenuItemNode> children.

import { MenuItemList, MenuItemNode } from '@react-admin/ra-navigation';
import { CardContent, Typography } from '@mui/material';

const MenuPopover = () => (
    <CardContent>
        <Typography variant="h3" gutterBottom>
            Artist Categories
        </Typography>
        <MenuItemList>
            <MenuItemNode
                name="artists.rock"
                to={'/artists?filter={"type":"rock"}'}
                label="Rock"
            />
            <MenuItemNode
                name="artists.jazz"
                to={'/artists?filter={"type":"jazz"}'}
                label="Jazz"
            />
            <MenuItemNode
                name="artists.classical"
                to={'/artists?filter={"type":"classical"}'}
                label="Rock"
            />
        </MenuItemList>
    </CardContent>
);

A wrapper to display a list of <MenuItemNode> with proper styles (see above).

Accepts the same props as the material-ui <List> component. See https://material-ui.com/api/list/.

CHANGELOG

v4.2.0

2022-08-05

  • (feat) Add the openItemList prop on the <MultiLevelMenu>. Defines which menu items should be open by default.
  • (feat) Add ability to omit the <MultiLevelMenu.Item to> prop

v4.1.2

2022-07-19

  • (fix) Add support for sx prop in <IconMenu.Item>
  • (doc) Fix documentation still referring to 3.x Breadcrumb integration methods

v4.1.1

2022-07-05

  • Doc: Fix wrong imports in some examples.

v4.1.0

2022-07-01

  • Add <IconMenu> and <IconMenu.Item> components.
  • Add <MultiLevelMenu.Item> component
  • Fix <MenuItemNode> component left arrows appear too close to the menu items

v4.0.7

2022-06-29

  • Fix: Replace classnames with clsx

v4.0.6

2022-06-20

  • (fix) Fix missing export <DashboardBreadcrumbItem>

v4.0.5

2022-06-14

  • (fix) Fix <MenuItemCategory> slider doesn't use the theme transitions duration

v4.0.4

2022-06-10

  • (fix) Fix navigating to a record page redirects to the list page after a few seconds.

v4.0.3

2022-06-08

  • (fix) Update peer dependencies ranges (support React 18)

v4.0.2

2022-06-08

  • Fix <MenuItemCategory> sub items popup backdrop prevents navigating when open

v4.0.1

2022-06-07

  • Fix user defined locations are sometime ignored

v4.0.0

2022-06-07

  • Upgrade to react-admin v4

Breaking Changes

  • <MenuItem> was renamed to <MenuItemNode>, to avoid conflicts with <MenuItem> from react-admin
-import { MultiLevelMenu, MenuItem } from '@react-admin/ra-navigation';
+import { MultiLevelMenu, MenuItemNode } from '@react-admin/ra-navigation';

const MyMenu = () => (
    <MultiLevelMenu>
-       <MenuItem name="dashboard" to="/" exact label="Dashboard" />
+       <MenuItemNode name="dashboard" to="/" exact label="Dashboard" />
-       <MenuItem name="songs" to="/songs" label="Songs" />
+       <MenuItemNode name="songs" to="/songs" label="Songs" />
    </MultiLevelMenu>
);
  • <Menu> was renamed to <MenuItemList>, to avoid conflicts with <Menu> from react-admin
const MyMenu = () => (
    <MultiLevelMenu variant="categories">
        <MenuItemCategory name="dashboard" to="/" exact label="Dashboard" icon={<DashboardIcon />} />
        <MenuItemCategory name="songs" icon={<MusicIcon />} to="/songs" label="Songs" />
        <MenuItemCategory
            name="artists"
            {/* The empty filter is required to avoid falling back to the previously set filter */}
            to={'/artists?filter={}'}
            label="Artists"
            icon={<PeopleIcon />}
        >
            <CardContent> {/* to get consistent spacing */}
                <Typography variant="h3" gutterBottom>Artist Categories</Typography>
                {/* Note that we must wrap our MenuItemNode components in a MenuItemList */}
-               <Menu>
-                   <MenuItem name="artists.rock" to={'/artists?filter={"type":"rock"}'} label="Rock" />
-                   <MenuItem name="artists.jazz" to={'/artists?filter={"type":"jazz"}'} label="Jazz" />
-                   <MenuItem name="artists.classical" to={'/artists?filter={"type":"classical"}'} label="Rock" />
-               </Menu>
+               <MenuItemList>
+                   <MenuItemNode name="artists.rock" to={'/artists?filter={"type":"rock"}'} label="Rock" />
+                   <MenuItemNode name="artists.jazz" to={'/artists?filter={"type":"jazz"}'} label="Jazz" />
+                   <MenuItemNode name="artists.classical" to={'/artists?filter={"type":"classical"}'} label="Rock" />
+               </MenuItemList>
            </CardContent>
        </MenuItemCategory>
        <MenuItemCategory
            name="configuration"
            to="/"
            exact
            label="Configuration"
            icon={<SettingsIcon />}
            sx={{ marginTop: 'auto' }}
        />
    </MultiLevelMenu>
);

v3.0.1

2021-09-13

  • (fix) Fix breadcrumb does not match paths correctly when there is a dashboard

v3.0.0

2021-09-08

  • (fix) Breaking change: Following the upgrade to react-admin 3.18, we now have to specify the width of the sidebar with an arbitrary default value. You might have to adjust it with a custom theme.
import { defaultTheme } from "react-admin";
import { ThemeOptions } from "@react-admin/ra-navigation";

export const theme: ThemeOptions = {
  ...defaultTheme,
  overrides: {
    RaSidebar: {
      drawerPaper: {
        width: 96,
      },
      fixed: {
        zIndex: 1200,
      },
    },
  },
};
import { defaultTheme } from "react-admin";

export const theme = {
  ...defaultTheme,
  overrides: {
    RaSidebar: {
      drawerPaper: {
        width: 96,
      },
      fixed: {
        zIndex: 1200,
      },
    },
  },
};

v2.3.5

2021-09-03

  • (fix) Fix useAppLocationMatcher should not confuse resources with names starting with the same characters (product and productCatalog for instance)

v2.3.4

2021-07-16

  • (fix) Fix "cannot read prop style of undefined" error in <MenuItem>

v2.3.3

2021-07-07

  • (fix) Fix resource path resolution to support TabbedForm and TabbedShowLayout tabs with path
  • (fix) Fix resource path resolution to support multiple resources which have names starting with the same characters

v2.3.2

2021-06-29

  • (fix) Update peer dependencies ranges (support react 17)

v2.3.1

2021-06-21

  • (doc) Update the documentation

v2.3.0

2021-06-16

  • (feat) Add translation key support on <BreadcrumbItem>

v2.2.4

2021-06-15

  • (fix) Fix custom routes for a resource might be inferred as the edit view for that resource

v2.2.3

2021-05-06

  • (fix) Fix Breadcrumb resource items for details views are not translated

v2.2.2

2021-05-03

  • (fix) Fix Breadcrumb Dark Mode Support

v2.2.1

2021-04-27

  • (fix) Fix split on undefined in getDeepestLocation

v2.2.0

2021-04-22

  • (feat) Add the initialOpen prop on the <MultiLevelMenu>. Defines whether the menu items with sub menus should be open initialy.

v2.1.0

2021-04-08

  • (feat) Add the hasDashboard prop on the <AppLocationContext> This allows to avoid specifying this prop on the <Breacrumb> itself. It's used in ra-enterprise to set up the breadcrumb automatically regarding the dashboard.

  • (feat) Introduce the useHasDashboard hook to check if a dashboard has been defined.

  • (fix) Ensure the AppLocation and breadcrumb behave correctly when views are included in other views (Create/Edit/Show in aside for example).

v2.0.0

2021-04-01

Breaking change

  • (feat) Introduce variant prop on <Breadcrumb>.
import * as React from "react";
import { TopToolbar, ShowButton } from 'react-admin';
-import { BreadcrumbForActions } from '@react-admin/ra-navigation';
+import { Breadcrumb } from '@react-admin/ra-navigation';

const PostEditActions = ({ basePath, data, resource }) => (
    <TopToolbar>
-        <BreadcrumbForActions />
+        <Breadcrumb variant="actions" />
        <ShowButton basePath={basePath} record={data} />
    </TopToolbar>
);

export const PostEdit = (props) => (
    <Edit actions={<PostEditActions />} {...props}>
        ...
    </Edit>
);

v1.3.3

2021-03-23

  • (fix) Allow to Override BreadcrumbForActions className

v1.3.2

2021-03-22

  • (fix) Fix BreacrumbForActions props interface

v1.3.1

2021-03-19

  • (fix) Fix Breacrumb Styles
  • (fix) Move Breadcrumb out of Layout

v1.3.0

2021-03-18

  • (feat) Added <BreadcrumbForActions>, a Breadcrumb variation with custom styles to make it fit inside an actions toolbar.
import * as React from "react";
import { TopToolbar, ShowButton } from "react-admin";
import { BreadcrumbForActions } from "@react-admin/ra-navigation";

const PostEditActions = ({ basePath, data, resource }) => (
  <TopToolbar>
    <BreadcrumbForActions />
    <ShowButton basePath={basePath} record={data} />
  </TopToolbar>
);

export const PostEdit = (props) => (
  <Edit actions={<PostEditActions />} {...props}>
    ...
  </Edit>
);
import * as React from "react";
import { TopToolbar, ShowButton } from "react-admin";
import { BreadcrumbForActions } from "@react-admin/ra-navigation";

const PostEditActions = ({ basePath, data, resource }) => (
  <TopToolbar>
    <BreadcrumbForActions />
    <ShowButton basePath={basePath} record={data} />
  </TopToolbar>
);

export const PostEdit = (props) => (
  <Edit actions={<PostEditActions />} {...props}>
    ...
  </Edit>
);

v1.2.5

2021-03-17

  • (fix) Fix MenuItemCategory popover is always at the page top

v1.2.4

2020-11-27

  • (fix) Fix MenuItem inside <MenuItemCategory> do not display their label when sidebar is collapsed
  • (fix) Fix custom menu cannot be collapsed in ra-enterprise by upgrading react-admin

v1.2.3

2020-11-03

  • (fix) Fix <MenuItemCategory> blocks scroll

v1.2.2

2020-10-23

  • (fix) Fix <MenuItemCategory> sometimes hidden by the <AppBar>

v1.2.1

2020-10-15

  • (feat) Show by default which <MenuItem> is hovered by using a grey background
  • (fix) Clicking on <MenuItem> borders wasn't possible

v1.2.0

2020-10-05

  • Upgrade to react-admin 3.9

v1.1.5

2020-10-01

  • (fix) Fix menu overlapping when passing from a <MenuItemCtagory /> to another one

v1.1.4

2020-09-30

  • Update Readme

v1.1.3

2020-09-29

  • (fix) Export breadcrumb types

v1.1.2

2020-09-25

  • (fix) Render the <BreadcrumbItem> using material-ui <Typography> and <Link>

v1.1.1

2020-09-17

  • (fix) Fix <MenuItemCategory> props types

v1.1.0

2020-09-17

  • (feat) Replace home by dashboard
  • (fix) Ensure the label of the dashboard <BreadcrumbItem> is translatable and uses react-admin defaults

v1.0.5

2020-09-16

  • (feat) Add a hover effect for the <MenuItemCategory>
  • (fix) Fix the dark mode for the <MultiLevelMenu>
  • (deps) Upgrade dependencies

v1.0.4

2020-09-03

  • (feat) Add a home link to the <Breadcrumb>
  • (feat) Allow to design the <Breadcrumb
  • (fix) Fix the breadcrumbs when used in the home page
  • (deps) Upgrade dependencies

v1.0.3

2020-08-21

  • (fix) Fix the <MenuItemCategory> blur

v1.0.2

2020-08-21

  • (feat) Allow the <MenuItemCategory> customization

v1.0.1

2020-08-20

  • (feat) Introduce the <MultiLevelMenu>
  • (doc) Improve the documentation
  • (deps) Upgrade dependencies

v1.0.0

2020-07-31

  • First release