3

I am using a higher order component (PublicRoute) to conditionally render another component depending on whether the user is authenticated or not. I have made another higher order component, PrivateRoute, to achieve the opposite effect. The idea is that I don't want users logged in to be able to access the login page, and I don't want users who aren't logged in to view other pages. (Pragmatically speaking, this is unlikely, unless they manually type in the URL, but still, I want to ensure that it doesn't happen.)

The problem is that the Redirect component is only working after the Login component renders for a few seconds. This seems like bizarre behavior. The auth piece of the state contains either an object or is false or null (null is the default state). So there shouldn't be any problem. The same problem occurs with the PrivateRoute (e.g. an unauthenticated user can still access the Cart page).

PublicRoute.js

export const PublicRoute = ({ 
  isAuthenticated, 
  component: Component,
  ...rest
  }) => (
     <Route 
     {...rest} 
     component={(props) => (!isAuthenticated ? (
        <div>
            <Component {...props}/>
        </div>
    ) : ( <Redirect to="/shop"/> )
    )}/>
);

const mapStateToProps = ({ auth }) => ({ isAuthenticated: !!auth });

export default connect(mapStateToProps)(PublicRoute); 

AppRouter.js

  <PublicRoute path="/login" component={Login}/>
janhartmann
  • 14,713
  • 15
  • 82
  • 138
HPJM
  • 517
  • 8
  • 17

3 Answers3

1

The pattern I use is that I use the render method of the <Route ... />, like so:

import React from "react";
import { Route, Redirect } from "react-router-dom";
import PropTypes from "prop-types";

const ProtectedRoute = ({ component: Component, isAuthenticated, ...rest }) => (
    <Route
        {...rest}
        render={props =>
            isAuthenticated ? (
                <Component {...props} />
            ) : (
                <Redirect
                    to={{
                        pathname: "/my-login-page",
                        state: { from: props.location }
                    }}
                />
            )
        }
    />
);

ProtectedRoute.propTypes = {
    component: PropTypes.func,
    location: PropTypes.object,
    isAuthenticated: PropTypes.bool
};

export default ProtectedRoute;

And wire it to my Redux state using a connect method:

import { connect } from "react-redux";
import { withRouter } from "react-router";

import ProtectedRoute from "components/shared/ProtectedRoute";

export default withRouter(
    connect(state => ({
        isAuthenticated: true // logic for verifying
    }))(ProtectedRoute)
);

And afterwards use the ProtectedRoute as the Route component for protected routes - otherwise just use regular Route.

janhartmann
  • 14,713
  • 15
  • 82
  • 138
  • Good answer. I had a very similar problem to the OP. I have custom route and menu builder functions that take in an access control list and render routes and components. Within my routebuilder, I was using "component" prop of `` instead of "render" ... [look here for more info](https://stackoverflow.com/questions/48150567/react-router-difference-between-component-and-render)... When you use component prop, the component passed is instantiated per every call of Route#render, compared to the render prop, where it is only evaluated... hence rendering feeling slower than the route change. – spencer741 Jul 01 '21 at 14:52
-1

You should keep a Route separate from the Redirect, also use render prop instead of component when you want to render a component with custom props

export const PublicRoute = ({ 
  isAuthenticated, 
  component: Component,
  ...rest
  }) => (
     !isAuthenticated? (
     <Route 
        {...rest} 
        render={(props) => (
           <div>
              <Component {...props}/>
           </div>
        )}
     />) : <Redirect to="/shop"/> 
    )
);
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
-1

Thanks guys for the alternative solutions.

I just realised my mistake. My auth reducer returns either null (default state) or false (if there is no-one authenticated). By using !!auth to coerce null to false I was failing to distinguish between these two when the action generator was fired. Thus, it was rendering the Public Route component for a few seconds even when I was logged in because null was returned before I got the user object back from the request.

Fixed this by changing the logic inside my HOCs:

For the public route:

component={(props) => (auth === false ? (
        <div>
            <Component {...props}/>
        </div>
    ) : ( <Redirect to="/shop"/> )
)}

For the private route:

component={(props) => (auth ? (
        <div>
            <Component {...props}/>
        </div>
    ) : ( <Redirect to="/login"/> )
)}
HPJM
  • 517
  • 8
  • 17