React Router DOM: Unable to change route to /view from /view/subview - reactjs

I am able to navigate from /about to /portfolio. I can go to /portfolio/subItem1 or /portfolio/subItem2. I can click the back button to return to /portfolio. But I can not click the nav link while on any subitem route to go to /portfolio.
I suspect some sort of routing error, possibly between the top level router on App and the subrouter on the Portfolio component. I'm using react-router-dom 5.2.0.
// top level component
const bodyViews: Record<string, ComponentData> = {
cover: { name: "Home", slug: '/', component: Cover },
portfolio: { name: "Portfolio", slug: "/portfolio", component: Portfolio },
about: { name: "About", slug: '/about', component: About },
contact: { name: "Contact", slug: '/contact', component: Contact }
}
function App() {
return (
<OutermostStyleContainer>
<BrowserRouter>
<Nav bodyViews={bodyViews} />
<main>
<Switch>
<Route path='/about'><About /></Route>
<Route path='/contact'><Contact /></Route>
<Route path='/portfolio'><Portfolio /></Route>
<Route path='/' exact><Cover /></Route>
</Switch>
</main>
</BrowserRouter>
</OutermostStyleContainer>
);
}
// reduced portfolio component
interface PropsShape {}
export default (props: PropsShape) => {
return (
<BrowserRouter>
<section>
<Switch>
{/* Routes to sub-views */}
<Route path={`${useRouteMatch().path}/call-track-voipms`}>
<CallTrackVoipMs GithubLogo={GithubLogo} />
</Route>
{/* Nav links on category view */}
<ItemPreviews
previewData={typeCheckedNavigationData}
portfolioRoute={useRouteMatch().url}
/>
</Switch>
</section>
</BrowserRouter>
)
}
// full Nav component
export default (props: PropsShape) => {
return (
<nav>
<div>
<LinkList jsxData={props.bodyViews} />
</div>
</nav>
)
}
// LinkList. NavList is a styled HOC of <ul>
export default (props: { jsxData: Record<string, ComponentData> }) => {
return (
<section>
<NavList>
{ Object.values(props.jsxData).map((nameAndSlug: ComponentData) => (
<SingleLinkListItem linkData={nameAndSlug} />
)) }
</NavList>
</section>
)
}
// SingleListItem. StyledLink is as the name suggests
export default (props: { linkData: ComponentData }): JSX.Element => {
return (
<li>
<StyledLink to={props.linkData.slug}>{props.linkData.name}</StyledLink>
</li>
)
}
Does anyone see what is causing the routing issue?

I think you should not wrap the Portfolio component with <BrowserRouter>.
You should only wrap the top of the component with <BrowserRouter>.
And you can only wrap your child components with <Switch>.
// reduced portfolio component
interface PropsShape {}
export default (props: PropsShape) => {
return (
<section>
<Switch>
{/* Routes to sub-views */}
<Route path={`${useRouteMatch().path}/call-track-voipms`}>
<CallTrackVoipMs GithubLogo={GithubLogo} />
</Route>
{/* Nav links on category view */}
<ItemPreviews
previewData={typeCheckedNavigationData}
portfolioRoute={useRouteMatch().url}
/>
</Switch>
</section>
)
}

Related

React Router v6 and recursive rendering of Route component from data

Following up from my question React router v6 and relative links from page within route, I'm trying to refactor the routes in our app to be more nested.
Trouble is that it doesn't seem possible to render a Route element recursively from data, because react-router insists that Route is directly inside Route and not wrapped in another component, and I cannot see how to render recursively (to arbitrary depth) any other way.
Reproduction on codesandbox.
import React from "react";
import { BrowserRouter, Routes, Route} from "react-router-dom";
import "./styles.css";
function GenericPage() {
return <div className="page">Generic page</div>;
}
const nav = {
slug: "",
title: "Home",
children: [
{
slug: "foo",
title: "Foo"
},
{
slug: "bar",
title: "Bar"
}
]
};
const RecursiveRoute = ({ node }) => {
return (
<Route path={node.slug} element={<GenericPage />}>
{node.children?.map((child) => (
<RecursiveRoute node={child} />
))}
</Route>
);
};
export default function App() {
return (
<BrowserRouter>
<Routes>
<RecursiveRoute node={nav} />
</Routes>
</BrowserRouter>
);
}
Error from react-router:
[RecursiveRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
Issue
As the error indicates, you can't render Route components directly, they must be rendered directly by a Routes component, or another Route component in the case of nested routes.
Solution
Refactor RecursiveRoute to render a Routes component with a route for the current node and then map the node's children to routes that render the RecursiveRoute as an element.
Example:
function GenericPage({ title }) {
return (
<div className="page">
{title} page
</div>
);
}
const RecursiveRoute = ({ node }) => (
<Routes>
<Route
path={`${node.slug}/*`}
element={<GenericPage title={node.title} />}
/>
{node.children?.map((child) => (
<Route
key={child.slug}
element={<RecursiveRoute key={child.slug} node={child} />}
/>
))}
</Routes>
);
Suggestion
I strongly suggest not trying to roll your own custom route configuration and renderer, use the useRoutes hook instead to do all the heavy lifting for you.
Example:
Refactor the navigation config:
const nav = [
{
path: "/",
element: <GenericPage title="Home" />,
children: [
{
path: "foo",
element: <GenericPage title="Foo" />
},
{
path: "bar",
element: <GenericPage title="Bar" />
}
]
}
];
Pass the config to the useRoutes hook and render the result:
const routes = useRoutes(nav);
...
return routes;
Demo
I had the same puzzle to solve. In general I solve it by passing a function in Routes component. Here is my solution with few code snippets.
// in Routes.ts
interface IRoutes {
path: string
component: JSX.Element
children?: IRoutes[]
}
const routes: IRoutes[] = [
{
path: 'warehouse'
component: <WarehousePage />
children: [
{
path: 'products'
component: <ProductsPage />
},
{
path: 'units'
component: <UnitsPage />
},
]
},
]
// in AppRouter.tsx
const renderRoutesRecursive = (routes: IRoutes[]) =>
routes.map((route, index) =>
route.children ? (
renderRoutesRecursive(route.children)
) : (
<Route
key={index}
path={route.path}
element={route.component}
/>
),
)
const renderRoutes = useMemo(() => renderRoutesRecursive(routes), [routes])
return (
<Routes>
<Route path='/' element={<Layout />}>
{renderRoutes}
</Route>
</Routes>
)
// in Layout.tsx
const Layout = () => {
return (
<>
<Header />
<Navigation />
<Main>
<Outlet />
</Main>
<Footer />
</>
)
}

Is it possible to use multiple outlets in a component in React-Router V6

I am using React Router v6 in an application. I have a layout page, which uses an outlet to then show the main content. I would also like to include a title section that changes based on which path has been matched, but I am unsure how to do this.
function MainContent() {
return (
<div>
<div>{TITLE SHOULD GO HERE}</div>
<div><Outlet /></div>
</div>
);
}
function MainApp() {
return (
<Router>
<Routes>
<Route path="/projects" element={<MainContent />} >
<Route index element={<ProjectList />} title="Projects" />
<Route path="create" element={<CreateProject />} title="Create Project" />
</Route>
<Routes/>
</Router>
);
}
Is something like this possible? Ideally, I would like to have a few other props besides title that I can control in this way, so a good organization system for changes like this would be great.
The most straightforward way would be to move the title prop to the MainContent layout wrapper and wrap each route individually, but you'll lose the nested routing.
An alternative could be to create a React context to hold a title state and use a wrapper component to set the title.
const TitleContext = createContext({
title: "",
setTitle: () => {}
});
const useTitle = () => useContext(TitleContext);
const TitleProvider = ({ children }) => {
const [title, setTitle] = useState("");
return (
<TitleContext.Provider value={{ title, setTitle }}>
{children}
</TitleContext.Provider>
);
};
Wrap the app (or any ancestor component higher than the Routes component) with the provider.
<TitleProvider>
<App />
</TitleProvider>
Update MainContent to access the useTitle hook to get the current title value and render it.
function MainContent() {
const { title } = useTitle();
return (
<div>
<h1>{title}</h1>
<div>
<Outlet />
</div>
</div>
);
}
The TitleWrapper component.
const TitleWrapper = ({ children, title }) => {
const { setTitle } = useTitle();
useEffect(() => {
setTitle(title);
}, [setTitle, title]);
return children;
};
And update the routed components to be wrapped in a TitleWrapper component, passing the title prop here.
<Route path="/projects" element={<MainContent />}>
<Route
index
element={
<TitleWrapper title="Projects">
<ProjectList />
</TitleWrapper>
}
/>
<Route
path="create"
element={
<TitleWrapper title="Create Project">
<CreateProject />
</TitleWrapper>
}
/>
</Route>
In this way, MainContent can be thought of as UI common to a set of routes whereas TitleWrapper (you can choose a more fitting name) can be thought of as UI specific to a route.
Update
I had forgotten about the Outlet component providing its own React Context. This becomes a little more trivial. Thanks #LIIT.
Example:
import { useOutletContext } from 'react-router-dom';
const useTitle = (title) => {
const { setTitle } = useOutletContext();
useEffect(() => {
setTitle(title);
}, [setTitle, title]);
};
...
function MainContent() {
const [title, setTitle] = useState("");
return (
<div>
<h1>{title}</h1>
<div>
<Outlet context={{ title, setTitle }} />
</div>
</div>
);
}
...
const CreateProject = ({ title }) => {
useTitle(title);
return ...;
};
...
<Router>
<Routes>
<Route path="/projects" element={<MainContent />}>
<Route index element={<ProjectList title="Projects" />} />
<Route
path="create"
element={<CreateProject title="Create Project" />}
/>
</Route>
</Routes>
</Router>
I was facing the same issue for a left-right layout: changing sidebar content and main content, without repeating styling, banner, etc.
The simplest approach I found was to remove nested routing, and create a layout component in which I feed the changing content through properties.
Layout component (stripped for this post):
export function Layout(props) {
return (
<>
<div class="left-sidebar">
<img id="logo" src={Logo} alt="My logo" />
{props.left}
</div>
<div className='right'>
<header className="App-header">
<h1>This is big text!</h1>
</header>
<nav>
<NavLink to="/a">A</NavLink>
|
<NavLink to="/b">B</NavLink>
</nav>
<main>
{props.right}
</main>
</div>
</>
);
}
Usage in react router:
<Route path="myPath" element={
<Layout left={<p>I'm left</p>}
right={<p>I'm right</p>} />
} />
Another solution is to use the handle prop on the route as described in the useMatches documentation.
import { useMatches } from "react-router-dom";
function MainContent() {
const matches = useMatches()
const [title] = matches
.filter((match) => Boolean(match.handle?.title))
.map((match) => match.handle.title);
return (
<div>
<div>{title}</div>
<div><Outlet /></div>
</div>
);
}
function MainApp() {
return (
<Router>
<Routes>
<Route path="/projects" element={<MainContent />} >
<Route index element={<ProjectList />} handle={{ title: "Projects" }} />
<Route path="create" element={<CreateProject />} handle={{ title: "Create Project" }} />
</Route>
<Routes/>
</Router>
);
}

home navlinks link stay active REACT

I hoe you are doing well :)
when I click on a link from the navbar, the home link is always active. I tried to add excat to the home route but nothing changed?
if someone can help me please that would be great.
thank you in advance
thank you for your time
import { NavLink } from "react-router-dom";
import { links } from "../utils/constants";
import styled from "styled-components";
const NavMenu = () => {
return (
<Nav>
<ul>
{links.map((link) => {
const { id, url, text } = link;
return (
<li key={id}>
<NavLink to={url}>{text}</NavLink>
</li>
);
})}
</ul>
</Nav>
);
};
const Nav = styled.nav`
////////
.active {
color: var(--white);
}
}
`;
export default NavMenu;
APP.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { NavMenu } from "./components";
import { Home, About, Projects, Contact, Error } from "./pages";
const App = () => {
return (
<Router>
<NavMenu />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/about">
<About />
</Route>
<Route exact path="/projects">
<Projects />
</Route>
<Route exact path="/contact">
<Contact />
</Route>
<Route path="*">
<Error />
</Route>
</Switch>
</Router>
);
};
export default App;
I think you are not using Route Component properly. You should try in this way.
Pass the path and component in the Route instead of wrapping it inside in Route component.
import { HomePage, UserPage } from "./pages";
const routes = [
{
path: '/home',
component: HomePage,
},
{
path: '/user',
component: UserPage,
}]
<Switch>
{routes.map(({ path, component }, index) => (
<Route key={String(index)} exact path={path} component={component} />
))}
</Switch>

TS2559: Type '{ children: Element[]; }' has no properties in common with type 'IntrinsicAttributes'

I want to use react-router-dom by React with TypeScript. I have a typescript error in <Router> at Home.jsx.
Error
Home.tsx
Type '{ children: Element[]; }' has no properties in common with type 'IntrinsicAttributes'.TS2559(9)
App.tsx
import React from 'react'
import Home from './pages/Home'
function App() {
return (
<div>
<Home />
</div>
)
}
export default App
Home.tsx
import React from 'react'
import { Button, TitleDesc } from '../components/Index'
import { Link } from 'react-router-dom'
import Router from '../router'
const Home: React.FC = () => {
return (
<>
<Router>
<div>
<div>Hii</div>
<div>
<Link to='/login'><Button color='green'>Login</Button></Link>
<Link to='/register'><Button color='blue'>Register</Button></Link>
</div>
</div>
<TitleDesc title='Hi' desc='Hi' />
</Router>
</>
)
}
export default Home
router.tsx
import * as React from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import Login from './pages/Login'
import Register from './pages/Register'
import Home from './pages/Home'
const Router = () => {
return (
<BrowserRouter>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/Login' component={Login} />
<Route exact path='/Register' component={Registerl} />
<Route component={() => <h1>204 No Content</h1>} />
</Switch>
</BrowserRouter>
)
}
export default Router
It's because of you don't call {props.children} in the router.tsx. Change it to bellow code will remove the error:
const Router = : React.FunctionComponent (props) => {
return (
<BrowserRouter>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/Login' component={Login} />
<Route exact path='/Register' component={Registerl} />
<Route component={() => <h1>204 No Content</h1>} />
{props.children}
</Switch>
</BrowserRouter>
)
}
But logically it doesn't need to do something as you did. Because your Router will handle the routes and you don't need to use it again in the Home. So instead of using Home in App, transform it to use Router in your App. Thus your code can change to this:
App.tsx
function App() {
return (
<div>
<Router/>
</div>
)
}
export default App
Home.tsx
const Home: React.FC = () => {
return (
<>
<div>
<div>Hii</div>
<div>
<Link to='/login'><Button color='green'>Login</Button></Link>
<Link to='/register'><Button color='blue'>Register</Button></Link>
</div>
</div>
<TitleDesc title='Hi' desc='Hi' />
</>
)
}
export default Home
Router.tsx
This component won't change.
I think Router only accepts a single child. You have two: div and TitleDesc. Wrap those in a <> and it should be fine:
const Home: React.FC = () => {
return (
<>
<Router>
<> {/* <-- new wrapper */ }
<div>
<div>Hii</div>
<div>
<Link to='/login'><Button color='green'>Login</Button></Link>
<Link to='/register'><Button color='blue'>Register</Button></Link>
</div>
</div>
<TitleDesc title='Hi' desc='Hi' />
</> {/* <-- new wrapper */}
</Router>
</>
)
}

How to implement nested Routing (child routes) in react router v4?

The component tree i want is as below
- Login
- Home
- Contact
- About
Contact and About are children of Home.
This is my App.js ,
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Route exact path="/home" component={HomeView} />
</div>
</BrowserRouter>
);
}
}
render(<App />, document.getElementById('root'));
This is Home,
export const HomeView = ({match}) => {
return(
<div>
<NavBar />
Here i want to render the contact component, (Navbar need to stay)
</div>
)
}
This is my Navbar,
export const NavBar = () => {
return (
<div>
<Link to="/home">Home</Link>
<Link to="/home/contact">Contact</Link>
<hr/>
</div>
)
}
Contact component just need to render "hello text".
To make nested routes you need to remove exact:
<Route path="/home" component={HomeRouter} />
And add some routes:
export const HomeRouter = ({match}) => {
return(
<div>
<NavBar />
{/* match.path should be equal to '/home' */}
<Switch>
<Route exact path={match.path} component={HomeView} />
<Route exact path={match.path + '/contact'} component={HomeContact} />
<Switch>
</div>
)
}
You don't need use match.path in nested routes but this way it will be easier to move everything from "/home" to "/new/home" in case you decide to change your routing.

Resources