This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 12 months ago.
I am using react router v6 and creating a private/protected route. Once the user is authenticated and when i refresh the page it shows the following error in console.
[ProtectedRoute] is not a component. All component children of must be a or <React.Fragment>
This is my ProtectedRoute.js file
import React, { Fragment } from "react";
import { useSelector } from "react-redux";
import { Navigate, Route } from "react-router-dom";
const ProtectedRoute = ({ element: Element, ...rest }) => {
const { loading, isAuthenticated, user } = useSelector((state) => state.user);
return (
<Fragment>
{!loading && (
<Route
{...rest}
render={(props) => {
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return <Element {...props} />;
}}
/>
)}
</Fragment>
);
};
export default ProtectedRoute;
And this is my App.js
import "./App.css";
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import WebFont from "webfontloader";
import Header from "./component/layout/Header/Header";
import Footer from "./component/layout/Footer/Footer";
import Home from "./component/Home/Home.js";
import ProductDetails from "./component/Product/ProductDetails.js";
import Products from "./component/Product/Products.js";
import LoginSignUp from "./component/user/LoginSignUp";
import Profile from "./component/user/Profile.js";
import store from "./store";
import { loadUser } from "./actions/userAction";
import UserOptions from "./component/layout/Header/UserOptions.js";
import { useSelector } from "react-redux";
import ProtectedRoute from "./component/Route/ProtectedRoute";
function App() {
const { isAuthenticated, user } = useSelector((state) => state.user);
React.useEffect(() => {
WebFont.load({
google: {
families: ["Roboto", "Droid Sans", "Chilanka"],
},
});
store.dispatch(loadUser());
}, []);
return (
<Router>
<Header />
{isAuthenticated && <UserOptions user={user} />}
<Routes>
<ProtectedRoute path="/account" element={<Profile />} />
<Route path="/login" element={<LoginSignUp />} />
<Route path="/products/:keyword" element={<Products />} />
<Route path="/products" element={<Products />} />
<Route path="/product/:id" element={<ProductDetails />} />
<Route path="/" element={<Home />} />
</Routes>
<Footer />
</Router>
);
}
export default App;
In Protected.js
import React, { Fragment } from "react";
import { useSelector } from "react-redux";
import { Navigate } from "react-router-dom";
const ProtectedRoute = ({ children }) => {
const { loading, isAuthenticated } = useSelector((state) => state.user);
if (loading) return null;
return isAuthenticated ? children : <Navigate to="/login" replace />;
};
export default ProtectedRoute;
In App.js
<Route
path="/account"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
Related
I'm trying to get an id from the useParams, but getting Property employeeId does not exist on type error and I don't understand why.
Here is the routes.tsx
//I omitted all the imports here
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Outlet } from 'react-router-dom';
const DefaultContainer = (): JSX.Element => (
<div>
<div id="header">
<Header />
</div>
<div>
<div id="main-container">
<Outlet />
</div>
</div>
</div>
);
const AllRoutes = () => (
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/" element={<DefaultContainer />}>
<PrivateRoute path="/" element={<Home />} />
<PrivateRoute path="/e/employees" element={<EmployeeList />} />
<PrivateRoute path="/e/add" element={<AddEmployee />} />
<PrivateRoute path="/e/:employeeId" element={<Employee />} />
<PrivateRoute path="/e/update/:employeeId" element={<UpdateEmployee />} />
</Route>
</Routes>
</Router>
);
export default AllRoutes;
Here is my UpdateEmployee.component.tsx
import React from 'react';
import { useParams } from 'react-router';
const UpdateEmployee = () => {
const { employeeId } = useParams();
console.log(employeeId);
return <h1>hello world</h1>
}
In the routes.tsx there is /e/:employeeId it is working fine.
I also tried adding type for employeeId as string, but still no luck.
Really appreciate your help.
PrivateRoutes.component.tsx
import React from 'react';
import { Navigate, Route } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from '../../hooks/reduxHooks';
import { setUser } from '../../services/reducers/Auth.reducer';
const PrivateRoute = ({ element, ...rest }) => {
const dispatch = useAppDispatch();
const localStorageData = localStorage.getItem('user');
const user = localStorageData ? JSON.parse(localStorageData) : null;
if (!user) {
return <Navigate to="/login" />;
}
// This will reload the user from localstorage in redux state.
const stateUser = useAppSelector((state) => state.auth.user);
if (!stateUser) {
dispatch(setUser(user));
}
return <Route element={element} {...rest} />;
};
export default PrivateRoute;
Try with setting to any type, this worked for me.
const { employeeId } : any = useParams();
So after user is logged in, getProfile() is called to get their profile information.
and by their role, users will be navigated to user page or admin page.
In order to do that, I made ProtectedRoute component.
But each user page and admin page are not appeared when I use this component.(only white screen is shown.)
I've checked the getProfile() in ProtectedRoute component is receiving result well.
but it seems like <Layout {...props} /> or <AdminLayout {...props} /> is not rendered.
Here's the codes.
protectedRoute.js : Filter users by their roles and navigate them to proper page.
import React, {useEffect} from 'react';
import {BrowserRouter as Router, Switch, Route, Redirect} from 'react-router-dom';
import Layout from './layouts/layout';
import AdminLayout from './layouts/adminLayout';
import {useDispatch} from 'react-redux';
import {getProfile} from './data_actions/userInfo_action/userInfo_action';
import {unwrapResult} from '#reduxjs/toolkit';
import {useHistory} from 'react-router-dom';
export const ProtectedRoute = ({component: Component, admin = null, ...rest}) => {
const dispatch = useDispatch();
return (
<Route
{...rest}
path={rest.path}
render={(props) => {
dispatch(getProfile())
.then(unwrapResult)
.then((res) => {
if ((res.role === 'PRO' || res.role === 'CLIENT') && !admin) {
<Layout {...props} />;
} else if (res.role === 'ADMIN' && admin) {
<AdminLayout {...props} />;
}
})
.catch((err) => {
return (
<Redirect
to={{
pathname: '/',
state: {
from: props.location,
},
}}
/>
);
});
}}
/>
);
};
App.jsx : Where protectedRoute component is placed.
import React, {useContext, useEffect, useRef, useState} from 'react';
import Layout from './layouts/layout';
import AdminLayout from './layouts/adminLayout';
import './App.scss';
import {BrowserRouter as Router, Switch, Route, Redirect} from 'react-router-dom';
import LoginPage from '#/pages/user/sign/login';
import NotFound from './pages/notFound';
import {ProtectedRoute} from './protectedRoute';
function App() {
return (
<Router>
<Switch>
{/* login */}
<Route exact path={`/fn/sign/login`} render={(props) => <LoginPage {...props} />} />
<ProtectedRoute exact path={`${process.env.PUBLIC_URL}`} component={Layout} admin={false} />
<ProtectedRoute
exact
path={`${process.env.PUBLIC_URL}/admin`}
component={AdminLayout}
admin={true}
/>
<Route path="*" component={NotFound} />
</Switch>
</Router>
);
}
export default App;
layout.jsx : Layout Component that contains user's router.
import React, {memo, useContext, useEffect} from 'react';
import PropTypes from 'prop-types';
import Drawer from '#material-ui/core/Drawer';
import Hidden from '#material-ui/core/Hidden';
import {makeStyles, useTheme} from '#material-ui/core/styles';
import SideBar from './sidebar';
import './layout.scss';
import Router from '../routes/router';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
},
toolbar: {
[theme.breakpoints.up(theme.breakpoints.values.md)]: {
display: 'none',
},
},
content: {
width: '100%',
flexGrow: 1,
},
}));
function Layout(props) {
const classes = useStyles();
return (
<div className={classes.root}>
<nav className={classes.drawer}>
<Hidden mdDown implementation="css">
<Drawer
className={classes.drawer}
classes={{
paper: classes.drawerPaper,
}}
variant="permanent"
open
>
<SideBar />
</Drawer>
</Hidden>
</nav>
<main className={classes.content}>
<div className={classes.toolbar} />
<Router {...props} />
</main>
</div>
);
}
Layout.propTypes = {
window: PropTypes.func,
};
export default memo(Layout);
router.js : user's pages
import React, {useEffect} from 'react';
import {Switch, Route, Redirect} from 'react-router-dom';
import JobList from '#/pages/user/jobNotiBoard/jobList';
import JobDetail from '#/pages/user/jobNotiBoard/jobDetail';
import NotFound from '#/pages/notFound';
import {useDispatch, useSelector} from 'react-redux';
import {setNewCount2} from '#/data_actions/jobNoti_action/newDataCount_action';
function Router(props) {
const dispatch = useDispatch();
const newDataCount = useSelector((state) => state.newJobNotiPostCount);
useEffect(() => {
dispatch(setNewCount2());
}, []);
return (
<React.Fragment>
<Switch>
<Route
exact
path={`${process.env.PUBLIC_URL}/joblist`}
render={(props) => <JobList {...props} newDataCount={newDataCount?.count} />}
/>
<Route
path={`${process.env.PUBLIC_URL}/detail/:id/:site`}
render={(props) => <JobDetail {...props} />}
/>
<Route path="*" component={NotFound} />
</Switch>
</React.Fragment>
);
}
export default Router;
The render prop in your ProtectedRoute does not return anything.
You will have to change it to something like
export const ProtectedRoute = ({
component: Component,
admin = null,
...rest
}) => {
const dispatch = useDispatch();
const [role, setRole] = React.useState(null);
React.useEffect(
() => {
dispatch(getProfile())
.then(unwrapResult)
.then((res) => {
setRole(res.role);
})
.catch((err) => {
setRole("PROFILE_ERROR");
});
},
[ /* dependencies for when to re-request the profile*/ ]
);
return (
<Route
{...rest}
path={rest.path}
render={(props) => {
if ((role === "PRO" || role === "CLIENT") && !admin) {
return <Layout {...props} />;
} else if (role === "ADMIN" && admin) {
return <AdminLayout {...props} />;
} else if (role === "PROFILE_ERROR") {
return (
<Redirect
to={{
pathname: "/",
state: {
from: props.location
}
}}
/>
);
}
}}
/>
);
};
How do I fix my private route to account for the emailVerification page?
Currently, if currentUser.user and currentUser.isVerified it will route to the home page, else it will route to the login page.
The problem, If currentUser.user is true and currentUser.isVerified is false, I want it to route to /emailVerification, not login. And then once currentUser.isVerified is also true, I want it to route to the home page.
Been stuck on this for a couple days any help is appreciated.
PrivateRoute.jsx
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import propTypes from 'prop-types';
import { useAuth } from '../Contexts/AuthContext';
const PrivateRoute = ({ children, ...rest }) => {
const { currentUser } = useAuth();
console.log(currentUser, 'here');
return (
<Route {...rest}>
{currentUser.user && currentUser.isVerified ? children : <Redirect to="/login" />}
</Route>
);
};
export default PrivateRoute;
PrivateRoute.propTypes = {
children: propTypes.node.isRequired,
};
App.jsx
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import { AuthProvider } from './Contexts/AuthContext';
import GridContainer, { Container, Row, Col } from './Components/Grid';
import Navbar from './Components/Navbar';
import Login from './Pages/Authentication/Login';
import Signup from './Pages/Authentication/Signup';
import ForgotPassword from './Pages/Authentication/ForgotPassword';
import EmailVerification from './Pages/Authentication/EmailVerification';
import Tabs from './Components/Tabs';
import Dashboard from './Pages/Portal/Dashboard';
import PrivateRoute from './Components/PrivateRoute';
import Home from './Pages/Home';
const App = () => {
const isAuthenticated = false;
return (
<AuthProvider>
<Container>
{!isAuthenticated ? (
<Row>
<Col lg={12}>
<Navbar />
</Col>
</Row>
) : null}
<Router>
<Col lg={12}>
<Switch>
<PrivateRoute path="/home">
<Home />
</PrivateRoute>
<Route path="/login">
<Login />
</Route>
<Route path="/signup">
<Signup />
</Route>
<Route path="/emailVerification">
<EmailVerification />
</Route>
<Route path="/forgotPassword">
<ForgotPassword />
</Route>
<Route path="/">
<Login />
</Route>
</Switch>
</Col>
</Router>
</Container>
</AuthProvider>
);
};
export default App;
I would rewrite te code to something like this:
import React from "react";
import { Route, Redirect } from "react-router-dom";
import propTypes from "prop-types";
import { useAuth } from "../Contexts/AuthContext";
const PrivateRoute = ({ children, ...rest }) => {
const { currentUser } = useAuth();
console.log(currentUser, "here");
if (currentUser.user && !currentUser.isVerified) {
<Route {...rest}>
{currentUser.user && currentUser.isVerified ? (
children
) : (
<Redirect to="/emailVerification" />
)}
</Route>;
} else {
return (
<Route {...rest}>
{currentUser.user && currentUser.isVerified ? (
children
) : (
<Redirect to="/login" />
)}
</Route>
);
}
};
export default PrivateRoute;
PrivateRoute.propTypes = {
children: propTypes.node.isRequired,
};
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import { library } from "#fortawesome/fontawesome-svg-core";
import {
faHome,
faClock,
faTasks,
faStickyNote,
faCalendarWeek
} from "#fortawesome/free-solid-svg-icons";
import { connect } from "react-redux";
import store from "./store";
import { loadUser } from "./actions/authActions";
import Home from "./Home";
import SideNav from "./Components/SideNav";
import Recent from "./Components/Recent";
import TopBar from "./Components/TopBar";
import AddNote from "./AddNote";
import LogIn from "./Components/LogIn/LogIn.js";
import Register from "./Components/Register/Register";
import ToDo from "./Components/ToDo/ToDo";
import { timingSafeEqual } from "crypto";
library.add(faHome, faClock, faTasks, faStickyNote, faCalendarWeek);
class App extends Component {
componentDidMount() {
store.dispatch(loadUser());
}
componentDidUpdate(prevProps) {
if(this.props != this.prevProps) {
console.log("hello")
}
}
LogInContainer = () => {
return <Route path="/login" component={LogIn} />;
};
RegisterContainer = () => {
return <Route path="/register" component={Register} />;
};
DefaultContainer = () => {
return (
<div className="app_container">
<SideNav />
<TopBar />
<Route exact path="/" component={Home} />
<Route path="/recent" component={Recent} />
<Route path="/AddNote" component={AddNote} />
<Route path="/ToDo" component={ToDo} />
</div>
);
};
// Check for authenticaition
AuthRoute = ({ component: Component, props, ...rest }) => {
return (
<Route
{...rest}
render={props => {
if (this.props.auth.isAuthenticated) {
return <Component {...props} />;
}
else {
return (
<Redirect
to={{
pathname: "/login",
state: { from: this.props.location }
}}
/>
);
}
}}
/>
);
};
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/login" component={this.LogInContainer} />
<Route exact path="/register" component={this.RegisterContainer} />
<this.AuthRoute component={this.DefaultContainer} />
</Switch>
</BrowserRouter>
);
}
}
const mapStateToProps = (state) => ({
auth: state.auth
})
export default connect(mapStateToProps)(App);
How can app.js receive the new state from redux after logging in? The initial fetch it will get isAuthenticated = false. User then log ins but app.js isn't getting the new state. Am I implementing authenitcation wrong? comonentDidUpdate is throwing an error when trying to update props but feel like this is a bad way of doing it anyways
I have routes with single and multiple components.I am using authguard to restrict access to some routes.How can I apply the authguard to the routes having multiple components.
routes.js
import { BrowserRouter as Router,Route} from 'react-router-dom';
import React from 'react';
import { FirstConnectedComponent,SecondConnectedComponent } from './App.js';
import Header from './components/header.js';
import Footer from './components/footer.js';
import Login from './components/login.js';
import UserReg from './components/registration.js';
import Home from './components/home';
import requireAuth from './components/authentication';
import PrivateRoute from './components/privateroutes';
const routes=() => (
<Router>
<div>
<Header />
<Route exact path="/" render={ props => <div><FirstConnectedComponent /><SecondConnectedComponent /></div>} />
<Route path="/login" component={PrivateRoute(Login) } />
<Route path="/register" component={ UserReg } />
<Route path="/home" component={ requireAuth(Home)} />
<Footer />
</div>
</Router>
)
export default routes;
In the above code I already applying authguard to routes having single components.But I don't know how to be applied into routes having multiple components.
privateroute
import { connect } from 'react-redux';
import React from 'react';
import { withRouter } from 'react-router';
export default function PrivateRoute(Component) {
class AuthenticatedComponent extends React.Component {
componentWillMount() {
console.log(this.props.loginStatus);
this.checkAuth();
}
checkAuth() {
if (this.props.loginStatus==1) {
this.props.history.push(`/home`);
}
}
render() {
return this.props.loginStatus!=1
? <Component { ...this.props } />
: null;
}
}
function mapStateProps(state) {
return {
loginStatus:state.loginDetails.status
}
}
return connect(mapStateProps)(withRouter(AuthenticatedComponent));
}
Instead of using render prop use the component prop as you have used the same in PrivateRoute and wrap the render function with PrivateRoute HOC like
const routes=() => (
<Router>
<div>
<Header />
<Route exact path="/" component={PrivateRoute(props => <div><FirstConnectedComponent /><SecondConnectedComponent /></div>)} />
<Route path="/login" component={PrivateRoute(Login) } />
<Route path="/register" component={ UserReg } />
<Route path="/home" component={ requireAuth(Home)} />
<Footer />
</div>
</Router>
)
Solution for v6 newer version of react-router-dom
If useNavigate not working, use useHistory
App.js
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import AuthGuard from "./Routes/AuthGuard";
function App() {
return (
<div className='App'>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/contact' element={<Contact />} />
<Route path='/guest-page' element={<AuthGuard isGuest={true}><h1>Guest Page</h1></AuthGuard>} />
<Route path='/protected-page' element={<AuthGuard requireToken={true}><h1>ProtectedPage</h1></AuthGuard>} />
</Routes>
</Router>
</div>
);
}
export default App;
AuthGuard.js
import { Route, useNavigate } from "react-router-dom";
import { useLayoutEffect } from "react";
const ProtectedRoute = ({requireToken, guest, children, ...rest}) => {
console.log(requireToken, guest, children, rest);
const navigate = useNavigate();
const hasToken = false;
let hasAccess = (!requireToken || (requireToken && hasToken));
let navigateTo = '/?login-rquired=true';
if(guest) {
hasAccess = !hasToken;
navigateTo = '/?guest=true';
console.log("Guest > hasAccess: " + hasAccess)
}
if(requireToken){
console.log("requireToken", requireToken)
}
useLayoutEffect(()=>{
if (!hasAccess) {
console.log("Not allowed");
navigate(navigateTo);
}
},[])
return (
<>{ hasAccess ? children : "Login required" }</>
)
}
export default ProtectedRoute;