I'm using React Router v6 and am creating private routes for my application.
In file PrivateRoute.js, I've the code
import React from 'react';
import {Route,Navigate} from "react-router-dom";
import {isauth} from 'auth'
function PrivateRoute({ element, path }) {
const authed = isauth() // isauth() returns true or false based on localStorage
const ele = authed === true ? element : <Navigate to="/Home" />;
return <Route path={path} element={ele} />;
}
export default PrivateRoute
And in file route.js I've written as:
...
<PrivateRoute exact path="/" element={<Dashboard/>}/>
<Route exact path="/home" element={<Home/>}/>
I've gone through the same example React-router Auth Example - StackBlitz, file App.tsx
Is there something I'm missing?
I ran into the same issue today and came up with the following solution based on this very helpful article by Andrew Luca
In PrivateRoute.js:
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoute = () => {
const auth = null; // determine if authorized, from context or however you're doing it
// If authorized, return an outlet that will render child elements
// If not, return element that will navigate to login page
return auth ? <Outlet /> : <Navigate to="/login" />;
}
In App.js (I've left in some other pages as examples):
import './App.css';
import React, {Fragment} from 'react';
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom';
import Navbar from './components/layout/Navbar';
import Home from './components/pages/Home';
import Register from './components/auth/Register'
import Login from './components/auth/Login';
import PrivateRoute from './components/routing/PrivateRoute';
const App = () => {
return (
<Router>
<Fragment>
<Navbar/>
<Routes>
<Route exact path='/' element={<PrivateRoute/>}>
<Route exact path='/' element={<Home/>}/>
</Route>
<Route exact path='/register' element={<Register/>}/>
<Route exact path='/login' element={<Login/>}/>
</Routes>
</Fragment>
</Router>
);
}
In the above routing, this is the private route:
<Route exact path='/' element={<PrivateRoute/>}>
<Route exact path='/' element={<Home/>}/>
</Route>
If authorization is successful, the element will show. Otherwise, it will navigate to the login page.
Only Route components can be a child of Routes. If you follow the v6 docs then you'll see the authentication pattern is to use a wrapper component to handle the authentication check and redirect.
function RequireAuth({ children }: { children: JSX.Element }) {
let auth = useAuth();
let location = useLocation();
if (!auth.user) {
// Redirect them to the /login page, but save the current location they were
// trying to go to when they were redirected. This allows us to send them
// along to that page after they login, which is a nicer user experience
// than dropping them off on the home page.
return <Navigate to="/login" state={{ from: location }} />;
}
return children;
}
...
<Route
path="/protected"
element={
<RequireAuth>
<ProtectedPage />
</RequireAuth>
}
/>
The old v5 pattern of create custom Route components no longer works. An updated v6 pattern using your code/logic could look as follows:
const PrivateRoute = ({ children }) => {
const authed = isauth() // isauth() returns true or false based on localStorage
return authed ? children : <Navigate to="/Home" />;
}
And to use
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
Complement to reduce lines of code, make it more readable and beautiful.
This could just be a comment but I don't have enough points, so I'll
put it as an answer.
Dallin's answer works but Drew's answer is better! And just to complete Drew's answer on aesthetics, I recommend creating a private component that takes components as props instead of children.
Very basic example of private routes file/component:
import { Navigate } from 'react-router-dom';
const Private = (Component) => {
const auth = false; //your logic
return auth ? <Component /> : <Navigate to="/login" />
}
Route file example:
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/user" element={<Private Component={User} />} />
</Routes>
I know that this is not exactly the recipe on how to make PirvateRoute work, but I just wanted to mention that the new documentation recommends a slightly different approach to handle this pattern with react-router v6:
<Route path="/protected" element={<RequireAuth><ProtectedPage /></RequireAuth>} />
import { Navigate, useLocation } from "react-router";
export const RequireAuth: React.FC<{ children: JSX.Element }> = ({ children }) => {
let auth = useAuth();
let location = useLocation();
if (!auth.user) {
return <Navigate to="/login" state={{ from: location }} />;
}
return children;
};
And you are supposed to add more routes inside ProtectedPage itself if you need it.
See the documentation and an example for more details. Also, check this note by Michael Jackson that goes into some implementation details.
Just set your router component to element prop:
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
You can also check for upgrading from v5.
Remove the PrivateRoute component from your project and use the following code in your App.js files:
import {Navigate} from "react-router-dom";
import {isauth} from 'auth'
...
<Route exact path="/home" element={<Home/>}/>
<Route exact path="/" element={isauth ? <Dashboard/> : <Navigate to="/Home" />}/>
It's 2022 and I did something like below:
// routes.tsx
import { lazy } from "react";
import { Routes, Route } from "react-router-dom";
import Private from "./Private";
import Public from "./Public";
const Home = lazy(() => import("../pages/Home/Home"));
const Signin = lazy(() => import("../pages/Signin/Signin"));
export const Router = () => {
return (
<Routes>
<Route path="/" element={Private(<Home />)} />
<Route path="/signin" element={Public(<Signin />)} />
</Routes>
);
};
// Private.tsx
import { Navigate } from "react-router-dom";
import { useEffect, useState } from "react";
function render(c: JSX.Element) {
return c;
}
const Private = (Component: JSX.Element) => {
const [hasSession, setHasSession] = useState<boolean>(false);
useEffect(() => {
(async function () {
const sessionStatus = await checkLoginSession();
setHasSession(Boolean(sessionStatus));
})();
}, [hasSession, Component]);
return hasSession ? render(Component) : <Navigate to="signin" />;
};
export default Private;
Hope this helps!
React Router v6, some syntactic sugar:
{auth && (
privateRoutes.map(route =>
<Route
path={route.path}
key={route.path}
element={auth.isAuthenticated ? <route.component /> : <Navigate to={ROUTE_WELCOME_PAGE} replace />}
/>
)
)}
I tried all answers, but it always displayed the error:
Error: [PrivateRoute] is not a component. All component children of must be a or <React.Fragment>
But I found a solution ))) -
In PrivateRoute.js file:
import React from "react"; import { Navigate } from "react-router-dom";
import {isauth} from 'auth'
const PrivateRoute = ({ children }) => {
const authed = isauth()
return authed ? children : <Navigate to={"/Home" /> };
export default ProtectedRoute;
In the route.js file:
<Route
path="/"
element={
<ProtectedRoute >
<Dashboard/>
</ProtectedRoute>
}
/>
<Route exact path="/home" element={<Home/>}/>
Children of Routes need to be Route elements, so we can change the ProtectedRoute:
export type ProtectedRouteProps = {
isAuth: boolean;
authPath: string;
outlet: JSX.Element;
};
export default function ProtectedRoute({
isAuth,
authPath,
outlet,
}: ProtectedRouteProps) {
if (isAuth) {
return outlet;
} else {
return <Navigate to={{pathname: authPath}} />;
}
}
And then use it like this:
const defaultProps: Omit<ProtectedRouteProps, 'outlet'> = {
isAuth: //check if user is authenticated,
authPath: '/login',
};
return (
<div>
<Routes>
<Route path="/" element={<ProtectedRoute {...defaultProps} outlet={<HomePage />} />} />
</Routes>
</div>
);
This is the simple way to create a private route:
import React from 'react'
import { Navigate } from 'react-router-dom'
import { useAuth } from '../../context/AuthContext'
export default function PrivateRoute({ children }) {
const { currentUser } = useAuth()
if (!currentUser) {
return <Navigate to='/login' />
}
return children;
}
Now if we want to add a private route to the Dashboard component we can apply this private route as below:
<Routes>
<Route exact path="/" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
</Routes>
For longer elements
<Router>
<div>
<Navbar totalItems={cart.total_items}/>
<Routes>
<Route exact path='/'>
<Route exact path='/' element={<Products products={products} onAddToCart={handleAddToCart}/>}/>
</Route>
<Route exact path='/cart'>
<Route exact path='/cart' element={<Cart cart={cart}/>}/>
</Route>
</Routes>
</div>
</Router>
Header will stay on all page
import React from 'react';
import {
BrowserRouter,
Routes,
Route
} from "react-router-dom";
const Header = () => <h2>Header</h2>
const Dashboard = () => <h2>Dashboard</h2>
const SurveyNew = () => <h2>SurveyNew</h2>
const Landing = () => <h2>Landing</h2>
const App = () =>{
return (
<div>
<BrowserRouter>
<Header />
<Routes >
<Route exact path="/" element={<Landing />} />
<Route path="/surveys" element={<Dashboard />} />
<Route path="/surveys/new" element={<SurveyNew/>} />
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
<Route path='/' element={<Navigate to="/search" />} />
You can use a function for a private route:
<Route exact path="/login" element={NotAuth(Login)} />
<Route exact path="/Register" element={NotAuth(Register)} />
function NotAuth(Component) {
if (isAuth)
return <Navigate to="/" />;
return <Component />;
}
I'm using "react-router-dom": "^6.3.0" and this is how I did mine
PrivateRoute Component and Route
import {Route} from "react-router-dom";
const PrivateRoute = ({ component: Compontent, authenticated }) => {
return authenticated ? <Compontent /> : <Navigate to="/" />;
}
<Route
path="/user/profile"
element={<PrivateRoute authenticated={true} component={Profile} />} />
For the error "[Navigate] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>", use the following method maybe solved:
DefaultPage is when no match router. Jump to the DefaultPage. Here use the <Route index element={} /> to replace the
<Navigate to={window.location.pathname + '/kanban'}/>
See Index Routes
<Routes>
<Route path={'/default'} element={<DefaultPage/>}/>
<Route path={'/second'} element={<SecondPage/>}/>
{/* <Navigate to={window.location.pathname + '/kanban'}/> */}
<Route index element={<DefaultPage/>} />
</Routes>
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<h1>home page</h1>} />
<Route path="/seacrch" element={<h1>seacrch page</h1>} />
</Routes>
</Router>
);
}
export default App;
I would like to refresh the current page (home) when the user tries to go back via browser, after logged in.
What's the best way to solve this? Any suggestions?
I was trying to do something like this inside index.tsx:
if (id) {
const rollback = history.goBack();
if (rollback) {
history.push('/');
}
}
Obs: In this case, '/' is my home page, and i can't apply the logic above because "An expression of type 'void' cannot be tested for truthiness".
Sorry for anything i'm still new at react and trying to learn.
Don't know if i could do something inside my router, here it is anyway:
import React, { Suspense, lazy } from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import history from '../utils/history';
import LoadingPage from '../components/organisms/LoadingPage';
const DownloadExams = lazy(() => import('../pages/private/DownloadExams'));
const Home = lazy(() => import('../pages/private/Home'));
const ProfileSelector = lazy(() => import('../pages/private/ProfileSelector'));
const AppRoutes = () => {
return (
<Router history={history}>
<Suspense fallback={<LoadingPage />}>
<Switch>
<Route exact path={'/'} component={Home} />
<Route exact path={'/baixar-exames'} component={DownloadExams} />
<Route exact path={'/profile'} component={ProfileSelector} />
</Switch>
</Suspense>
</Router>
);
};
export default AppRoutes;
Any suggestions?
Thanks!
User logIn time you can store a token or flag and store it in localStorage. After that, you can check if the user login so redirects to the page. You can also create some HOC for the same.
Example :
import React from "react";
import { Route, Redirect } from "react-router-dom";
export const ProtectedRoute = ({ component: Component, ...rest }) => {
const isLoggedIn = localStorage.getItem("token");
return (
<Route
{...rest}
render={(props) => {
if (isLoggedIn) {
return <Component {...props} />;
} else {
return (
<Redirect
to={{
pathname: "/",
state: {
from: props.location,
},
}}
/>
);
}
}}
/>
);
};
Example Usage :
<ProtectedRoute path="/home" exact component={Home} />
This will redirect the user to /home after login.
I am trying to change the contents of my Navbar and my Router via useContext and conditional rendering.
This is my App.js:
import "./App.css";
import axios from "axios";
import { AuthContextProvider } from "./context/AuthContext";
import MyRouter from "./MyRouter";
axios.defaults.withCredentials = true;
function App() {
return (
<AuthContextProvider>
<MyRouter />
</AuthContextProvider>
);
}
export default App;
This is my Router:
import React, { useContext } from "react";
import AuthContext from "./context/AuthContext";
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from "react-router-dom";
import MyNavBar from "./components/MyNavbar";
import "./App.css";
import Home from "./components/pages/Home";
import AboutUs from "./components/pages/AboutUs";
import Register from "./components/pages/Register";
import MyFooter from "./components/MyFooter";
import login from "./components/pages/login";
import ProfilePage from "./components/pages/ProfilePage";
function MyRouter() {
const { loggedIn } = useContext(AuthContext);
return (
<Router>
<MyNavBar />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/about-us" component={AboutUs} />
{loggedIn === false && (
<>
<Route exact path="/register" component={Register} />
<Route exact path="/login" component={login} />
</>
)}
{loggedIn === true && (
<>
<Route exact path="/profile" component={ProfilePage} />
</>
)}
<Redirect to="404" />
</Switch>
<MyFooter />
</Router>
);
}
export default MyRouter;
My Navbar's conditional rendering works in the same way as the router. My problem is that neither of the conditional rendering fragments are working. For example, when my application starts, users are not logged in and the "loggedIn" value is false. With this logic, the routes "register" and "login" should be accessible, but they are not. Any advice or help would be greatly appreciated as I am quite new to using React.
Here is a screenshot of my console upon loading the application
This is my "AuthContext":
const AuthContext = createContext();
function AuthContextProvider(props) {
const [loggedIn, setLoggedIn] = useState(undefined);
async function getLoggedIn() {
const loggedInRes = await axios.get(
"http://localhost:5000/api/users/loggedIn"
);
setLoggedIn(loggedInRes.data);
}
useEffect(() => {
getLoggedIn();
}, []);
return (
<AuthContext.Provider value={{ loggedIn, getLoggedIn }}>
{props.children}
</AuthContext.Provider>
);
}
export default AuthContext;
export { AuthContextProvider };
Thanks for all the help guys, I learned a lot about the ProtectedRoute concept. My issue came from my Navigation Bar component. Instead of declaring my variable as an object, I declared loggedin like
const loggedIn = useContext(AuthContext);
All my errors were solved once I changed it to
const { loggedIn } = useContext(AuthContext);
Edited:
Instead of using your conditions in your main route, you can create a ProtectedRoute which Routes your component according to your condition.
import React, { useEffect } from "react";
import { Route, Redirect, useHistory } from "react-router-dom";
const ProtectedRoute = ({ component: Component, ...rest }) => {
const { loggedIn } = useContext(AuthContext);
return (
<Route
{...rest}
render={(props) =>
loggedIn() ? (
<Component {...props} />
) : (
<Redirect to='/login' />
)
}
/>
);
};
export default ProtectedRoute;
In your component you can change your logic with this:
<ProtectedRoute exact path="/" component={Home} />
<ProtectedRoute exact path="/about-us" component={AboutUs}/>
<Route exact path="/register" component={Register} />
<Route exact path="/login" component={login} />
<ProtectedRoute exact path="/profile" component={ProfilePage} />
This means that if you wanna protect a route you should route it with ProtectedRoute otherwise classic react route.
You can read about that ProtectedRoute concept here
I'm using React 16, React-router-dom 4 and Mobx in my app.
I have this code for a private route:
export default (props) => {
console.log('props from private',props)//Here i can see that the component doesn't contain the "history" prop.
const Component = props.component;
const match = props.computedMatch
if (isValidated()) {
return (
<div>
<div><Component {...props} match={match} /></div>
</div>
)
} else {
return <Redirect to="/login" />
}
};
This is the routing setup:
export const history = createHistory();
const AppRouter = () => (
<Router history={history}>
<Switch>
<PrivateRoute path="/" component={Chat} exact={true} />
<Route path="/login" component={Login} />
</Switch>
</Router>
);
For some reason, the history prop just doesn't exist in the private route, therefore i'm unable to use the this.props.history.push function, to redirect programatically. The prop does get passed to a "normal" route though.
What is wrong with my code?
Use below:
import {withRouter} from 'react-router-dom';
wrap component with withRouter.
withRouter(component_name)
I would like to make use of react-router's onEnter handler in order to prompt users to authenticate when entering a restricted route.
So far my routes.js file looks something like this:
import React from 'react';
import { Route, IndexRoute } from 'react-router';
export default (
<Route path="/" component={App}>
<IndexRoute component={Landing} />
<Route path="learn" component={Learn} />
<Route path="about" component={About} />
<Route path="downloads" component={Downloads} onEnter={requireAuth} />
</Route>
)
Ideally, I'd like my requireAuth function to be a redux action that has access to the store and current state, that works like this: store.dispatch(requireAuth()).
Unfortunately I don't have access to the store in this file. I don't think I can use really use connect in this case to access the relevant actions that I want. I also can't just import store from the file where the store is created, as this is undefined when the app first loads.
The easiest way to accomplish this is to pass your store to a function that returns your routes (rather than return your routes directly). This way you can access the store in onEnter and other react router methods.
So for your routes:
import React from 'react';
import { Route, IndexRoute } from 'react-router';
export const getRoutes = (store) => (
const authRequired = (nextState, replaceState) => {
// Now you can access the store object here.
const state = store.getState();
if (!state.user.isAuthenticated) {
// Not authenticated, redirect to login.
replaceState({ nextPathname: nextState.location.pathname }, '/login');
}
};
return (
<Route path="/" component={App}>
<IndexRoute component={Landing} />
<Route path="learn" component={Learn} />
<Route path="about" component={About} />
<Route path="downloads" component={Downloads} onEnter={authRequired} />
</Route>
);
)
Then update your main component to call the getRoutes function, passing in the store:
<Provider store={ store }>
<Router history={ history }>
{ getRoutes(store) }
</Router>
</Provider>
As for dispatching an action from requireAuth, you could write your function like this:
const authRequired = (nextState, replaceState, callback) => {
store.dispatch(requireAuth()) // Assume this action returns a promise
.then(() => {
const state = store.getState();
if (!state.user.isAuthenticated) {
// Not authenticated, redirect to login.
replaceState({ nextPathname: nextState.location.pathname }, '/login');
}
// All ok
callback();
});
};
Hope this helps.
If you want that you could write route.js like this:
var requireAuth = (store, nextState, replace) => {
console.log("store: ", store);
//now you have access to the store in the onEnter hook!
}
export default (store) => {
return (
<Route path="/" component={App}>
<IndexRoute component={Landing} />
<Route path="learn" component={Learn} />
<Route path="about" component={About} />
<Route path="downloads" component={Downloads} onEnter={requireAuth.bind(this, store)} />
</Route>
);
);
I've setup an example which you could play with in this codepen.
Not sure if triggering an action in order to handle the auth is a good idea. Personally I prefer handling auth in a different way:
Instead of using an onEnter hook, I use a wrapping function. I want the admin section of my blog protected, therefore I wrapped the AdminContainer component in the routes with a function, requireAuthentication, see below.
export default (store, history) => {
return (
<Router history={history}>
<Route path="/" component={App}>
{ /* Home (main) route */ }
<IndexRoute component={HomeContainer}/>
<Route path="post/:slug" component={PostPage}/>
{ /* <Route path="*" component={NotFound} status={404} /> */ }
</Route>
<Route path="/admin" component={requireAuthentication(AdminContainer)}>
<IndexRoute component={PostList}/>
<Route path=":slug/edit" component={PostEditor}/>
<Route path="add" component={PostEditor}/>
</Route>
<Route path="/login" component={Login}/>
</Router>
);
};
requireAuthentication is a function that
if the user is authenticated, renders the wrapped component,
otherwise redirects to Login
You can see it below:
export default function requireAuthentication(Component) {
class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkAuth();
}
componentWillReceiveProps (nextProps) {
this.checkAuth();
}
checkAuth () {
if (!this.props.isAuthenticated) {
let redirectAfterLogin = this.props.location.pathname;
this.context.router.replace({pathname: '/login', state: {redirectAfterLogin: redirectAfterLogin}});
}
}
render () {
return (
<div>
{this.props.isAuthenticated === true
? <Component {...this.props}/>
: null
}
</div>
)
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.blog.get('isAuthenticated')
});
AuthenticatedComponent.contextTypes = {
router: React.PropTypes.object.isRequired
};
return connect(mapStateToProps)(AuthenticatedComponent);
}
Also, requireAuthentication will protect all routes under /admin. And you can reuse it wherever you like.
Lots have changed over the time. onEnter no longer exists on react-router-4
The following is from my real project for your reference
export const getRoutes = (store) => {
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
checkIfAuthed(store) ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login'
}}/>
)
)}/>
)
return (
<Router>
<div>
<PrivateRoute exact path="/" component={Home}/>
<Route path="/login" component={Login} />
</div>
</Router>
)
}
After trying out a few of the above suggestions, I found the best way to track the state of your store with updates is to use React-Redux's useSelector function which basically connects a functional component to the store.
import * as React from "react";
import {Redirect, Route, Switch} from "react-router";
import {Provider, useSelector} from "react-redux";
import { createBrowserHistory } from "history";
// Your imports
import {IApplicationState,} from "./store/store";
import {Login} from "./routes/login/login.component";
import {getToken} from "./store/helpers/httpHelpers";
function handleRedirect() {
if(!getToken()) {
return <Redirect to="/login"/>;
}
}
const restricted = (Component: _ComponentType, isLoggedIn: boolean) => {
// Don't redirect here if there is a token in localStorage.
// This is happening when we are on a restricted route and the user
// refreshes & the isLoggedIn state hasn't been updated yet.
return !isLoggedIn ? (
() => handleRedirect()
) : () => <Route component={Component}/>
};
const AuthenticateRoutes = () => {
const isLoggedIn = useSelector((state: IApplicationState) => state.auth.isLoggedIn);
return (
<Switch>
<Route path="/login" component={Login} />
<Route path="/downloads" render={restricted(Download, isLoggedIn)} />
</Switch>
);
};
export function App() {
return (
<Provider store={store}>
<>
<Router history={createBrowserHistory()}>
<AuthenticateRoutes />
</Router>
</>
</Provider>
);
}