I have a React-based web application that utilizes React Router to map pages to different URLs:
export const Container = () => (
<div>
<SideNav/>
<div>
<Switch>
<Route path="/login" component={LoginView} />
<Route path="/route1" component={RouteOne} />
<Route path="/route2" component={RouteTwo} />
</Switch>
</div>
</div>
)
When any route gets hit, the sidebar gets rendered as well as the appropriate view. However, I am trying to build the layout such that for certain routes (such as "login"), the SideNav doesn't get rendered and the component (in this case, LoginView) is the only thing that gets rendered. In other words, LoginView should take over the div and be the only child of the top div.
Is there anyway this can be done?
According to react-router docs:
path: string Any valid URL path that path-to-regexp understands.
path-to-regexp understand a string, array of strings, or a regular expression.
Array of routes:
State which routes will render the SideNav as well (Working Example):
<Route path={['/route1', '/route2']} component={SideNav} />
RegExp:
Another option is to show the SideNav only if the path doesn't contain a certain word (working example):
<Route path={/^(?!.*login).*$/} component={SideNav} />
And in your code:
export const Container = () => (
<div>
<Route path={['/route1', '/route2']} component={SideNav} />
<div>
<Switch>
<Route path="/login" component={LoginView} />
<Route path="/route1" component={RouteOne} />
<Route path="/route2" component={RouteTwo} />
</Switch>
</div>
</div>
)
Another approach (I am not sure about this but I faced the same problem and this is how I fixed it, but I admit it's less cleaner than what Ori Drori proposed):
In your SideNav component :
import React from "react";
import {useLocation} from "react-router"
export const SideNav = (props) => {
const location = useLocation();
const show = !location.pathname.includes("login");
return (
<>
{show && (<YourLoginComponentCode /> }
</>
)
}
Related
I am having trouble writing code to render a login page with no navbar and sidebar. I have come across some pages that ask similar questions but none seem to pertain to my current situation.
How to hide navbar in login page in react router
the example given is great but I believe the way of accomplishing that same task has changed with react-router-dom v6 leading me to read about this change in https://dev.to/iamandrewluca/private-route-in-react-router-v6-lg5
It seems I am not understanding a certain aspect about routing with React Router. In the code below I have two Routes. One of the routes(Login) I would like to have render without the NavBar and SideBar component.
const App = () => {
return (
<>
<Routes>
<Route path="/login" element={<LoginPage />} />
</Routes>
<NavBar />
<SideBar />
<main className={styles["main--container"]}>
<div className={styles["main--content"]}>
<Routes>
<Route path="/" element={<Dashboard />} />
</Routes>
</div>
</main>
</>
);
};
An alternative, that I also tried, would be to move the NavBar and SideBar tags into the Dashboard component, but then I would essentially have to do the same copy and paste for any new components. This method felt wrong and inefficient , but if this is the correct way of doing it I will do the needful
Edit: I think it's important to include what it currently does is load the Login page with the NavBar and SideBar included. Navigating to the dashboard component has the NavBar and SideBar but this is intended.
What I would like is for the Login page not to have the NavBar and SideBar
If I understand your question, you are wanting to render the nav and sidebar on the non-login route. For this you can create a layout component that renders them and an outlet for the nested routes.
Using nested routes
import { Outlet } from 'react-router-dom';
const AppLayout = () => (
<>
<NavBar />
<SideBar />
<main className={styles["main--container"]}>
<div className={styles["main--content"]}>
<Outlet /> // <-- nested routes rendered here
</div>
</main>
</>
);
const App = () => {
return (
<>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route element={<AppLayout />} >
<Route path="/" element={<Dashboard />} /> // <-- nested routes
</Route>
</Routes>
</>
);
};
Using a routes configuration and useRoutes hook
const routesConfig = [
{
path: "/login",
element: <LoginPage />,
},
{
element: <AppLayout />,
children: [
{
path: "/",
element: <Dashboard />,
},
],
},
];
...
import { useRoutes } from 'react-router-dom';
const App = () => {
const routes = useRoutes(routesConfig);
return routes;
};
Using a routes configuration and data routers (introduced in v6.4.0)
const routesConfig = [
{
path: "/login",
element: <LoginPage />,
},
{
element: <AppLayout />,
children: [
{
path: "/",
element: <Dashboard />,
},
],
},
];
...
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const router = createBrowserRouter(routesConfig);
const App = () => {
return <RouterProvider router={router} />;
};
The easiest way for you to hide the navbar would be to go to the login page component and call useLocation(). Then you woulf do something like this after declaring the use location. And assigning it to a variable location
{ location.pathname === "/login" ? null : (
Render the whole navbar component);
Not sute if you can be able to read as I type from my phone
//Route.js
<NestedRoutes base="/logs">
<Route>
<LogsMainPage updateQuery={setQuery} />
</Route>
<Route
path="/logs/detail"
component={props => {
return React.createElement(LogDetailsPage, {
currentMainPageQuery: query,
...props,
});
}}
/>
<Route
path={'/:rest*'}
component={params => <h1>Not Found {params.rest}</h1>}
/>
</NestedRoutes>
// NestedRoutes.js
const NestedRoutes = props => {
const router = useRouter();
const [parentLocation] = useLocation();
const nestedBase = `${router.base}${props.base}`;
// don't render anything outside of the scope
if (!parentLocation.startsWith(nestedBase)) return null;
// we need key to make sure the router will remount when base changed
return (
<Router base={nestedBase} key={nestedBase}>
{props.children}
</Router>
);
};
The problem is everytime the page render it always shown Not Found(main content is rendered ie: LogsMainPage) even though the url is correct. Tried digging but can't found how to fix this "not found" url. Is there any guide to set this up properly?
I've edit the example from docs on the "help center route" I put the same code to show Not Found
https://codesandbox.io/s/wouter-demo-nested-routes-forked-qmg6q
Fix your problem by following these steps.
Wrap your /help index page content using Route component.
Then wrap all your /help routes using the Switch component. It will make sure that only one route is rendered at a time like in React Router. (Resource - https://github.com/molefrog/wouter/blob/master/README.md#switch-)
<Scope base="/help">
<Switch>
<Route path="/topics">
<h1>Topics</h1>
<p> To be announced...</p>
</Route>
<Route path="/how-to">
<article>
<h1>How it all started?</h1>
<p></p>
<p></p>
</article>
</Route>
<Route path="/">
<div>
These are nested routes. Relative location: <CurrentLoc />
<ul>
<li>
<Link href="/topics">Topics</Link>
</li>
<li>
<Link href="/how-to">How to use?</Link>
</li>
</ul>
</div>
</Route> {/* Wrap the index page content */}
<Route path="/:rest*" component={() => <h1>Not Found</h1>} />
</Switch> {/* Wrap all the help routes */}
</Scope>
https://codesandbox.io/s/wouter-demo-nested-routes-4q9iv?file=/src/index.js
Let me know if you need further support.
Setup
I have an App component rendering following routes:
<Route path="/items/:id" component={ItemDetail} />
<Route path="/items" component={AllItems} />
In my AllItems component I render a list of all items and the option to create a new item or update an existing one. Doing either one of those actions opens a popup. To do this I render following routes in AllItems:
<Route path="/items/add" component={AddItemModal} />
<Route path="/items/edit" component={EditItemModal} />
Note: It's important that these modals are actually linked to these routes, I can't change that. Neither can I render those routes outside of AllItems as I need to pass soms props to the modals.
Problem
When I go to a route like /items/1: ItemDetail renders (as expected).
When I go to /items/add: ItemDetail renders with add as :id.
I need it to render AddItemModal here as defined in AllItems.
What I tried:
I tried adding exact to the /items/:id route and I also tried adding it to /items/add & /items/edit. Neither of those solutions worked. Either only ItemDetail rendered, or only the modals.
I tried defining /items before /items/:id to hopefully give higher priority to the nested routes. ItemDetail never rendered in this case.
Is there a solution to this so I can prioritise items/add & items/edit over items/:id
Try nesting the routes under /items
<Route
path="/items"
render={() => (
<>
<Route path="" component={AllItems} exact />
<Route path="/add" component={AddItemModal} />
<Route path="/edit" component={EditItemModal} />
<Route path="/:id" component={ItemDetail} />
</>
)}
/>
If you want to have an independent views for ItemDetail and AllItems but at the same time have /items/add and /items/:id/edit (took a little liberty with the url, you need and id to edit an item right?) as modals over AllItems so the structure of the routes would be something like this:
AllItemsView (/items)
AddItemModal (/items/new)
EditItemModal (/items/:id/edit)
ItemDetailView (/items/:id)
You need a little tweak of Tnc Andrei response:
<Route
path="/items"
render={({ match: {url, isExact}, location: {pathname} }) => {
let pathnameArray = pathname.split("/")
let lastChunk = pathnameArray[pathnameArray.length - 1]
if (isExact || lastChunk === "new" || lastChunk === "edit") {
return (
<>
<Route path={`${url}/`} component={CompetitionsView} />
<Switch>
<Route path={`${url}/new`} component={CompetitionFormModal} />
<Route path={`${url}/:competitionId/edit`} component={CompetitionFormModal} />
</Switch>
</>
)
}
return (
<>
<Route path={`${url}/:competitionId`} component={CompetitionView} />
</>
)
}}
/>
I have been trying to understand nested routes and switch in the React v4 Router.
Consider the main router looks like this (simplified):
<Switch>
<Route path="/" component={LoginPage} exact={true} />
<Route path="/dashboard/edit/:id" component={DashboardPage} />
<Route path="/dashboard" component={DashboardPage} />
</Switch>
The "dashboard" component renders the sub-route:
render(){
return (
<div className="note">
<Route to='/edit/:id' render={(props) =>
<div>
<NoteList {...props} />
<EditNotePage {...props} />
</div>
} />
</div>
)
}
The "EditNotePage" component can access the param by:
const mapStateToProps = (state, props) => ({
note: state.notes.find((note) => note.id === props.match.params.id
});
Is this the correct approach?
It seems a little redundant to specify "/dashboard/edit/:id" twice ( ? )
Once in main router and the again in the dashboard component.
However, if I do not match the route in the main router "Switch" the "props.match.params.id" is not accessible since "props.match" will only point to "/dashboard" .
Have I missed something crucial regarding how the React v4 Router works? :)
Kind regards
Kermit
Nope, didn't miss anything. That's how react router v4 works. You define full routes. The trick you can use is that you can grab the current path and prepend it to your "nested path".
I can't find the way how to send object from one React Route Component to another.
For example I have container router like this:
const App = () => (
<Router>
<div>
<Route exact path="/" component={Home} />
<Route path="/sections/:id"
component={Section} />
</div>
</Router>
)
with Home component like this:
const Home = () => {
const Sections = tilesData.map( (section, index) => (
<div>
<img src={section.img} height="200" /><br/>
<Link to={`/sections/'${section.id}`} >Details for {section.author}</Link>
<hr/>
</div>
))
return(
<div>
{Sections}
</div>
)
}
and I don't understand =\ how to pass selected object when next route clicked with <Link>. Here is example component:
const Section = (props) => {
return(
<div>
Section {props.title}
<img src={props.img} />
</div>
)
}
Here is code example: https://codesandbox.io/s/Mv037AE3
In react-router v4, we usually do the following to pass in a ProductPage component:
<Route path='/' component={ProductPage} />
When you want to use props, you can prepare a function that returns your component with the desired props. Here I'm preparing to pass in a toggleSidebarOn prop:
const MyProductPage = (props) => {
return (
<ProductPage
toggleSidebarOn={this.toggleSidebarOn.bind(this)}
{...props}
/>
);
}
Then you use the render prop of the <Route /> instead of its component prop. Do not use both and do not use the component prop as this leads to undesired remounting and might break your app (e.g. for me CSS transitions for the sidebar animation stopped working properly).
Using the render prop for the MyProductPage we can then pass in our toggleSidebarOn prop to its target route:
<Router>
<div>
<Switch>
<Route exact path="/products" render={MyProductPage} />
<Route exact path="/perspectives" component={PerspectivePage}/>
<Route component={NotFound}/>
</Switch>
</div>
</Router>
Hope this helps!