React history push not rendering the component - javascript

I am new to the react js. I am using the following way for the purpose of the react route,
In the main component
render() {
return (
<div>
<BrowserRouter>
<div>
<Switch>
<PrivateRoute exact path="/" component={ LandingScreen } />
<PrivateRoute exact path="/QuizSetupMain" component={QuizSetupMain} />
<Route exact path="/login" component={LoginComponent} />
</Switch>
</div>
</BrowserRouter>
</div>
)
privateRoute ->
import React from 'react';
import { Route, Redirect, withRouter } from 'react-router';
import { connect } from 'react-redux';
import Loading from '../Loader/Loading';
const PrivateRoute = ({ component: Component, hasUserLogIn, ...rest }) => {
return <Route
{...rest}
render={
props => {
return hasUserLogIn ?
(
isFetching ? <Loading /> :
<Component {...props} />
)
:
(
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
}
/>
};
function mapStateToProps(state) {
return {
hasUserLogIn: state.LoginReducer.hasUserLogIn,
isFetching: state.LoginReducer.isFetching
}
}
Here
after success of login,
case LOGIN_SUCCESS:
console.log("going in LOGIN_SUCCESS");
return {
...state,
hasUserLogIn: true,
response: action.data,
error: false,
isFetching: true,
loginRequest: false
}
this state will return, so here if you see isFetching is a property that I am using the privateRoutes.js , then also it does not call that loader .
export default connect(mapStateToProps, null)(PrivateRoute);
So, here What I want to do is that, when a user is not logged in the redirected user to the login page. after that, it should be redirected to the / home page. so, I am doing that from,
export function sendUserJd(data, dispatch) {
dispatch(setFlag());
history.push('/');
return {
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: data,
}
}
So, but still, it is not rendering the landing screen and also not going in the private route after login. can any one tell me why is this happening? Thanks.

Since you are using custom history object in the action to change Route, you need to use a Router with a custom history object that you use in the action creator.
import { history } from 'path/to/history';
<Router history={history}>
<div>
<Switch>
<PrivateRoute exact path="/" component={ LandingScreen } />
<PrivateRoute exact path="/QuizSetupMain" component={QuizSetupMain} />
<Route exact path="/login" component={LoginComponent} />
</Switch>
</div>
</Router>
As per the loading condition, you would write it like
const PrivateRoute = ({ component: Component, isFetching, hasUserLogIn, path, ...rest }) => {
if(isFetching){
return <Loading />
}
return hasUserLogIn ?
(
<Route
{...rest}
path={path}
component={Component}
/>
)
:
(
<Redirect
to={{
pathname: "/login",
state: { from: path }
}}
/>
)
};

Related

How can one navigate to previous location on React using react router version 6 when a user logs in? [duplicate]

How to create a protected route with react-router-dom and storing the response in localStorage, so that when a user tries to open next time they can view their details again. After login, they should redirect to the dashboard page.
All functionality is added in ContextApi.
Codesandbox link : Code
I tried but was not able to achieve it
Route Page
import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";
function Routes() {
const { authLogin } = useContext(globalC);
console.log("authLogin", authLogin);
return (
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
export default Routes;
Context Page
import React, { Component, createContext } from "react";
import axios from "axios";
export const globalC = createContext();
export class Gprov extends Component {
state = {
authLogin: null,
authLoginerror: null
};
componentDidMount() {
var localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({
authLogin: localData
});
}
}
loginData = async () => {
let payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome",
_repeat: 300
}
};
await axios
.post(`https://app.fakejson.com/q`, payload)
.then((res) => {
if (res.status === 200) {
this.setState({
authLogin: res.data
});
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
})
.catch((err) =>
this.setState({
authLoginerror: err
})
);
};
render() {
// console.log(localStorage.getItem("loginDetail"));
return (
<globalC.Provider
value={{
...this.state,
loginData: this.loginData
}}
>
{this.props.children}
</globalC.Provider>
);
}
}
Issue
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
The Switch doesn't handle rendering anything other than Route and Redirect components. If you want to "nest" like this then you need to wrap each in generic routes, but that is completely unnecessary.
Your login component also doesn't handle redirecting back to any "home" page or private routes that were originally being accessed.
Solution
react-router-dom v5
Create a PrivateRoute component that consumes your auth context.
const PrivateRoute = (props) => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin ? (
<Route {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
);
};
Update your Login component to handle redirecting back to the original route being accessed.
export default function Login() {
const location = useLocation();
const history = useHistory();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
history.replace(from);
}
}, [authLogin, history, location]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
Render all your routes in a "flat list"
function Routes() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/dashboard" component={Dashboard} />
<PrivateRoute path="/About" component={About} />
<Route path="/login" component={Login} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
react-router-dom v6
In version 6 custom route components have fallen out of favor, the preferred method is to use an auth layout component.
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoutes = () => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
}
...
<BrowserRouter>
<Routes>
<Route path="/" element={<PrivateRoutes />} >
<Route path="dashboard" element={<Dashboard />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
or
const routes = [
{
path: "/",
element: <PrivateRoutes />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <PageNotFound />
},
];
...
export default function Login() {
const location = useLocation();
const navigate = useNavigate();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
For v6:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/protected"
element={
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
}
/>
</Routes>
);
}
function RequireAuth({ children, redirectTo }) {
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
Link to docs:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f
import { v4 as uuidv4 } from "uuid";
const routes = [
{
id: uuidv4(),
isProtected: false,
exact: true,
path: "/home",
component: param => <Overview {...param} />,
},
{
id: uuidv4(),
isProtected: true,
exact: true,
path: "/protected",
component: param => <Overview {...param} />,
allowed: [...advanceProducts], // subscription
},
{
// if you conditional based rendering for same path
id: uuidv4(),
isProtected: true,
exact: true,
path: "/",
component: null,
conditionalComponent: true,
allowed: {
[subscription1]: param => <Overview {...param} />,
[subscription2]: param => <Customers {...param} />,
},
},
]
// Navigation Component
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";
// ...component logic
<Switch>
{routes.map(params => {
return (
<ProtectedRoutes
exact
routeParams={params}
key={params.path}
path={params.path}
/>
);
})}
<Route
render={() => {
props.setHideNav(true);
setHideHeader(true);
return <ErrorPage type={404} />;
}}
/>
</Switch>
// ProtectedRoute component
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoutes = props => {
const { routeParams } = props;
const currentSubscription = 'xyz'; // your current subscription;
if (routeParams.conditionalComponent) {
return (
<Route
key={routeParams.path}
path={routeParams.path}
render={routeParams.allowed[currentSubscription]}
/>
);
}
if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
if (!routeParams.isProtected) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
return null;
};
export default ProtectedRoutes;
Would like to add highlight never forget to give path as prop to ProtectedRoute, else it will not work.
Here is an easy react-router v6 protected route. I have put all the routes I want to protect in a routes.js:-
const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]
To render the routes just map them as follows: -
<Routes>
{routes.map((routes, id) => {
return(
<Route
key={id}
path={route.path}
exact={route.exact}
name={route.name}
element={
localStorage.getItem("token") ? (
route.element
) : (
<Navigate to="/login" />
)
}
)
})
}
</Routes>
If you want an easy way to implement then use Login in App.js, if user is loggedin then set user variable. If user variable is set then start those route else it will stuck at login page. I implemented this in my project.
return (
<div>
<Notification notification={notification} type={notificationType} />
{
user === null &&
<LoginForm startLogin={handleLogin} />
}
{
user !== null &&
<NavBar user={user} setUser={setUser} />
}
{
user !== null &&
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/adduser" element={<AddUser />} /> />
<Route exact path="/viewuser/:id" element={<ViewUser />} />
</Routes>
</Router>
}
</div>
)

React private route not renderring the Login component

I am new to the react js. I am trying to implement the private route concept. so,
Main.js
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
error: false,
hasUserLogIn: false,
dataFetched: false,
isFetching: false,
}
}
render() {
const template =
<Switch>
<PrivateRoute exact path="/login" component={LoginComponent} />
<PrivateRoute exact path="/QuizSetupMain" component={QuizSetupMain} />
<PrivateRoute exact path="/" component={LandingScreen} />
</Switch>
return (
<div>
{template}
</div>
)
}
}
function mapStateToProps(state) {
return {
hasUserLogIn: state.LoginReducer.hasUserLogIn,
isFetching: state.LoginReducer.isFetching
}
}
export default connect(mapStateToProps)(Main);
Privatecomponent.js
import React from 'react';
import { Route, Redirect, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
const PrivateRoute = ({ component: Component, isAuthenticated, ...rest }) => {
return <Route
{...rest}
render={
props => {
return isAuthenticated ?
(
<Component {...props} />
)
:
(
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
}
/>
};
const mapStateToProps = state => (
{
// isAuthenticated value is get from here
isAuthenticated: state.LoginReducer.hasUserLogIn
}
);
export default withRouter(connect(
mapStateToProps, null, null, { pure: false }
)(PrivateRoute));
so, here what I wanted to do is if user has not logged in then user should be redirected to the login component.otherwise user should redirect to the landingscreen components. so, I did in the following way.
but it is adding the /login in the url but that component is not getting renderd. so, can any one help me with this ?
export function sendUserJd(data, dispatch) {
dispatch(setFlag());
history.push('/');
return {
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: data,
}
}
I am doing this in the action to redirect the user.
Two things that you need to correct,
First:The login route need not be a PrivateRoute since an unauthenticated user should be able to access it'
Second: You need to wrap your Routes with a Router which is the Provider at some level in the parent components
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
error: false,
hasUserLogIn: false,
dataFetched: false,
isFetching: false,
}
}
render() {
const template =
<Switch>
<Route exact path="/login" component={LoginComponent} />
<PrivateRoute exact path="/QuizSetupMain" component={QuizSetupMain} />
<PrivateRoute exact path="/" component={LandingScreen} />
</Switch>
return (
<BrowserRouter>
<div>
{template}
</div>
</BrowserRouter>
)
}
}
The login route needs to be public (not private) coz that is the page through which your users can authenticate themselves
Essentially, replace
<PrivateRoute exact path="/login" component={LoginComponent} />
with
<Route exact path='/login' component={LoginComponent} />

How to correctly handle non PrivateRoutes in React?

I have this piece of code in my app.js and have configured a PrivateRoute which requires the user to login and only allows access if the cookie is set. However, I would like to restrict users trying to hit /login after they have successfully logged in. I used the reverse logic of the PrivateRoute and created LoginRoute which serves the purpose but would like to know if there is a better approach.
import React from 'react';
import {
BrowserRouter as Router,
Route,
Switch,
Redirect
} from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router'
import cookies from 'cookies-js';
import Home from './homeComponent';
import Login from './loginComponent';
import Dashboard from './dashboardComponent';
import NoMatch from './noMatchComponent';
const App = ({ history }) => {
return (
<ConnectedRouter history={history}>
<Switch>
<Route exact={true} path="/" component={Home} />
<LoginRoute path="/login" component={Login} />
<PrivateRoute path="/dashboard" component={Dashboard} />
<Route component={NoMatch} />
</Switch>
</ConnectedRouter>
);
};
const LoginRoute = ({ component: Component, rest }) => (
<Route {...rest} render={(props) => (
cookies.get('access-token')
? <Redirect to={{
pathname: '/dashboard',
state: { from: props.location }
}} />
: <Component {...props} />
)} />
)
const PrivateRoute = ({ component: Component, rest }) => (
<Route {...rest} render={(props) => (
cookies.get('access-token')
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
export default App;
There are a few ways to handle Private Routes, one such way is to write a custom Login route as you have written which prevents user from visiting /login if he/she is already loggedIn. The only correction that you need to make in your route is to use rest syntax correctly
const LoginRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
cookies.get('access-token')
? <Redirect to={{
pathname: '/dashboard',
state: { from: props.location }
}} />
: <Component {...props} />
)} />
)
and PrivateRoute
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
cookies.get('access-token')
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
The other way to handle this would be an authentication HOC.
const RequireAuth = (Component) => {
return class App extends Component {
render() {
const { location } = this.props;
if (cookies.get('access-token')) {
if(location.pathname === '/login') {
return <Redirect to={'/dashboard'} />
}
return <Component {...this.props} />
}
return <Redirect to="/login"/>
}
}
}
export { RequireAuth }
and you would use it like
const App = ({ history }) => {
return (
<ConnectedRouter history={history}>
<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/login" component={RequireAuth(Login)} />
<Route path="/dashboard" component={RequireAuth(Dashboard)} />
<Route component={NoMatch} />
</Switch>
</ConnectedRouter>
);
};

multiple page in react router v4

I am using react router v4 for routing. The layout my app is, there is homepage which is for end user. The path for homepage is obviously /. There is dashboard section too. One for admin, one for agent and another for owner. They have their own layout from top to bottom. For homepage its working. But when i hit url /admin/dashboard, the main page of admin dasboard is not shown. The same homepage is shown.
Here is what i have done
const AsyncRoute = ({ load, ...others }) => (
<Route
{...others} render={(props) => (
<Bundle load={load} {...props} />
)}
/>
);
app.js
const render = messages => {
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById("app")
);
};
import Routes from 'routes';
class App extends React.Component {
render() {
return (
<div>
<Navbar userForm={this.handleDialog} />
<Routes />
</div>
);
}
}
routes.js
function Routes({ location }) {
return (
<Switch location={location}>
<AsyncRoute exact path="/" load={loadHomePage} />
<AsyncRoute exact path="/features" load={loadFeaturePage} />
<AsyncRoute path="" load={loadNotFoundPage} />
</Switch>
);
}
I want now admin dashboard a complete new page with different layout not children of App component so i did
the following
class AdminDashboard extends React.Component {
render() {
return (
<div>
<TopNavigation />
<SideNavigation />{/* it will have link */}
<Routes /> {/* it will display page */}
</div>
);
}
}
function AdminRoutes({ location }) {
return (
<Switch location={location}>
<AsyncRoute
exact
path="/admin/dashboard"
load={loadAdminDashboardPage}
/>
<AsyncRoute exact path="/commission" load={loadCommissionPage} />
<AsyncRoute path="" load={loadNotFoundPage} />
</Switch>
);
}
when i hit the url /admin/dashboard i get the app page not the admin dashboard page and same with the
/commission which should be a child of AdminDashboard
How can i make my router work for different layout?
From Meteorchef Base, dedicated routing according to the user (loggingIn prop in this case):
Public.js, only for non admin, redirect if not :
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Redirect } from 'react-router-dom';
const Public = ({ loggingIn, authenticated, component, ...rest }) => (
<Route {...rest} render={(props) => {
if (loggingIn) return <div></div>;
return !authenticated ?
(React.createElement(component, { ...props, loggingIn, authenticated })) :
(<Redirect to="/account" />);
}} />
);
Public.propTypes = {
loggingIn: PropTypes.bool,
authenticated: PropTypes.bool,
component: PropTypes.func,
};
export default Public;
Authenticated.js, only for admin, also redirect if not :
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Redirect } from 'react-router-dom';
const Authenticated = ({ loggingIn, authenticated, component, ...rest }) => (
<Route {...rest} render={(props) => {
if (loggingIn) return <div></div>;
return authenticated ?
(React.createElement(component, { ...props, loggingIn, authenticated })) :
(<Redirect to="/login" />);
}} />
);
Authenticated.propTypes = {
loggingIn: PropTypes.bool,
authenticated: PropTypes.bool,
component: PropTypes.func,
};
export default Authenticated;
then in your App, (stateless in this case, but you can create a class as well too) :
// import everything
const App = appProps => (
<Router>
<div>
<Switch>
<Route exact name="index" path="/" component={Home} />
<Authenticated exact path="/account" component={Account} {...appProps} />
<Public path="/signup" component={Signup} {...appProps} />
<Public path="/login" component={Login} {...appProps} />
<Route component={NotFound} />
</Switch>
</div>
</Router>
);
App.propTypes = {
loggingIn: PropTypes.bool,
authenticated: PropTypes.bool,
};

How to implement authenticated routes in React Router 4?

I was trying to implement authenticated routes but found that React Router 4 now prevents this from working:
<Route exact path="/" component={Index} />
<Route path="/auth" component={UnauthenticatedWrapper}>
<Route path="/auth/login" component={LoginBotBot} />
</Route>
<Route path="/domains" component={AuthenticatedWrapper}>
<Route exact path="/domains" component={DomainsIndex} />
</Route>
The error is:
Warning: You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored
In that case, what's the correct way to implement this?
It appears in react-router (v4) docs, it suggests something like
<Router>
<div>
<AuthButton/>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/protected" component={Protected}/>
</div>
</Router>
But is it possible to achieve this while grouping a bunch of routes together?
After some research, I came up with this:
import React, {PropTypes} from "react"
import {Route} from "react-router-dom"
export default class AuthenticatedRoute extends React.Component {
render() {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <Route {...this.props} />
}
}
AuthenticatedRoute.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
component: PropTypes.element,
redirectToLogin: PropTypes.func.isRequired
}
Is it correct to dispatch an action in render()? It feels wrong. It doesn't really seem correct with componentDidMount or some other hook, either.
You're going to want to use the Redirect component. There's a few different approaches to this problem. Here's one I like, have a PrivateRoute component that takes in an authed prop and then renders based on that props.
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
Now your Routes can look something like this
<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />
If you're still confused, I wrote this post that may help -
Protected routes and authentication with React Router v4
All answers are outdated
In 2022 the render prop of the Route component is for legacy use according to the react-router-dom documentation is not even working anymore in V5 and in V6 was removed.
This works instead:
const RequireAuth: FC<{ children: React.ReactElement }> = ({ children }) => {
const userIsLogged = useLoginStatus(); // Your hook to get login status
if (!userIsLogged) {
return <LoginPage />;
}
return children;
};
Usage:
/* A route that doesn't require login */
<Route
path="sign-up"
element={
<SignUpPage />
}
/>
/* A route that requires login */
<Route
path="dashboard"
element={
<RequireAuth>
<DashboardPage />
</RequireAuth>
}
/>
EDIT: I updated the code example to v6 of React Router
Tnx Tyler McGinnis for solution.
I make my idea from Tyler McGinnis idea.
const DecisionRoute = ({ trueComponent, falseComponent, decisionFunc, ...rest }) => {
return (
<Route
{...rest}
render={
decisionFunc()
? trueComponent
: falseComponent
}
/>
)
}
You can implement that like this
<DecisionRoute path="/signin" exact={true}
trueComponent={redirectStart}
falseComponent={SignInPage}
decisionFunc={isAuth}
/>
decisionFunc just a function that return true or false
const redirectStart = props => <Redirect to="/orders" />
(Using Redux for state management)
If user try to access any url, first i am going to check if access token available, if not redirect to login page,
Once user logs in using login page, we do store that in localstorage as well as in our redux state. (localstorage or cookies..we keep this topic out of context for now).
since redux state as updated and privateroutes will be rerendered. now we do have access token so we gonna redirect to home page.
Store the decoded authorization payload data as well in redux state and pass it to react context. (We dont have to use context but to access authorization in any of our nested child components it makes easy to access from context instead connecting each and every child component to redux)..
All the routes that don't need special roles can be accessed directly after login.. If it need role like admin (we made a protected route which checks whether he had desired role if not redirects to unauthorized component)
similarly in any of your component if you have to disable button or something based on role.
simply you can do in this way
const authorization = useContext(AuthContext);
const [hasAdminRole] = checkAuth({authorization, roleType:"admin"});
const [hasLeadRole] = checkAuth({authorization, roleType:"lead"});
<Button disable={!hasAdminRole} />Admin can access</Button>
<Button disable={!hasLeadRole || !hasAdminRole} />admin or lead can access</Button>
So what if user try to insert dummy token in localstorage. As we do have access token, we will redirect to home component. My home component will make rest call to grab data, since jwt token was dummy, rest call will return unauthorized user. So i do call logout (which will clear localstorage and redirect to login page again).
If home page has static data and not making any api calls(then you should have token-verify api call in the backend so that you can check if token is REAL before loading home page)
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import history from './utils/history';
import Store from './statemanagement/store/configureStore';
import Privateroutes from './Privateroutes';
import Logout from './components/auth/Logout';
ReactDOM.render(
<Store>
<Router history={history}>
<Switch>
<Route path="/logout" exact component={Logout} />
<Route path="/" exact component={Privateroutes} />
<Route path="/:someParam" component={Privateroutes} />
</Switch>
</Router>
</Store>,
document.querySelector('#root')
);
History.js
import { createBrowserHistory as history } from 'history';
export default history({});
Privateroutes.js
import React, { Fragment, useContext } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { AuthContext, checkAuth } from './checkAuth';
import App from './components/App';
import Home from './components/home';
import Admin from './components/admin';
import Login from './components/auth/Login';
import Unauthorized from './components/Unauthorized ';
import Notfound from './components/404';
const ProtectedRoute = ({ component: Component, roleType, ...rest })=> {
const authorization = useContext(AuthContext);
const [hasRequiredRole] = checkAuth({authorization, roleType});
return (
<Route
{...rest}
render={props => hasRequiredRole ?
<Component {...props} /> :
<Unauthorized {...props} /> }
/>)};
const Privateroutes = props => {
const { accessToken, authorization } = props.authData;
if (accessToken) {
return (
<Fragment>
<AuthContext.Provider value={authorization}>
<App>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" render={() => <Redirect to="/" />} />
<Route exact path="/home" component={Home} />
<ProtectedRoute
exact
path="/admin"
component={Admin}
roleType="admin"
/>
<Route path="/404" component={Notfound} />
<Route path="*" render={() => <Redirect to="/404" />} />
</Switch>
</App>
</AuthContext.Provider>
</Fragment>
);
} else {
return (
<Fragment>
<Route exact path="/login" component={Login} />
<Route exact path="*" render={() => <Redirect to="/login" />} />
</Fragment>
);
}
};
// my user reducer sample
// const accessToken = localStorage.getItem('token')
// ? JSON.parse(localStorage.getItem('token')).accessToken
// : false;
// const initialState = {
// accessToken: accessToken ? accessToken : null,
// authorization: accessToken
// ? jwtDecode(JSON.parse(localStorage.getItem('token')).accessToken)
// .authorization
// : null
// };
// export default function(state = initialState, action) {
// switch (action.type) {
// case actionTypes.FETCH_LOGIN_SUCCESS:
// let token = {
// accessToken: action.payload.token
// };
// localStorage.setItem('token', JSON.stringify(token))
// return {
// ...state,
// accessToken: action.payload.token,
// authorization: jwtDecode(action.payload.token).authorization
// };
// default:
// return state;
// }
// }
const mapStateToProps = state => {
const { authData } = state.user;
return {
authData: authData
};
};
export default connect(mapStateToProps)(Privateroutes);
checkAuth.js
import React from 'react';
export const AuthContext = React.createContext();
export const checkAuth = ({ authorization, roleType }) => {
let hasRequiredRole = false;
if (authorization.roles ) {
let roles = authorization.roles.map(item =>
item.toLowerCase()
);
hasRequiredRole = roles.includes(roleType);
}
return [hasRequiredRole];
};
DECODED JWT TOKEN SAMPLE
{
"authorization": {
"roles": [
"admin",
"operator"
]
},
"exp": 1591733170,
"user_id": 1,
"orig_iat": 1591646770,
"email": "hemanthvrm#stackoverflow",
"username": "hemanthvrm"
}
const Root = ({ session }) => {
const isLoggedIn = session && session.getCurrentUser
return (
<Router>
{!isLoggedIn ? (
<Switch>
<Route path="/signin" component={<Signin />} />
<Redirect to="/signin" />
</Switch>
) : (
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/something-else" component={SomethingElse} />
<Redirect to="/" />
</Switch>
)}
</Router>
)
}
install react-router-dom
then create two components one for valid users and other for invalid users.
try this on app.js
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom';
import ValidUser from "./pages/validUser/validUser";
import InValidUser from "./pages/invalidUser/invalidUser";
const loggedin = false;
class App extends React.Component {
render() {
return (
<Router>
<div>
<Route exact path="/" render={() =>(
loggedin ? ( <Route component={ValidUser} />)
: (<Route component={InValidUser} />)
)} />
</div>
</Router>
)
}
}
export default App;
Based on the answer of #Tyler McGinnis. I made a different approach using ES6 syntax and nested routes with wrapped components:
import React, { cloneElement, Children } from 'react'
import { Route, Redirect } from 'react-router-dom'
const PrivateRoute = ({ children, authed, ...rest }) =>
<Route
{...rest}
render={(props) => authed ?
<div>
{Children.map(children, child => cloneElement(child, { ...child.props }))}
</div>
:
<Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
export default PrivateRoute
And using it:
<BrowserRouter>
<div>
<PrivateRoute path='/home' authed={auth}>
<Navigation>
<Route component={Home} path="/home" />
</Navigation>
</PrivateRoute>
<Route exact path='/' component={PublicHomePage} />
</div>
</BrowserRouter>
Heres how I solved it with React and Typescript. Hope it helps !
import * as React from 'react';
import { FC } from 'react';
import { Route, RouteComponentProps, RouteProps, Redirect } from 'react-router';
const PrivateRoute: FC<RouteProps> = ({ component: Component, ...rest }) => {
if (!Component) {
return null;
}
const isLoggedIn = true; // Add your provider here
return (
<Route
{...rest}
render={(props: RouteComponentProps<{}>) => isLoggedIn ? (<Component {...props} />) : (<Redirect to={{ pathname: '/', state: { from: props.location } }} />)}
/>
);
};
export default PrivateRoute;
<PrivateRoute component={SignIn} path="/signin" />
I know it's been a while but I've been working on an npm package for private and public routes.
Here's how to make a private route:
<PrivateRoute exact path="/private" authed={true} redirectTo="/login" component={Title} text="This is a private route"/>
And you can also make Public routes that only unauthed user can access
<PublicRoute exact path="/public" authed={false} redirectTo="/admin" component={Title} text="This route is for unauthed users"/>
I hope it helps!
I implemented using-
<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
authenticate props will be passed to components e.g. signup using which user state can be changed. Complete AppRoutes-
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';
import Home from '../pages/home';
import Login from '../pages/login';
import Signup from '../pages/signup';
import Dashboard from '../pages/dashboard';
import { config } from '../utils/Config';
export default class AppRoutes extends React.Component {
constructor(props) {
super(props);
// initially assuming that user is logged out
let user = {
isLoggedIn: false
}
// if user is logged in, his details can be found from local storage
try {
let userJsonString = localStorage.getItem(config.localStorageKey);
if (userJsonString) {
user = JSON.parse(userJsonString);
}
} catch (exception) {
}
// updating the state
this.state = {
user: user
};
this.authenticate = this.authenticate.bind(this);
}
// this function is called on login/logout
authenticate(user) {
this.setState({
user: user
});
// updating user's details
localStorage.setItem(config.localStorageKey, JSON.stringify(user));
}
render() {
return (
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/login' render={() => <Login authenticate={this.authenticate} />} />
<Route exact path='/signup' render={() => <Signup authenticate={this.authenticate} />} />
<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
</Switch>
);
}
}
Check the complete project here: https://github.com/varunon9/hello-react
The accepted answer is good, but it does NOT solve the problem when we need our component to reflect changes in URL.
Say, your component's code is something like:
export const Customer = (props) => {
const history = useHistory();
...
}
And you change URL:
const handleGoToPrev = () => {
history.push(`/app/customer/${prevId}`);
}
The component will not reload!
A better solution:
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import store from '../store/store';
export const PrivateRoute = ({ component: Component, ...rest }) => {
let isLoggedIn = !!store.getState().data.user;
return (
<Route {...rest} render={props => isLoggedIn
? (
<Component key={props.match.params.id || 'empty'} {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
} />
)
}
Usage:
<PrivateRoute exact path="/app/customer/:id" component={Customer} />
I love #fermmm answer but in his implementation the rendered component will not match with the url if the user is not logged in. Thus it might be confusing for a visitor.
So, instead of
return (
<Route {...props}>{userIsLogged ? props.children : <LoginPage/>}</Route>
);
I would suggest using:
return (
<Route {...props}>
{userIsLogged ? (
props.children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location },
}}
/>
)}
</Route>
);
In this case you will still get the component rendered but "/login" in the URL instead of the previous route segment.
It seems your hesitation is in creating your own component and then dispatching in the render method? Well you can avoid both by just using the render method of the <Route> component. No need to create a <AuthenticatedRoute> component unless you really want to. It can be as simple as below. Note the {...routeProps} spread making sure you continue to send the properties of the <Route> component down to the child component (<MyComponent> in this case).
<Route path='/someprivatepath' render={routeProps => {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <MyComponent {...routeProps} anotherProp={somevalue} />
} />
See the React Router V4 render documentation
If you did want to create a dedicated component, then it looks like you are on the right track. Since React Router V4 is purely declarative routing (it says so right in the description) I do not think you will get away with putting your redirect code outside of the normal component lifecycle. Looking at the code for React Router itself, they perform the redirect in either componentWillMount or componentDidMount depending on whether or not it is server side rendering. Here is the code below, which is pretty simple and might help you feel more comfortable with where to put your redirect logic.
import React, { PropTypes } from 'react'
/**
* The public API for updating the location programatically
* with a component.
*/
class Redirect extends React.Component {
static propTypes = {
push: PropTypes.bool,
from: PropTypes.string,
to: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
])
}
static defaultProps = {
push: false
}
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired
}).isRequired,
staticContext: PropTypes.object
}).isRequired
}
isStatic() {
return this.context.router && this.context.router.staticContext
}
componentWillMount() {
if (this.isStatic())
this.perform()
}
componentDidMount() {
if (!this.isStatic())
this.perform()
}
perform() {
const { history } = this.context.router
const { push, to } = this.props
if (push) {
history.push(to)
} else {
history.replace(to)
}
}
render() {
return null
}
}
export default Redirect
My Previous answer is not scalable. Here is what I think is good approach-
Your Routes-
<Switch>
<Route
exact path="/"
component={matchStateToProps(InitialAppState, {
routeOpen: true // no auth is needed to access this route
})} />
<Route
exact path="/profile"
component={matchStateToProps(Profile, {
routeOpen: false // can set it false or just omit this key
})} />
<Route
exact path="/login"
component={matchStateToProps(Login, {
routeOpen: true
})} />
<Route
exact path="/forgot-password"
component={matchStateToProps(ForgotPassword, {
routeOpen: true
})} />
<Route
exact path="/dashboard"
component={matchStateToProps(DashBoard)} />
</Switch>
Idea is to use a wrapper in component props which would return original component if no auth is required or already authenticated otherwise would return default component e.g. Login.
const matchStateToProps = function(Component, defaultProps) {
return (props) => {
let authRequired = true;
if (defaultProps && defaultProps.routeOpen) {
authRequired = false;
}
if (authRequired) {
// check if loginState key exists in localStorage (Your auth logic goes here)
if (window.localStorage.getItem(STORAGE_KEYS.LOGIN_STATE)) {
return <Component { ...defaultProps } />; // authenticated, good to go
} else {
return <InitialAppState { ...defaultProps } />; // not authenticated
}
}
return <Component { ...defaultProps } />; // no auth is required
};
};
Here is the simple clean protected route
const ProtectedRoute
= ({ isAllowed, ...props }) =>
isAllowed
? <Route {...props}/>
: <Redirect to="/authentificate"/>;
const _App = ({ lastTab, isTokenVerified })=>
<Switch>
<Route exact path="/authentificate" component={Login}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/secrets"
component={Secrets}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/polices"
component={Polices}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/grants" component={Grants}/>
<Redirect from="/" to={lastTab}/>
</Switch>
isTokenVerified is a method call to check the authorization token basically it returns boolean.
This is just a basic approach for beginners not for professional redux developers
import React, { useState, useEffect } from "react";
import {
Route,
BrowserRouter as Router,
Switch,
Redirect,
} from "react-router-dom";
import Home from "./components/Home";
import Dashboard from "./components/Dashboard";
import Login from "./components/Login";
function App() {
const [isAuth, setAuth] = useState(false);
const checkAuth = () => {
// Your auth logic here
setAuth(true);
};
useEffect(() => {
checkAuth();
});
return (
<Router>
<Switch>
<Route
path="/user/dashboard"
render={(props) =>
isAuth ? <Dashboard {...props} /> : <Redirect to="/" />
}
/>
<Route path="/login" component={Login} />
<Route path="/" component={Home} />
</Switch>
</Router>
);
}
Here is my own approach
const RedirectionUnit = () => {
const [user] = useContext(AuthContext);
const pathname = useLocation().pathname;
let redirectTo;
if (user === null) redirectTo = "login";
else if (pathname === "/")
if (user.type === "supervisor" ) redirectTo = "all-parteners";
else if (user.type === "manager" ) redirectTo = "all-employees";
else if (user.type === "employee" ) redirectTo = "unfinished-tasks";
if (redirectTo && '/' + redirectTo !== pathname)
return <Redirect to={redirectTo} />;
return null;
};
const NavigationRoutes = () => {
return (
<>
<Route component={RedirectionUnit} />
{/* prettier-ignore */}
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/logout" component={Logout} />
<Route exact path="/new-parteners" component={NewParteners} />
<Route exact path="/all-parteners" component={AllParteners} />
<Route exact path="/new-employees" component={NewEmployees} />
<Route exact path="/all-employees" component={AllEmployees} />
<Route exact path="/unfinished-tasks" component={UnfinishedTasks} />
<Route exact path="/finished-tasks" component={FinishedTasks} />
<Route exact path="/finished-tasks" component={FinishedTasks} />
<Route component={NotFound} />
</Switch>
</>
);
};
I was looking for a solution where my main router file had everything it needed to authenticate the routes. No nested component needed or complicated if else's. Below is my approach
import React from "react";
import { Routes, Route } from "react-router-dom";
import { Navigate } from "react-router-dom";
// Other imports
export default function AppRoutes() {
// This coming from react-redux
// After a user is logged in this will set in the global state
const { currentUser } = useCurrentUser();
const landing = <Landing />
const authenticate = (component) => {
return currentUser ? component : <Navigate to="/" />;
}
return (
<Routes>
<Route path="/" element={currentUser ? <Home /> : landing} />
<Route path="/blogs/:id" element={authenticate(<Blog />)} />
<Route path="/blogs/:id/edit" element={authenticate(<BlogEdit />)} />
<Route path="/profile" element={authenticate(<Profile />)} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
);
}
Based on the solution of #MaxThom for TypeScript, here is an option to be able to pass a component or a render function to PrivateRoute:
import React from "react";
import { Route, Redirect, RouteProps, RouteComponentProps } from "react-router-dom";
const PrivateRoute: React.FC<RouteProps> = ({component, render, ...rest}) => {
const userIsLogged = window.localStorage.getItem('currentUsecase');
if (userIsLogged === undefined) return (
<Route render={
(props: RouteComponentProps<{}>) => <Redirect
to={{ pathname: '/', state: { from: props.location } }}
/>
}/>
)
return (
<Route {...rest} render={render} component={component} />
)
};
export default PrivateRoute;
I hope this helps.
I was also looking for some answer. Here all answers are quite good, but none of them give answers how we can use it if user starts application after opening it back. (I meant to say using cookie together).
No need to create even different privateRoute Component. Below is my code
import React, { Component } from 'react';
import { Route, Switch, BrowserRouter, Redirect } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './stores';
import requireAuth from './components/authentication/authComponent'
import SearchComponent from './components/search/searchComponent'
import LoginComponent from './components/login/loginComponent'
import ExampleContainer from './containers/ExampleContainer'
class App extends Component {
state = {
auth: true
}
componentDidMount() {
if ( ! Cookies.get('auth')) {
this.setState({auth:false });
}
}
render() {
return (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route exact path="/searchComponent" component={requireAuth(SearchComponent)} />
<Route exact path="/login" component={LoginComponent} />
<Route exact path="/" component={requireAuth(ExampleContainer)} />
{!this.state.auth && <Redirect push to="/login"/> }
</Switch>
</BrowserRouter>
</Provider>);
}
}
}
export default App;
And here is authComponent
import React from 'react';
import { withRouter } from 'react-router';
import * as Cookie from "js-cookie";
export default function requireAuth(Component) {
class AuthenticatedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
auth: Cookie.get('auth')
}
}
componentDidMount() {
this.checkAuth();
}
checkAuth() {
const location = this.props.location;
const redirect = location.pathname + location.search;
if ( ! Cookie.get('auth')) {
this.props.history.push(`/login?redirect=${redirect}`);
}
}
render() {
return Cookie.get('auth')
? <Component { ...this.props } />
: null;
}
}
return withRouter(AuthenticatedComponent)
}
Below I have written blog, you can get more depth explanation there as well.
Create Protected routes in ReactJS

Categories