user9667412
user9667412

Reputation:

Private and public route based authentication with redux not working as intended

I am trying to implement private, protected and public routes on a project I'm working on. On every page refresh, I dispatch my setUserAsync() action creator inside componentDidMount in my app.js which calls my backend asking for the user information based on the token assigned on the sign-in or signup. SetUserAsync sets the user and toggles a state called isAuthenticated to true if the user is received. I then use my isAuthenticated state to determine which component to render or redirect to in my private and public routes.

But the issue I am facing is that whenever I try to access any private or protected route, the ternary operator is immediately fired based on the initial state of isAuthenticated which is false. Hence on a private route like /dashboard, it will take me to /signin as isAuthenticated is false initially until set to true by setUserAsync() in componentDidMount of app.js. Then from /signin I am redirected back to /dashboard as /signin is a protected route and takes me back to /dashboard as isAuthenticated finally turns to true by then.

How can I make it so that my routes on refresh wait for the API call to be completed before rendering anything. I've seen the same flow being implemented by many people and this works fine for them but I don't know what I'm doing wrong.

//App.js

class App extends React.Component {

componentDidMount() {
const { setUserAsync, token, tokenExpiry } = this.props;
token ? setUserAsync(token) : null
}
 render() {
  return (
  <Switch>
    <PublicRoute exact path="/" component={Home} />
    <PublicRoute exact path="/about-us" component={AboutUs} />
    <PublicRoute exact path="/contact-us" component={ContactUs} />
    <PublicRoute exact path="/privacy-policy" component={PrivacyPolicy} />
    <PublicRoute exact path="/terms-of-use" component={TermsOfUse} />

    <ProtectedRoute exact path="/signin" component={SignIn} />
    <ProtectedRoute exact path="/signup" component={SignUp} />

    <PrivateRoute exact path="/dashboard" component={Dashboard} />
    <PrivateRoute exact path="/all-leads" component={AllLeads} />
   </Switch>
 )};

const mapStateToProps = createStructuredSelector({
 token: selectUserToken,
 tokenexpiry: selectTokenExpiry
});

const mapDispatchToProps = dispatch => ({
 setUserAsync: (token) => dispatch(setUserAsync(token))
});

export default connect(mapStateToProps, mapDispatchToProps)(App);




//PrivateRoute - dashboard
const PrivateRoute = ({ component: Component,isAuthenticated, ...rest }) => {
  return (
    <Route
      {...rest}
   render={props =>
     isAuthenticated ? (
       <Component {...props} />
     ) : (
       <Redirect
         to={{ pathname: "/signin", state: { from: props.location } }}
       />
     )
    }
  />
 ) 
};

const mapStateToProps = createStructuredSelector({
  isAuthenticated: selectIsUserAuthenticated,
});

export default connect(mapStateToProps)(PrivateRoute);


//ProtectedRoute - signin & signup
const ProtectedRoute = ({component: Component, isAuthenticated, isFetching, ...rest}) => {
    return !isFetching ? (
     <Route
      {...rest}
    render={(props) => 
      isAuthenticated
      ? <Redirect to={{pathname: '/dashboard', state: {from: props.location}}} />
      : <Component {...props} /> }
   />
  )
  : null
}

const mapStateToProps = createStructuredSelector({
  isAuthenticated: selectIsUserAuthenticated,
  isFetching: selectIsFetching
});

export default connect(mapStateToProps)(ProtectedRoute);


//User reducer

const INITIAL_STATE = {
  isAuthenticated: false,
  currentUser: null
};

const UserReducer = (state = INITIAL_STATE, action) => {
  const { type, payload } = action;
  switch (type) {
case UserActionTypes.FETCH_USER_SUCCESS: {
  return {
    ...state,
    isAuthenticated: true,
    currentUser: payload
  };
}
case UserActionTypes.FETCH_USER_ERROR: {
  return {
    ...state,
    isAuthenticated: false,
    currentUser: null
  };
}
default:
  return state;
  }
};

export default UserReducer;

Upvotes: 0

Views: 464

Answers (1)

Max Starling
Max Starling

Reputation: 1037

Which kind of auth so you use?

If you use JWT and store token in the localStorage, you can do something like this:

const isAuthorized = isLoggedIn || localStorage.get('token');
/* isLoggedIn - user has just logged in with Login Page,
localStorage.get('token') - user refreshed page and he has a token,
you don't want to redirect him until token fails */

So you think of user with a token as authorized, but he can't see any backend data, because he has to fetch it.

If his token is invalid, backend will return 401 response and you will clear user token and redirect him to login.

You can also to use two Switch blocks.

const renderPrivateRoute = (item, index) => (
  <PrivateRoute key={index} {...item} />
);
const renderPublicRoute = (item, index) => (
  <PublicRoute key={index} {...item} />
);
/* ... */
{
  isAuthorized ?
(<Switch>
    {privateRoutes.map(renderPrivateRoute)} 
    <Route
        path="*"
       render={() => <Redirect to={{ pathname: DEFAULT_PRIVATE_ROUTE }} />}
    />
</Switch>) :
(<Switch>
    {publicRoutes.map(renderPublicRoute)}
    <Route
       path="*"
        render={() => <Redirect to={{ pathname: DEFAULT_PUBLIC_ROUTE }} />}
    />
</Switch>)
}

The second way it to use React Suspense but it's still experimental. You can also try to do something similar yourself. https://reactjs.org/docs/concurrent-mode-suspense.html

The third way is to persist your storage. Have a look at redux-persist npm package.

Upvotes: 0

Related Questions