How to protect routes in React 18? - javascript

I have an admin dashboard and want that only admins are able to see the pages. So I set a condition into my router. When I am logged in, I am able to open every page, but I get the warning:
No routes matched location “/pagename”
Navbar and Sidebar staying in the same position, so that every page opens in a div named ContentWrapper.
How can I get rid of this warning?
Code:
const admin = useAppSelector((state)=>state.auth.user?.isAdmin);
return (
<Container>
<Router>
<Routes>
<Route path="/" element={<Login/>}/>
</Routes>
{admin &&
<>
<Navbar/>
<Wrapper>
<Sidebar/>
<ContentWrapper>
<Routes>
<Route path="/home" element={<Home/>}/>
<Route path="/sales" element={<Sales/>}/>
<Route path="/analytics" element={<Analytics/>}/>
<Route path="/transactions" element={<Transactions/>}/>
<Route path="/reports" element={<Reports/>}/>
<Route path="/mail" element={<Mail/>}/>
<Route path="/feedback" element={<Feedback/>}/>
<Route path="/messages" element={<Messages/>}/>
<Route path="/manage" element={<Manage/>}/>
<Route path="/user" element={<User/>}/>
<Route path="/products" element={<Products/>}/>
<Route path="/productlistChild" element={<ProductlistChild/>}/>
<Route path="/productlistWomen" element={<ProductlistWomen/>}/>
<Route path="/productlistMen" element={<ProductlisttMen/>}/>
<Route path="/productlistSportschuhe" element={<ProductlistSportschuhe/>}/>
<Route path="/productlistSneaker" element={<ProductlistSneaker/>}/>
<Route path="/cardImages" element={<CardImages/>}/>
<Route path="/sneakerImage" element={<SneakerImage/>}/>
<Route path="/sliderImage" element={<SliderImages/>}/>
<Route path="/newsletterBackground" element={<NewsletterBackground/>}/>
<Route path="/descriptionItems" element={<DescriptionItems/>}/>
</Routes>
</ContentWrapper>
</Wrapper>
</>
}
</Router>
</Container>
);
}

Did something similar recently
You can create a PrivateRoute Component
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRouteAdmin = ({ isLoggedIn, role }) => {
return isLoggedIn && role === 'Admin' ? <Outlet /> : <Navigate to="/login" />;
};
export default PrivateRouteAdmin;
and then you can use it in your App.js like this
<Route element={<PrivateRouteAdmin isLoggedIn={isLoggedIn} role={user?.data?.role} />}>
<Route element={<DashboardWrapper />}>
{PublicRoutesAdmin.map((route, index) => {
return <Route key={index} path={route.path} element={<route.component />} />;
})}
</Route>
</Route>
PublicRoutesAdmin.map has just all the routes in a separate file, nothing fancy.
You can have other public routes pout of the private route component

I would resolve this by creating two groups of routes: Private and Public:
const PublicRoutes = () => (
<Fragment>
<Route path="/public/a"><ComponentA /></Route>
<Route path="/public/b"><ComponentB /></Route>
</Fragment>
)
const PrivateRoutes = () => (
<Fragment>
<Route path="/private/a"><PrivComponentA /></Route>
<Route path="/private/b"><PrivComponentB /></Route>
</Fragment>
)
And then have a RootRouter component which conditionally renders either the public or the private routes based on a condition:
const RootRouter = () => {
return (
<Router>
<Routes>
{admin ?
<Fragment>
<PublicRoutes />
<PrivateRoutes />
</Fragment> :
<PublicRoutes />
}
</Routes>
</Router>
)
}
This way, you are rendering the PublicRoutes AND the PrivateRoutes for the admin, but only the PublicRoutes for non-admin users.

Related

How to make home path display from sub-route using React Router Dom?

www.mysite.com
www.mysite.com/
www.mysite.com/1
I want my site to display the same page for each of the above routes. Currently it displays nothing for www.mysite.com and www.mysite.com/
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Products />}>
<Route path="/:id" element={<ProductDisplay />} />
</Route>
</Routes>
</Router>
);
}
Products component
function Products() {
return (
<div className="products">
<Outlet />
</div>
);
}
export default Products;
If you want the ProductDisplay component to render on "/" as well as "/:id" then render an additional index route that will match with the parent route path.
Example:
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Products />}>
<Route index element={<ProductDisplay />} /> // <-- renders on "/"
<Route path="/:id" element={<ProductDisplay />} />
</Route>
</Routes>
</Router>
);
}
See Index Routes for more details.

PrivateRoute in React-router-dom v6 isnt working

I'm trying to use ProtectedRoute as
I can't see why the code isn't working, I'm not getting any error, but at /account it should display <Profile/> and it's blank, I can see the header and footer, but the whole <Profile/> is missing
before trying to use a PrivateRoute, I could display Profile with any problem.
my ProtectedRoute.js
import React from "react";
import { useSelector } from "react-redux";
import { Navigate, Outlet } from "react-router-dom";
const ProtectedRoute = () => {
const {isAuthenticated} = useSelector((state)=>state.user)
return isAuthenticated ? <Outlet /> : <Navigate to="/login"/>
}
export default ProtectedRoute;
my app.js
function App() {
const {isAuthenticated, user} = useSelector(state=>state.user)
React.useEffect(() => {
WebFont.load({
google:{
families: [ "Droid Sans", "Chilanka"],
},
});
store.dispatch(loadUser())
}, []);
return (
<Router>
<Header/>
{isAuthenticated && <UserOptions user={user} />}
<Routes>
<Route exact path="/" element={<Home/>}/>
<Route exact path="/product/:id" element={<ProductDetails/>}/>
<Route exact path="/products" element={<Products/>}/>
<Route path="/products/:keyword" element={<Products/>}/>
<Route exact path="/search" element={<Search/>}/>
<Route exact path="/account" element={<ProtectedRoute/>}/>
<Route exact path="/account" element={<Profile/>}/>
<Route exact path="/login" element={<LoginSignUp/>}/>
</Routes>
<Footer/>
</Router>
);
}
export default App;
and my Profile
const Profile = () => {
const { user, loading, isAuthenticated} = useSelector((state) => state.user);
const navigate = useNavigate();
useEffect(() => {
if(isAuthenticated === false){
navigate("/login");
}
}, [navigate,isAuthenticated])
return (
<Fragment>
<MetaData title={`${user.name}'s Profile`} />
<div className="profileContainer">
<div>
<h1>My Profile</h1>
<img src={user.avatar?.url} alt={user.name} />
<Link to="/me/update">Edit Profile</Link>
</div>
<div>
<div>
<h4>Full Name</h4>
<p>{user.name}</p>
</div>
<div>
<h4>Email</h4>
<p>{user.email}</p>
</div>
<div>
<h4>Joined On</h4>
<p>{String(user.createdAt).substr(0, 10)}</p>
</div>
<div>
<Link to="/orders">My Orders</Link>
<Link to="/password/update">Change Password</Link>
</div>
</div>
</div>
</Fragment>
);
};
export default Profile;
You are rendering two routes for the same "/account" path. ProtectedRoute is rendered on its own self-closing route, so the second route rendering Profile is unreachable.
<Routes>
<Route exact path="/" element={<Home/>}/>
<Route exact path="/product/:id" element={<ProductDetails/>}/>
<Route exact path="/products" element={<Products/>}/>
<Route path="/products/:keyword" element={<Products/>}/>
<Route exact path="/search" element={<Search/>}/>
<Route exact path="/account" element={<ProtectedRoute/>}/>
<Route exact path="/account" element={<Profile/>}/> // <-- unreachable, oops!
<Route exact path="/login" element={<LoginSignUp/>}/>
</Routes>
Remove the path prop from the layout route rendering the ProtectedRoute and ensure it is actually wrapping other Route components. You may as well also remove the exact prop on all the routes as this prop was removed in RRDv6.
Example:
<Router>
<Header/>
{isAuthenticated && <UserOptions user={user} />}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/product/:id" element={<ProductDetails />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:keyword" element={<Products />} />
<Route path="/search" element={<Search />} />
<Route element={<ProtectedRoute />}>
<Route path="/account" element={<Profile />} /> // <-- wrapped by layout route!
</Route>
<Route path="/login" element={<LoginSignUp />} />
</Routes>
<Footer/>
</Router>
The ProtectedRoute component doesn't appear to wait for the user state to populate before rendering either the Outlet for protected content or the redirect. Apply some conditional rendering to render a loading indicator or similar while the user state is populated.
import React from "react";
import { useSelector } from "react-redux";
import { Navigate, Outlet } from "react-router-dom";
const ProtectedRoute = () => {
const user = useSelector((state) => state.user);
if (!user) return null; // <-- or loading indicator, etc...
return user.isAuthenticated
? <Outlet />
: <Navigate to="/login" replace />;
}
export default ProtectedRoute;

Uncaught Error: [RouteWrapper] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

I'm using React Router v6 and am creating private routes for my application.
In file Route.js, I've the code
export default function RouteWrapper({
element: Element,
isPrivate,
...rest
}) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Route {...rest} render={(props) => <Element {...props} />} />;
}
And in file index.js I've written as:
return (
<Routes>
<Route path="/" element={SignIn} />
<Route path="/register" element={SignUp} />
<Route path="/dashboard" element={Dashboard} isPrivate />
<Route path="/profile" element={Profile} isPrivate />
<Route path="/customers" element={Customers} isPrivate />
<Route path="/new" element={New} isPrivate />
<Route path="/new/:id" element={New} isPrivate />
</Routes>
);
}
Is there something I'm missing?
Issue
RouteWrapper isn't a Route component, and fails an invariant check by react-router-dom.
RouteWrapper is directly rendering a Route component, which if the first invariant wasn't failed would trigger another invariant violation. Route components can only be rendered directly by the Routes component or another Route component in the case of building nested routing.
In short, in react-router-dom#6 custom route components are no longer supported. You should instead use wrapper components/layout routes to handle this use case.
Solution
Convert RouteWrapper to a wrapper component that renders an Outlet component for nested routed components to be rendered into.
Example:
import { Navigate, Outlet } from 'react-router-dom';
export default function RouteWrapper({ isPrivate }) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Outlet />; // <-- nested routes render here
}
Wrap the routes you want to protect with the RouteWrapper.
return (
<Routes>
<Route element={<RouteWrapper />}>
<Route path="/" element={<SignIn />} />
<Route path="/register" element={<SignUp />} />
</Route>
<Route element={<RouteWrapper isPrivate />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/customers" element={<Customers />} />
<Route path="/new" element={<New />} />
<Route path="/new/:id" element={<New />} />
</Route>
</Routes>
);
See Layout Routes for further details.
You should convert
<Routes>
<Route exact path="/" element={Dashboard} />
</Routes>
to
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
Also if you want to keep your UI in sync with the URL use like this.
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
</BrowserRouter>
Best.

how do you redirect a user who is not logged in using passport authentication

trying to stop a user from accessing my /mainpage route if they arent logged and to redirect them to login.... currently using passport and react router and components... here is my app js
return (
<Router>
<div>
<Route exact path="/" component={Login} />
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={Signup} />
<Route exact path="/mainpage" component={MainPage} />
<Footer />
</div>
</Router>
);
}
export default App;
this is the html-routes.js part im trying to make work
app.get("/mainpage", isAuthenticated, (req, res) => {
res.sendFile(path.join(__dirname, "/signup.html"));
});
If you had an endpoint to validate their authentication status, as well as some persisted value (such as isAuthenticated) stored in the UI you can set up an auth gate of sorts.
function AuthGate({ children ]} {
return(
<>
{isAuthenticated && validationCheck() ? children : <Login />}
</>
)
}
and then pass render your protected routes as such
function App() {
return(
<Router>
<div>
<Route exact path="/" component={Login} />
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={Signup} />
<AuthGate>
<Route exact path="/mainpage" component={MainPage} />
</AuthGate>
<Footer />
</Router>
)
}
this would ensure that as long as your checks to see if a user was authenticated were true, you would render the children of the AuthGate, otherwise return the Login page. Once a user logs in, as long as there is no redirect logic, they will already be on the Mainpage route and view the content as desired. Any routes not wrapped in the AuthGate would be accessible regardless of authentication status.
You can try
app.get('/mainpage', passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login' }));
1. declare Private Route and add auth logic
// declare Private Route
type PrivateRouteProps = {
children: React.ReactNode
path: string
exact?: boolean
rest?: never
}
const PrivateRoute: React.FC<PrivateRouteProps> = (
{
children,
path,
exact,
...rest
}: PrivateRouteProps) => {
const store = useGlobalStore()
const loggedIn = store.loggedIn
console.log(store.loggedIn, '??')
return (
<Route
path={path}
exact={exact}
{...rest}
render={(props) =>
loggedIn ? (
children
) : (
<Redirect
to={{
pathname: '/login', // eslint-disable-next-line react/prop-types
state: { from: props.location },
}}
/>
)
}
/>
)
}
2. use Private Router
// use Router
const Router: React.FC = () => (
<BrowserRouter>
<Suspense fallback={<PageLoading />}>
<Switch>
<PrivateRoute path="/" exact>
<Redirect to={'/welcome'} />
</PrivateRoute>
<Route path="/login" exact>
<Login />
</Route>
<Route path="/403" exact>
<Error403 />
</Route>
<Route path="/404" exact>
<Error404 />
</Route>
<Route path="/500" exact>
<Error500 />
</Route>
<PrivateRoute path="*">
<LayoutRoutes />
</PrivateRoute>
</Switch>
</Suspense>
</BrowserRouter>
)

How to skip header and footer for certain routes in ReactJS?

I have the following code which renders an app with a header and footer for all pages.
app.js
import React from 'react';
import {
Route,
Switch
} from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router'
import Layout from './components/Layout'
import Home from './homeComponent';
import Login from './loginComponent';
import Dashboard from './dashboardComponent';
const App = ({ history }) => {
return (
<Layout>
<ConnectedRouter history={history}>
<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/dashboard" component={Dashboard} />
... more routes
<Route component={NoMatch} />
</Switch>
</ConnectedRouter>
</Layout>
);
};
export default App;
layout.js
import Header from './headerComponent'
import Footer from './footerComponent'
import React, {Component} from 'react'
class Layout extends Component {
render() {
return (
<div>
<Header />
{this.props.children}
<Footer />
</div>
)
}
}
What is the best way to skip rendering of the header and footer for certain pages like Home and Login routes?
I'd recommend creating two layouts with their own header and footers and a private route:
Public Layout
export const PublicLayout = (props) => <div>
<PublicHeader/>
<Switch>
<Route exact path="/" component={HomePage}/>
<Route exact path='/signin' component={SigninForm} />
<Route exact path='/signup' component={Signup} />
</Switch>
<PublicFooter/>
Protected Layout
export const ProtectedLayout = (props) => <div>
<ProtectedHeader/>
<Switch>
<PrivateRoute exact path='/app/dashboard' component={Dashboard} />
<Route component={NotFound} />
</Switch>
<ProtectedFooter/>
Define high-level routes in app.js:
export default () => {
return <div>
<Switch>
<Route path='/app' component={ProtectedLayout} />
<Route path='/' component={PublicLayout} />
</Switch>
</div>
}
Define PrivateRoute:
export default ({component: Component, ...rest}) => (
<Route {...rest} render={props => (
window.globalState.isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: '/signin',
state: {from: props.location}
}} />
)
)} />
)
Yeah i know a bit late .
Visual studio 2019
import React from 'react';
import { Container } from 'reactstrap';
import NavMenu from '../components/NavMenu';
export default props => (
<div>
{window.location.pathname !== '/login' ? <NavMenu /> : null}
<Container>
{props.children}
</Container>
</div>
);
i hope somebody helps out there.. !!! Happy coding
I made some solution while solving the problem.
First You can wrap the Switch in a website header and footer
<BrowserRouter>
<WebsiteHeader />
<Switch>
<Route/>
<Route/>
<Route/>
</Switch>
<WebsiteFooter/>
<BrowserRouter>
then inside the header or footer wrap the components using withRouter from 'react-router-dom' so you can access the routes props
const WebsiteHeader = props => {
if (props.location.pathname == "/register") return null;
return (
<Fragment>
<DesktopHeader {...props} />
<MobileHeader {...props} />
</Fragment>
);
};
export default withRouter(WebsiteHeader);
Use render
<ConnectedRouter history={history}>
<Switch>
<Route path="/dashboard" render={props => <Layout><Dashboard {...props} /></Layout>} />
<Route path="/login" component={Login} />
</Switch>
</ConnectedRouter>
For forcefully refresh Header inside routing.
use forceRefresh={true}
const Routing = () => {
return(
<BrowserRouter forceRefresh={true}>
<Header/>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/list/:id" component={ListingApi}/>
<Route path="/details/:id" component={HotelDetails}/>
<Route path="/booking/:hotel_name" component={PlaceBooking}/>
<Route path="/viewBooking" component={ViewBooking}/>
<Route exact path="/login" component={LoginComponent}/>
<Route path="/signup" component={RegisterComponent}/>
</Switch>
<Footer/>
</BrowserRouter>
)
}
I would just create a few different layouts one with header and footer and one without. And then instead of wrapping everything into one layout. I'd just do this wrapping inside each page component. So your components would be like:
Dashboard component
<SimpleLayout>
<Dashboard>
</SimpleLayout>
Home component
<MainLayout>
<Home>
</MainLayout>
Try like this
<Route path="/" render={(props) => (props.location.pathname !== "/login") &&
<Header />}>
</Route>
<Route path="/" render={(props) => (props.location.pathname !== "/login") &&
<Menu />}>
</Route>
<PrivateRoute path="/scope" component={Scope} ></PrivateRoute>
<Route exact path="/login" component={Login} />
In this example I'm checking the URL, If the URL is "/Login" I'm removing Menu and header component
For forcefully refresh Header inside routing.
const Routing = () => {
return(
<BrowserRouter forceRefresh={true}>
<Header/>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/list/:id" component={ListingApi}/>
<Route path="/details/:id" component={HotelDetails}/>
<Route path="/booking/:hotel_name" component={PlaceBooking}/>
<Route path="/viewBooking" component={ViewBooking}/>
<Route exact path="/login" component={LoginComponent}/>
<Route path="/signup" component={RegisterComponent}/>
</Switch>
<Footer/>
</BrowserRouter>
)
}

Categories

Resources