I'm having trouble configuring my react-router I'm building a dashboard with 2 pages with nested routes. The first page is the main page with all the protected routes and the second login with unprotected routes. The problem I'm facing is when I place the exact prop on AuthRoute in the Routes component I won't be able to navigate to all the protected routes but I can navigate to login page. If I remove the exact prop I'll be able to navigate to all the protected routes but I can't navigate to the login page. I tried purring the exact on both components it won't work as well
this is may Routes component with two pages Main with all protected routes such as Dashboard and logIn page with another route reset-page
function Routes({ ColorModeContext }) {
return (
<Router>
<Switch>
<AuthRoute
path="/"
ColorModeContext={ColorModeContext}
component={Main}
/>
<Route path="/login" component={Login} />
<Route path="*">
<div className="center">
<Box pb={2}>
<Typography variant="h3">Page Not Found (-_-)</Typography>
</Box>
<Button variant="contained" onClick={() => window.history.back()}>
Go Back
</Button>
</div>
</Route>
</Switch>
</Router>
);
}
this is my AuthRoute that separates protected routes from unprotected routes
const AuthRoute = ({ component: Component, authStatus = true, ...rest }) => {
const { ColorModeContext, ...other } = rest;
return (
<Route
{...other}
render={(props) =>
authStatus ? (
<Component {...props} ColorModeContext={ColorModeContext} />
) : (
<Redirect to="/login" />
)
}
/>
);
};
here's the main page with all the routes
const Main = ({ props }) => {
const { path } = useRouteMatch();
return (
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<DrawerHeader />
<Switch>
<Route exact path={path} component={Home} />
<Route path={`${path}/exam-results`} component={ExamResults} />
<Route path={`${path}/new-reg`} component={Registration} />
//...more components
</Switch>
</Box>
);
};
here's the login page
const LogIn = ({ props }) => {
const { path } = useRouteMatch();
return (
<Switch>
<Route exact path={path} component={LogInForm} />
<Route path={`${path}/reset-password`} component={ResetPassForm} />
</Switch>
);
};
Related
const Navbar = () => {
return (
<div>
{location === '/' ? (
<AuthNav />
) : location === '/home' && isAuthenticated ? (
<MainNav />
) : <AuthNav />
}
</div>
);
};
How do I render two separate navbars on different application routes, in this case, I want to render the AuthNav in the login and signup path and I want to render MainNav on the home path.
Issues
I think you've a few things working against you:
The Navbar component is unconditionally rendered and using window.location.pathname to compute which actual navigation component to render. This means the view to be rendered is only computed when the Navbar component rerenders.
The Navbar component is rendered outside the Routes, so it's not rerendered when a route changes.
Solution
Instead of unconditionally rendering Navbar and trying to compute which nav component to render based on any current URL pathname, split them out into discrete layout routes that render the appropriate nav component.
Example:
Navbar.jsx
export const AuthNav = ({ auth }) => {
....
};
export const MainNav = () => {
....
};
App.jsx
import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
import { useState } from "react";
// components
import { AuthNav, MainNav } from './components/Navbar';
// pages
...
...
const AuthLayout = ({ auth }) => (
<>
<AuthNav auth={auth} />
<Outlet />
</>
);
const MainLayout = () => (
<>
<MainNav />
<Outlet />
</>
);
const PrivateRoute = ({ auth }) => {
return auth.isAuthenticated
? <Outlet />
: <Navigate to="/" replace />;
};
const App = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
return (
<div className='parent'>
<Routes>
<Route element={<AuthLayout auth={{ isAuthenticated, setIsAuthenticated }} />}>
<Route path='/' element={<SignIn />} />
<Route path='/signup' element={<SignUp />} />
</Route>
<Route element={<MainLayout />}>
<Route element={<PrivateRoute auth={{ isAuthenticated }} />}>
<Route path='/Home' element={<Home />} />
<Route path='/music' element={<Music />} />
<Route path='/genre/' element={<Pop />} />
<Route path='/Hiphop' element={<HipHop />} />
<Route path='/Rock' element={<Rock />} />
<Route path='/EDM' element={<EDM />} />
<Route path='/Jazz' element={<Jazz />} />
<Route path='/RandB' element={<RandB />} />
<Route path='/store' element={<Store />} />
<Route path='/News' element={<News />} />
<Route path='/Contact' element={<Contact />} />
<Route path='/album/:id' element={<Album />} />
<Route path ="/album/:id/nested/" element={<Albums2 />} />
</Route>
</Route>
</Routes>
</div>
);
};
I am new to React & React-Router#v6.
I was upgrading to react-router v6 from v5.3, and updated my local routes to v6 architecture.
v5 route file
import { lazy } from 'react';
import { Route, Switch } from 'react-router-dom';
const SchoolRoutes = () => {
const login = lazy(() => import('./login/login'));
// Component from Other Library as home page and acts as initial route
const home = lazy(() => import('#portal-school/home').then(({ Home }) => ({ default: Home })));
// Routes from Other libs
const studentRoutes = lazy(() => import('#portal-school/school').then(({ Students }) => ({ default: Students, })));
const activitiesRoutes = lazy(() => import('#portal-school/school').then(({ Activities }) => ({ default: Activities, })));
const reportRoutes = lazy(() => import('#portal-school/reports').then(({ ReportRoutes }) => ({ default: ReportRoutes, })));
return (
<Switch>
<Route exact path="/" component={home} />
<Route exact path="/login" component={login} />
<Route path="/students" component={studentRoutes} />
<Route path="/activities" component={activitiesRoutes} />
<Route path="/school-reports" component={reportRoutes} />
</Switch>
);
};
export default SchoolRoutes;
v6 route file
import React,{ lazy } from 'react';
import { Route, Routes } from 'react-router-dom';
const SchoolRoutes = () => {
const Login = lazy(() => import('./login/login'));
// Below is the home component that to be loaded.
const Home = lazy(() => import('#portal-school/home').then(({ UserLogs }) => ({ default: UserLogs })));
// Below are the routes from other libs to be loaded.
const StudentRoutes = lazy(() => import('#portal-school/school').then(({ Students }) => ({ default: Students, })));
const ActivitiesRoutes = lazy(() => import('#portal-school/school').then(({ Activities }) => ({ default: Activities, })));
const ReportRoutes = lazy(() => import('#portal-school/reports').then(({ ReportRoutes }) => ({ default: ReportRoutes, })));
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/students/*" element={<StudentRoutes />} />
<Route path="/activities/*" element={<ActivitiesRoutes />} />
<Route path="/school-reports/*" element={<ReportRoutes />} />
</Routes>
);
};
export default SchoolRoutes;
Main App component
function App(){
return (
<BrowserRouter>
<AppBar className="app-header">
<div className="school-logo"></div>
</AppBar>
<SideMenu/>
//Injecting Routes Below
<main className="main-blk">
<Suspense fallback={<CircularProgress />}> // Circular Progress is loading
// School Routes file injected below.
<SchoolRoutes/>
</Suspense>
</main>
</BrowserRouter>
)
}
So While trying to access the routes, the home screen is not loading, only Suspense fallback Message Renders, and when I try to access other Route Links, there is the same issue.
Components are not loading through the routes. Can anyone share a solution ?.
Update: I solved by moving the Suspense from App to SchoolRoutes.
Old SchoolRoutes File
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/students/*" element={<StudentRoutes />} />
<Route path="/activities/*" element={<ActivitiesRoutes />} />
<Route path="/school-reports/*" element={<ReportRoutes />} />
</Routes>
Updated SchoolRoutes File
<Suspense fallback={<CircularProgress />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/students/*" element={<StudentRoutes />} />
<Route path="/activities/*" element={<ActivitiesRoutes />} />
<Route path="/school-reports/*" element={<ReportRoutes />} />
</Routes>
<Suspense/>
Currently using react-router-dom 6.1.1 and I'm working with a private route.
Inside this private route I usually had other routes (so that I can keep my Sidebar on them).
My code looks like this
// App.tsx
const RequireAuth: React.FC<PrivateRouteProps> = ({ children, redirectTo }) => {
const isAuthenticated = Auth.isLogedIn()
return isAuthenticated ? children : <Navigate to={redirectTo} />
}
const Page = () => {
return (
<div className={css.host}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/"
element={
<RequireAuth redirectTo="/login">
<Home />
</RequireAuth>
}
/>
</Routes>
</BrowserRouter>
</div>
)
}
// Home/index.tsx
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<Routes>
{routes.map(({ breadCrumbtitle, link, component }, index) => (
<Route path={link} key={index}>
{component ? component : <p>[{breadCrumbtitle}] To be done</p>}
</Route>
))}
</Routes>
</div>
)
}
So... This setup worked with v5 but it seems to be something that doesn't really work with v6.
What can I do if I still want to keep the Sidebar for all the routes once I'm logged in?
I ended up finding the solution to my issue.
Doing what Drew Reese suggested only worked to a certain point since I was being led to a route that, for react router, didn't exist.
For it to work I add to do
// App.tsx
const RequireAuth: React.FC<PrivateRouteProps> = ({ children, redirectTo }) => {
const isAuthenticated = Auth.isLogedIn()
return isAuthenticated ? children : <Navigate to={redirectTo} />
}
const Page = () => {
return (
<div className={css.host}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path=""
element={
<RequireAuth redirectTo="/login">
<Home />
</RequireAuth>
}
>
{routes.map(({ breadCrumbtitle, link, component }, index) => {
return <Route path={link} key={index} element={component}></Route>
})}
</Route>
</Routes>
</BrowserRouter>
</div>
)
}
// Home/index.tsx
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<div className={css.contentContainer}>
<Outlet />
</div>
</div>
)
}
Using the Outlet seemed to be essential, don't know if it's something new on react router v6 but seemed to do the trick!
As far as I can tell the only issue is with the routes mapping, the Route components have invalid children, i.e. you are rendering another Route or React.Fragment as a child.
Move this up to the element prop of the mapped Route components.
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<Routes>
{routes.map(({ breadCrumbtitle, link, component }, index) => (
<Route
path={link}
key={index}
element={component || <p>[{breadCrumbtitle}] To be done</p>}
/>
))}
</Routes>
</div>
);
};
I am working on application where application have two parts one for public and for admin users. I completed for public side but now I am am facing issue on admin side because when I render my 2nd menu for admin public side menu is also showing . Could someone please help me how to handle just 2nd menu let suppose if user hit ( /admin/home ) then I need to show 2nd menu if user hit (/home) then I want to show Ist menu.
Admin Route
<TopMenu>
<PrivateRoute
exact
auth={auth}
path="/dashboard"
currentUser={"admin" || null}
roles={["admin"]}
component={Dashboard}
/>
</TopMenu>
Public Route
<Route exact path="/" render={(props) => <Home {...props} />} />
<Route
exact
path="/about"
render={(props) => <About {...props} />}
/>
<Route
exact
path="/contact"
render={(props) => <Contact {...props} />}
/>
create a Private Route file and check if user is authenticated if yes show that component and if not redirect to some other route
You can do it like this or implement your own way
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({
isAuthenticated,
component: Component,
...rest
}) => (
<Route {...rest} component={(props) => (
isAuthenticated ? (
<div>
<Component {...props} />
</div>
) : (
<Redirect to="/" />
)
)} />
);
export default PrivateRoute
and in the main router file you can import the Private Route and use it like this
import PublicRoute from './routers/PublicRoute';
import Appp from './components/Appp';
import PrivateRoute from './routers/PrivateRoute';
const store = configureStore();
function App() {
return (
<div className="App">
<Provider store={store}>
<Switch>
<PublicRoute path="/" component={HomePage} exact={true} />
<PrivateRoute path="/yourPrivateRoute" component={yourComponent}/>
</Switch>
</Provider>
</div>
);
}
export default App;
React app with react-router-dom: 4.3.1:
Main App.js render:
render() {
let routes = (
<Switch>
<Route component={LogIn} path="/login" />
<Redirect to="/login" />
</Switch>
);
if (this.props.isAuthenticated) {
routes = (
<Switch>
<Route component={ParcelListView} path="/" exact />
<Route component={StatusTable} path="/status" />
<Redirect to="/" />
</Switch>
);
}
return (
<div className="app">
{routes}
</div>
);
}
I see white screen When use this code, but when I assign to routes first or second Switch without if it works perfectly in both cases.
I guess the problem comes from assignment in if block. Is this some kind of async thing?
You might want to set routes inside of a <Switch /> component whatever the scenario and have either public or private route components. Here is a common approach:
const PublicRoute = ({
isAuthenticated,
component: Component,
...rest
}) => (
<Route
{...rest}
component={props => (
isAuthenticated ? (
<Redirect to="/somewhere" />
) : (
<Component {...props} />
))}
/>
);
const PrivateRoute = ({
isAuthenticated,
component: Component,
...rest
}) => (
<Route
{...rest}
component={props => (
isAuthenticated ? (
<div>
<Header />
<Component {...props} />
</div>
) : (
<Redirect to="/login" />
)
)}
/>
);
Both components take component (function) and isAuthenticated(boolean) as props and we pass the rest of the props down ({...rest}) anyway (path etc.)
This way you're able to allow/deny routes based on the propspassed down to your components:
...your code
render() {
<Switch>
<PublicRoute path="/" component={YourPublicComponent} />
<PrivateRoute path="/" isAuthenticated component={ParcelListView} />
</Switch>
}
More at Tyler McGinnis's website: https://tylermcginnis.com/react-router-protected-routes-authentication/
Another post on the subject: https://medium.com/#tomlarge/private-routes-with-react-router-dom-28e9f40c7146
You'll be able to find a lot of stuff on the subject on the web