Search code examples
reactjsreact-routerantd

Ant Design Custom Tabs with react Router v6


So I am doing a little project with react, react router V6 and ant design tabs ! As I will have to reuse the Tab component on other components later, I created a custom basic Tabs component into which I pass an array with 'tab name', 'tab Body', and 'path' to dynamically map the required tabs. Only that, I am facing a difficulty in routing the Tabs in a generic way. i found a little solution, but what i found is that it will only work with only a specific tab and not the generic one that i want it to work with.

i used the custom Tab in different occasion and i want to route each one of them specifically, for exemple url/Notifications/tab1, url/Notifications/tab2, url/products/tab1 .....

So these are the custom Tabs that i did, but here i won't get the url that i want, and also it won't work with all the tabs in the different pages

import React from 'react';
import { Tabs } from 'antd';
import './CustomTab.css';
import { Outlet, useNavigate, useLocation, useParams } from 'react-router-dom';

const { TabPane } = Tabs;

const CustomTab = ({ elements }) => {
  const history = useNavigate();
  let { source } = useParams();
  const { pathname } = useLocation();
  console.log('source', source);
  console.log('pathname', pathname);

  return (
    <>
      <Tabs
        activeKey={source}
        onChange={(key) => {
          history(`/${key}`);
        }}
        tabBarGutter={70}
        tabBarStyle={{ backgroundColor: 'white' }}
        size="large"
        className="Tabs">
        {elements.map((e, index) => (
          <TabPane tab={e.tabTitle} className="TabsBody" key={e.path}>
            {e.tabBody}
          </TabPane>
        ))}
      </Tabs>
    </>
  );
};

export default CustomTab;

this is an example of Custom tab that I am using in the Notification page:

import React from 'react';
import './NotificationPage.css';
import CustomTab from '../../Components/GenericTabs/CustomTab';
import AllNotifications from './AllNotifications';
import GeneralUpdates from './GeneralUpdates';

const NotificationPage = () => {
  return (
    <>
      <div className="notification-title">Notifications</div>
      <div className="notification-container">
        <CustomTab
          elements={[
            {
              tabTitle: 'All Notifications',
              tabBody: <AllNotifications />,
              path: 'AllNotifications'
            },
            {
              tabTitle: 'General Updates',
              tabBody: <GeneralUpdates />,
              path: 'General_Updates'
            },
            {
              tabTitle: 'Purchase Updates',
              tabBody: 'aze',
              path: 'Purchase_Updates'
            },
            {
              tabTitle: 'Products Updates',
              tabBody: 'aze',
              path: 'Products_Updates'
            },
            {
              tabTitle: 'Account & Billing',
              tabBody: 'aze',
              path: 'Account&Billing'
            }
          ]}
        />
      </div>
    </>
  );
};

export default NotificationPage;

This is App.js where am doing the routing, as you see here the problem it will only work for the first root that is the notification and won't work for the Archived Products as they share the same /:source

            <Route path="/Notifications" element={<NotificationPage />}></Route>
            <Route path="/Notifications/:source" element={<NotificationPage />} />

            <Route path="/Archived_Products" element={<ArchivedProducts />} />
            <Route path="/:source" element={<ArchivedProducts />} />

i tried different things but they only worked if only i had 1 custom tabs and not many.


Solution

  • Issue

    Using history(`/${key}`);, or specifically "`/${key}`" path with a leading slash "/" character denotes an absolute, versus a relative, path from the "/Notifications" path that is rendering the NotificationPage component.

    Solution

    I suggest using relative routes to issue navigation actions to a sibling path. This needs to be done relative to the parent "/Notifications" path and not relative from the current tab's path. To do this prefix the tab path with a ".." to remove a segment (e.g. from "/Notifications/segment" back to "/Notifications") and append the new sibling path.

    Example:

    const CustomTab = ({ elements }) => {
      const navigate = useNavigate();
      const { source } = useParams();
    
      return (
        <>
          <Tabs
            activeKey={source}
            onChange={(path) => {
              navigate(`../${path}`); // <-- sibling path
            }}
            tabBarGutter={70}
            tabBarStyle={{ backgroundColor: "white" }}
            size="large"
            className="Tabs"
          >
            {elements.map((element) => (
              <TabPane
                tab={element.tabTitle}
                className="TabsBody"
                key={element.path}
              >
                {element.tabBody}
              </TabPane>
            ))}
          </Tabs>
        </>
      );
    };
    

    To make the sibling path navigation work though you can't render the NotificationPage on the parent "/Notifications" path. Refactor the routes to render an index route on "/Notifications" that redirects, or "seeds", to an initial selected tab.

    Example:

    <Routes>
      ...
      <Route path="/Notifications">
        <Route index element={<Navigate to="AllNotifications" replace />} />
        <Route path=":source" element={<NotificationPage />} />
      </Route>
      ...
    </Routes>
    

    Edit ant-design-custom-tabs-with-react-router-v6