Reputation: 2675
I am using React Router v6 to build nested routes. I am facing 2 issues:
Please help.
This is my code.
App.js
import "./styles.css";
import {
Navigate,
Route,
Routes,
useMatch,
useLocation,
BrowserRouter,
Link,
Outlet
} from "react-router-dom";
import ComponentC from "./ComponentC";
import { Fragment } from "react";
const ComponentA = () => <p>Component A</p>;
const ComponentB = () => <p>Component B</p>;
const ComponentC1 = () => <p>I am in Component C1</p>;
const ComponentC2 = () => <p>I am in Component C2</p>;
const SubComponentC3 = () => <p>SubComponent C3</p>;
export const ComponentC3 = () => {
const location = useLocation();
const match = useMatch(location.pathname);
return (
<>
<p>Component C3</p>
<Link to={`${match.path}/newRoute`}>Take me to a new route</Link>
<Routes>
<Route
exact
path={`${match.path}/newRoute`}
element={<SubComponentC3 />}
/>
</Routes>
</>
);
};
export const componentCChildren = [
{
label: "Component C - 1",
code: "subC1",
component: ComponentC1
},
{
label: "Component C - 2",
code: "subC2",
component: ComponentC2
},
{
label: "Component C - 3",
code: "subC3",
component: ComponentC3
}
];
export const routeValues = [
{
label: "Component A",
path: "/routeA",
component: ComponentA,
children: []
},
{
label: "Component B",
path: "/routeB",
component: ComponentB,
children: []
},
{
label: "Component C",
path: "/routeC/*",
component: ComponentC,
children: componentCChildren
}
];
export default function App() {
return (
<div className="App">
<BrowserRouter>
{routeValues.map((item) => (
<Link key={item.path} to={item.path} style={{ paddingRight: "10px" }}>
{item.label}
</Link>
))}
<Routes>
{routeValues.map((route) => {
if (route.children.length > 0) {
return (
<Route
key={route.path}
path={route.path}
element={<route.component />}
>
{route.children.map((r, i, arr) => (
<Fragment key={r.code}>
<Route
path={`${route.path}/${r.code}`}
element={<r.component />}
/>
<Route
path={route.path}
element={<Navigate to={`${route.path}/${arr[0].code}`} />}
/>
</Fragment>
))}
</Route>
);
}
return (
<Route
key={route.path}
path={route.path}
element={<route.component />}
/>
);
})}
<Route path="*" element={<Navigate to="routeA" />} />
</Routes>
<Outlet />
</BrowserRouter>
</div>
);
}
ComponentC.js
import { useState } from "react";
import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box";
import TabContext from "@mui/lab/TabContext";
import TabList from "@mui/lab/TabList";
import TabPanel from "@mui/lab/TabPanel";
import { useNavigate, useMatch, useLocation } from "react-router-dom";
import { componentCChildren } from "./App";
export default function ComponentC(props) {
const navigate = useNavigate();
const location = useLocation();
const match = useMatch(location.pathname);
const [tabId, setTabId] = useState(componentCChildren[0].code);
const handleTabChange = (e, tabId) => {
console.log("tabId", tabId);
navigate(`${tabId}`);
setTabId(tabId);
};
return (
<>
<p>Component C</p>
<TabContext value={tabId}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<TabList onChange={handleTabChange} aria-label="lab API tabs example">
{componentCChildren.map((tab) => {
return <Tab key={tab.code} value={tab.code} label={tab.label} />;
})}
</TabList>
</Box>
{componentCChildren.map((tab) => {
return (
<TabPanel key={tab.code} value={tab.code}>
{<tab.component />}
</TabPanel>
);
})}
</TabContext>
</>
);
}
This is a link to my sandbox.
Upvotes: 2
Views: 2559
Reputation: 202605
Here's a refactor that leaves most of your route definitions in tact. The changes are mostly in how, and where, the routes are rendered.
App.js
Remove the routeValues
children and change the "/routeC/*"
string literal to "/routeC"
since it's used for both the route path and the link. Append the "*"
wildcard character to the route's path when rendering.
ComponentC3
will use relative links and paths to get to ".../newRoute"
where "..." is the currently matched route path.
export const ComponentC3 = () => {
return (
<>
<p>Component C3</p>
<Link to="newRoute">Take me to a new route</Link>
<Routes>
<Route path="newRoute" element={<SubComponentC3 />} />
</Routes>
</>
);
};
export const routeValues = [
{
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">
<BrowserRouter>
{routeValues.map((item) => (
<Link key={item.path} to={item.path} style={{ paddingRight: "10px" }}>
{item.label}
</Link>
))}
<Routes>
{routeValues.map((route) => (
<Route
key={route.path}
path={`${route.path}/*`} // <-- append wildcard '*' here
element={<route.component />}
/>
))}
<Route path="*" element={<Navigate to="routeA" />} />
</Routes>
</BrowserRouter>
</div>
);
}
ComponentC.js
Here is where you'll render the componentCChildren
as descendent routes. Within a new Routes
component map componentCChildren
to Route
components each rendering a TabPanel
component. Append the "*"
wildcard matcher to the route path again so further descendent routes can be matched. Use a useEffect
hook to issue an imperative redirect from "/routeC"
to the first tab at "/routeC/subC1"
.
export default function ComponentC(props) {
const navigate = useNavigate();
useEffect(() => {
if (componentCChildren?.[0]?.code) {
// redirect to first tab if it exists
navigate(componentCChildren[0].code, { replace: true });
}
// run only on component mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [tabId, setTabId] = useState(componentCChildren[0].code);
const handleTabChange = (e, tabId) => {
console.log("tabId", tabId);
navigate(tabId, { replace: true }); // just redirect between tabs
setTabId(tabId);
};
return (
<>
<p>Component C</p>
<TabContext value={tabId}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<TabList onChange={handleTabChange} aria-label="lab API tabs example">
{componentCChildren.map((tab) => {
return <Tab key={tab.code} value={tab.code} label={tab.label} />;
})}
</TabList>
</Box>
<Routes>
{componentCChildren.map((tab) => {
const TabComponent = tab.component;
return (
<Route
key={tab.code}
path={`${tab.code}/*`} // <-- append wildcard '*' here
element={
<TabPanel value={tab.code}>
<TabComponent />
</TabPanel>
}
/>
);
})}
</Routes>
</TabContext>
</>
);
}
Upvotes: 1
Reputation: 697
in ComponentC
just you need to pass <Outlet />
. i updated your working demo pls check here
Upvotes: 0