arunmmanoharan
arunmmanoharan

Reputation: 2675

Nested routes inside a route not working - React

I have a page which has three routes. The 3rd route has a tab component which handles 3 sub routes. I am able to navigate to Route 3, but unable to view the tabs and unable to render the content under each tab.

Please advice.

This is my code:

import "./styles.scss";
import React, { useState } from "react";
import { Redirect, Route, Switch } from "react-router";
import { BrowserRouter, Link } from "react-router-dom";
import { Tab, Tabs } from "@blueprintjs/core";

const ComponentC1 = () => <p>Component C1</p>;
const ComponentC2 = () => <p>Component C2</p>;
const ComponentC3 = () => <p>Component C3</p>;

const componentCRoutes = [
  {
    label: "Component C - 1",
    code: "subC1",
    component: ComponentC1
  },
  {
    label: "Component C - 2",
    code: "subC2",
    component: ComponentC2
  },
  {
    label: "Component C - 3",
    code: "subC3",
    component: ComponentC3
  }
];
const ComponentA = () => <p>Component A</p>;
const ComponentB = () => <p>Component B</p>;
const ComponentC = (props) => {
  const [tabId, setTabId] = useState(componentCRoutes[0].label);

  const handleTabChange = (tabId) => setTabId(tabId);
  return (
    <>
      <p>Component C</p>
      <Tabs onChange={handleTabChange} selectedTabId={tabId}>
        {componentCRoutes.map((tab) => {
          return (
            <Tab
              key={tab.code}
              id={tab.label}
              title={
                <Link to={`/${props.match.url}/${tab.code}`}>{tab.label}</Link>
              }
            />
          );
        })}
      </Tabs>
      {(() => {
        const { component, code } = componentCRoutes.find(
          (item) => item.label === tabId
        );
        return (
          <Route path={`${props.match.url}/${code}`} component={component} />
        );
      })()}
      <Route exact path={props.match.url}>
        <Redirect to={`${props.match.url}/${componentCRoutes[0].code}`} />
      </Route>
    </>
  );
};

const routes = [
  { label: "Component A", path: "/routeA", component: ComponentA },
  { label: "Component B", path: "/routeB", component: ComponentB },
  { label: "Component C", path: "/routeC", component: ComponentC }
];

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <BrowserRouter>
        {routes.map((item) => (
          <Link key={item.path} to={item.path} style={{ paddingRight: "10px" }}>
            {item.label}
          </Link>
        ))}
        <Switch>
          {routes.map((route) => {
            return (
              <Route
                key={route.path}
                exact
                path={route.path}
                component={route.component}
              />
            );
          })}
          <Route exact path="/">
            <Redirect to="/routeA" />
          </Route>
        </Switch>
      </BrowserRouter>
    </div>
  );
}

This is my codesandbox link

Please advice.

Upvotes: 1

Views: 2822

Answers (1)

Ajeet Shah
Ajeet Shah

Reputation: 19863

Issues:

  1. Tabs in ComponentC are not working correctly as React-Router Link. It can be fixed using history.push in Tab's onChange handler.
  2. You have not defined Routes in your nested component properly. You are using find to define the Route, that looks dirty. It can be fixed using a Switch and Route in nested component i.e. ComponentC
  3. You used Route and Redirect to make default paths. That can be simplified as well.
  4. You used props.match.url and props.match.path incorrectly. props.match.url (URL) should be used in Link or history.push and props.match.path (PATH) should be used in path of your nested Routes declarations.

Solution:

After fixing all the issues mentioned above, Here is the working code:

(Also, note that the Route that has nested routes should not be marked exact={true})

Main Routes:

const routes = [
  { exact: true, label: "Component A", path: "/routeA", component: ComponentA },
  { exact: true, label: "Component B", path: "/routeB", component: ComponentB }
  { exact: false, label: "Component C", path: "/routeC", component: ComponentC }
  // ^ it is false because it has nested routes
];

// JSX
<BrowserRouter>

  {routes.map((item) => (
    <Link key={item.path} to={item.path}>
      {item.label}
    </Link>
  ))}

  <Switch>
    {routes.map((route) => {
      return (
        <Route
          key={route.path}
          exact={route.exact}
          path={route.path}
          component={route.component}
        />
      );
    })}
    <Redirect exact from="/" to="/routeA" />
  </Switch>
</BrowserRouter>

And Here is nested routes declarations inside ComponentC:

const routes = [
  {
    label: "Component C1",
    code: "subC1",
    component: ComponentC1
  },
  {
    label: "Component C2",
    code: "subC2",
    component: ComponentC2
  },
  {
    label: "Component C3",
    code: "subC3",
    component: ComponentC3
  }
];

export default function ComponentC(props) {
  const [tabId, setTabId] = useState(routes[0].code);
  const handleTabChange = (tabId) => {
    props.history.push(`${props.match.url}/${tabId}`);
    setTabId(tabId);
  };

  return (
    <>
      <Tabs onChange={handleTabChange} selectedTabId={tabId}>
        {routes.map((tab) => {
          return <Tab key={tab.code} id={tab.code} title={tab.label} />;
        })}
      </Tabs>

      <Switch>
        {routes.map((route) => (
          <Route
            key={route.code}
            exact
            path={`${props.match.path}/${route.code}`}
            component={route.component}
          />
        ))}
        <Redirect
          exact
          from={props.match.url}
          to={`${props.match.url}/${routes[0].code}`}
        />
      </Switch>
    </>
  );
}

Here is full demo on Sandbox.

Upvotes: 2

Related Questions