I am currently developing a react app with an admin dashboard that has different "tabs" that I integrated them in a Router. After the admin logs in, the app navigates you to /admin
. The idea of that page is that I have a side menu with different pages. Inside the AdminView
I want to display the specific component based on the id that I provide:
Here is the main route that displays the admin view
<Route path="/admin">
<Route path="login" element={<AdminLogin />} />
<Route path=":page" element={<AdminView />} />
</Route>
Inside the Admin view I have something like this:
I am selecting the component that I want to display based in the :page
:
const getTable = () => {
switch (table) {
case 'realms':
return RealmsTable;
case 'clients':
return ClientsTable;
case 'users':
return UsersTable;
case 'roles':
return RolesTable;
case 'management':
return Management;
default:
return NotFoundtView;
}
};
const Table = useMemo(getTable, [table]);
With that component selected I am displaying it like so:
<Layout className="dashboard-layout" hasSider>
<DSider />
<Layout>
<DHeader />
<Content>
<Suspense>
<Routes>
<Route index Component={Table} />
</Routes>
</Suspense>
</Content>
</Layout>
</Layout>
This is doing what I want, but I feel like I am doing it wrong. Is there a cleaner way?
As a reference:
I am using react 18
and react-router-dom v6
More info:
I mentioned a sider that is in sync with the selected table:
const { table } = useParams();
return (
<Sider>
<Menu
className="no-bg"
items={items}
onClick={onMenuSelect}
mode="inline"
defaultSelectedKeys={[table!]}
inlineCollapsed={collapsed}
theme="dark"
></Menu>
</Sider>
);
It's a basic antd
Menu
component here and I am using the id as the keys for the menu items:
const items: MenuItem[] = [
{
type: 'divider',
className: 'menu-devider'
},
{
key: 'realms',
label: 'Realms',
className: 'menu-item',
icon: <ReactSVG src="/svg/realms.svg" />
},
{
type: 'divider',
className: 'menu-devider'
},
{
key: 'clients',
label: 'Clients',
className: 'menu-item',
icon: <ReactSVG src="/svg/clients.svg" />
},
{
type: 'divider',
className: 'menu-devider'
},
{
key: 'users',
label: 'Users',
className: 'menu-item',
icon: <ReactSVG src="/svg/users.svg" />
},
{
type: 'divider',
className: 'menu-devider'
},
{
key: 'roles',
label: 'Roles',
className: 'menu-item',
icon: <ReactSVG src="/svg/roles.svg" />
},
{
type: 'divider',
className: 'menu-devider'
},
{
key: 'management',
label: 'Management',
className: 'menu-item',
icon: <ReactSVG src="/svg/management.svg" />
},
{
type: 'divider',
className: 'menu-devider'
}
];
My suggestion would be to make AdminView
a layout route component that renders an Outlet
component instead of an index route and each table component on its own nested route. This should remove the need for the getTable
component matcher and component reference memoization. In other words, let React-Router do the matching/rendering work for you, no need to "re-invent the wheel".
Example:
import { Outlet } from 'react-router-dom';
...
<Layout className="dashboard-layout" hasSider>
<DSider />
<Layout>
<DHeader />
<Content>
<Suspense>
<Outlet /> // <-- nested routes render content here
</Suspense>
</Content>
</Layout>
</Layout>
<Route path="/admin">
<Route path="login" element={<AdminLogin />} />
<Route element={<AdminView />}>
<Route path="realms" element={<RealmsTable />} />
<Route path="clients" element={<ClientsTable />} />
<Route path="users" element={<UsersTable />} />
<Route path="roles" element={<RolesTable />} />
<Route path="management" element={<RealmsTable />} />
</Route>
<Route path="*" element={<NotFoundtView />} />
</Route>