useMenu
useMenu
is used to get menu items derived from the resources. These items include a link to dashboard page (if it exists) and links to the user defined resources (passed as children to <Refine>
).
This hook can also be used to build custom menus, including multi-level support. <Sider/>
components inside @pankod/refine-antd
, @pankod/refine-mui
and @pankod/refine-mantine
packages are using this hook as a base for their menus.
const { selectedKey, menuItems, defaultOpenKeys } = useMenu();
menuItems
is a list of style agnostic menu items. Each of them has a key.selectedKey
is the key of the resource user is viewing at the moment. Its inferred from the route.defaultOpenKeys
is the array with the keys of default opened menus.
useMenu
hooks exported from @pankod/refine-antd
and @pankod/refine-mui
packages are now deprecated and will be removed. Please use useMenu
from @pankod/refine-core
.
Usage
If you are using @pankod/refine-antd
, @pankod/refine-mui
or @pankod/refine-mantine
as a UI framework integration, you can find out more info about how their <Sider/>
components are created and how to create a custom one by following their guides.
Ant Design > Customization > Custom Sider →
Creating a Menu
We will show you how to use useMenu
to create a simple menu for your refine application.
Create a <Layout />
component inside src/components/layout.tsx
with the following code;
import { LayoutProps } from "@pankod/refine-core";
import { useMenu, useNavigation, useRouterContext, useRefineContext } from "@pankod/refine-core";
export const Layout: React.FC<LayoutProps> = ({ children }) => {
const { menuItems, selectedKey } = useMenu();
const { hasDashboard } = useRefineContext();
const { Link } = useRouterContext();
// You can also use navigation helpers from `useNavigation` hook instead of `Link` from your Router Provider.
// const { push } = useNavigation();
return (
<div className="flex min-h-screen flex-col">
<div className="mb-2 border-b py-2">
<div className="container mx-auto">
<div className="flex items-center gap-2">
<Link to="/">
<img
className="w-32"
src="https://refine.dev/img/refine_logo.png"
alt="Logo"
/>
</Link>
<ul>
{hasDashboard && (
<li>
<Link to="/">
<a style={{ fontWeight: selectedKey === "/" ? "bold" : "normal" }}>
<span>Dashboard</span>
</a>
</Link>
</li>
)}
{menuItems.map(({ name, label icon, route }) => {
const isSelected = route === selectedKey;
return (
<li key={name}>
<Link to={route}>
<a style={{ fontWeight: isSelected ? "bold" : "normal" }}>
{icon}
<span>{label ?? name}</span>
</a>
</Link>
</li>
);
})}
</ul>
</div>
</div>
</div>
<div className="bg-white">{children}</div>
</div>
);
};
We created a header with a logo and a list of links to all menu items (resources). The links are clickable and will navigate to the corresponding resource. To do this, we used the useMenu
hook to get the menu items from the <Refine/>
and the useRouterContext
hook to get the <Link/>
component from the router provider. Also useNavigation
hook can be used to navigate between routes.
children
is the content of the layout. In our case, it is the content of the Page components.
Now, we can use the <Layout/>
in our application.
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";
import dataProvider from "@pankod/refine-simple-rest";
import { PostList, PostCreate, PostEdit, PostShow } from "pages/post";
import { CategoryList, CategoryCreate, CategoryEdit } from "pages/category";
import { Layout } from "components/layout";
import { PostIcon } from "icons";
export const App: React.FC = () => {
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
resources={[
{
name: "posts",
list: PostList,
create: PostCreate,
edit: PostEdit,
show: PostShow,
},
{
name: "categories",
list: CategoryList,
create: CategoryCreate,
edit: CategoryEdit,
canDelete: true,
},
]}
Layout={Layout}
/>
);
};
Multi Level Menus
useMenu
hook comes out of the box with multi level menu support, you can render menu items recursively to get a multi level menu.
Update your resources
in <Refine/>
with parentName
to nest them inside a label.
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";
import dataProvider from "@pankod/refine-simple-rest";
import { PostList, PostCreate, PostEdit, PostShow } from "pages/post";
import { CategoryList, CategoryCreate, CategoryEdit } from "pages/category";
import { Layout } from "components/layout";
import { PostIcon } from "icons";
export const App: React.FC = () => {
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
resources={[
{
name: "CMS",
},
{
name: "posts",
parentName: "CMS",
list: PostList,
create: PostCreate,
edit: PostEdit,
show: PostShow,
},
{
name: "categories",
parentName: "CMS",
list: CategoryList,
create: CategoryCreate,
edit: CategoryEdit,
canDelete: true,
},
]}
Layout={Layout}
/>
);
};
Now you can update your <Layout/>
to support multi level rendering with following code:
import { LayoutProps } from "@pankod/refine-core";
import { useMenu, useNavigation, useRouterContext } from "@pankod/refine-core";
export const Layout: React.FC<LayoutProps> = ({ children }) => {
const { menuItems, selectedKey } = useMenu();
const { Link } = useRouterContext();
const renderMenu = (items) => {
return (
<ul>
{items.map((item) => (
<li key={item.label}>
<span>{item.label}</span>
{item.children ? renderMenu(item.children) : null}
</li>
))}
</ul>
);
};
return (
<div className="flex min-h-screen flex-col">
<div className="mb-2 border-b py-2">
<div className="container mx-auto">
<div className="flex items-center gap-2">
<Link to="/">
<img
className="w-32"
src="https://refine.dev/img/refine_logo.png"
alt="Logo"
/>
</Link>
{renderMenu(menuItems)}
</div>
</div>
</div>
<div className="bg-white">{children}</div>
</div>
);
};
API Reference
Return values
Property | Description | Type |
---|---|---|
selectedKey | Key of the resource the user is viewing at the moment | string |
menuItems | List of keys and routes and some metadata of resources and also the dashboard if exists | ITreeMenu[] |
defaultOpenKeys | Array with the keys of default opened menus. | string[] |
Interfaces
interface IResourceItem extends IResourceComponents {
name: string;
label?: string;
route?: string;
icon?: ReactNode;
canCreate?: boolean;
canEdit?: boolean;
canShow?: boolean;
canDelete?: boolean;
parentName?: string;
options?: OptionsProps;
}
interface IResourceComponents {
list?: React.FunctionComponent<IResourceComponentsProps>;
create?: React.FunctionComponent<IResourceComponentsProps>;
edit?: React.FunctionComponent<IResourceComponentsProps>;
show?: React.FunctionComponent<IResourceComponentsProps>;
}
interface IResourceComponentsProps<TCrudData = any> {
canCreate?: boolean;
canEdit?: boolean;
canDelete?: boolean;
canShow?: boolean;
name?: string;
initialData?: TCrudData;
}
type OptionsProps<TExtends = { [key: string]: any }> = TExtends & {
label?: string;
route?: string;
auditLog?: {
permissions?: AuditLogPermissions[number][] | string[];
};
hide?: boolean;
[key: string]: any;
};
type IMenuItem = IResourceItem & {
key: string;
};
type ITreeMenu = IMenuItem & {
children: ITreeMenu[];
};