1

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.

arunmmanoharan
  • 2,535
  • 2
  • 29
  • 60
  • 1
    If a `Route` has nested Routes, it should not be exact. So you need `exact={false}` for the one that has nested routes. https://reactrouter.com/web/example/nesting – Ajeet Shah Mar 22 '21 at 13:39
  • @AjeetShah Thanks. That fixed rendering the tabs. However when I click on those tabs, I get Failed to execute 'pushState' on 'History': A history state object with URL 'https://routec/subC2' cannot be created in a document with origin 'https://sw231.csb.app' and URL 'https://sw231.csb.app/routeC/subC1'. Below is my codesandbox link – arunmmanoharan Mar 22 '21 at 13:44
  • https://codesandbox.io/s/blissful-goldstine-sw231?file=/src/App.js – arunmmanoharan Mar 22 '21 at 13:44

1 Answers1

2

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.

Ajeet Shah
  • 18,551
  • 8
  • 57
  • 87
  • Thanks Ajeet. Can you just hint me on a specific question? Lets say I have queryParams to be carried over on each route. E.g.: /routeA?id=4 and I need it to be carried over to /routeC/subC3?id=4 but it fails. – arunmmanoharan Mar 22 '21 at 16:03
  • 1
    I think there is no way to automatically forward this. You will have to write manual code. See [query-parameters in doc](https://reactrouter.com/web/example/query-parameters). It is possible to pass Query Params with `Link`, `history.push`. See [this answer](https://stackoverflow.com/a/66610677/2873538). **May be**, you can write some manual code using [this listener](https://stackoverflow.com/a/66404585/2873538) which can forward Query Params automatically. Try this, if you fail, maybe you can ask a new question : "How to forward query params automatically?". Maybe someone knows solution. – Ajeet Shah Mar 22 '21 at 16:11
  • Thanks @AjeetShah – arunmmanoharan Mar 22 '21 at 16:13
  • BUT wait, if it is being passed automatically on Each Route. Why not just make it a permanent part of route, using [route param] -> you can find this in docs. https://reactrouter.com/web/example/url-params You can decide. – Ajeet Shah Mar 22 '21 at 16:13
  • Thanks. Will have a look – arunmmanoharan Mar 22 '21 at 16:19
  • 1
    I was able to fix it by manually adding search to history.push with the necessary value – arunmmanoharan Mar 22 '21 at 16:43
  • I have one more nested route inside ComponentC3, but it doesnt render. Can you help me out on this or do you want me to ask a new question? https://codesandbox.io/s/stackoverflow-nested-routes-forked-2pe0z?file=/src/ComponentC.js – arunmmanoharan Mar 22 '21 at 17:45
  • 1
    @a2441918 Done: https://codesandbox.io/s/stackoverflow-nested-routes-forked-denfq – Ajeet Shah Mar 22 '21 at 18:21
  • Sorry to trouble you again. I think this is the final piece. Whenever I click on Take me to a new route, I need the url changes but the new component is rendered just below Component C3 content. I want it in a way where the componentC3 content is removed and only subComponentC3 is shown below the tab. I can create a new question if that works out for you. – arunmmanoharan Mar 23 '21 at 14:42