16

I have created a login component on which I have all the logic stuff.

The login reducer is:

const openState = {
  loggedIn: null,
  user: null
}

export default (state = openState, action) => {
  switch (action.type) {
    case LOGIN:
      return { ...state, loggedIn: true, user: action.payload }
    case LOGOUT:
      return { ...state, loggedIn: false, user: null }
    default:
      return openState
  }
}

The Action :

export const logIn = (user) => {
  return {
    type: LOGIN,
    payload: user
  }
}

export const logOut = () => {
  return {
    type: LOGOUT
  }
}

everything is working just fine but I'm not sure how to pass the loggedIn and user props from action into the routes component in order to secure all routes:

const MainRoutes = props => {
  const { loggedIn } = props;

  console.log(props.loggedIn)

return (
  <Router history={history}>
    <Baseline />
    <Menu/>
    <Container maxWidth="md">
      <Switch>
        <Route exact path="/Login" component={Login} />
        <Route exact path="/Carousel" component={Carousel} />
        <Route exact path="/Stepper" component={Stepper} />
        <Route component={NotFound} />
      </Switch>
    </Container>
  </Router>
);
}

const mapStateToProps = (state) => {
return { loggedIn: state.loggedIn };
};

export default connect(mapStateToProps)(MainRoutes);

If I'll console.log the loggedIn props I get undefined :| Based on loggedIn I can create a logic into the routes component.

9
  • have you console state.loggedIn inside mapStateToProps ? whats it return ? Commented Feb 15, 2021 at 11:10
  • @sayalok is returning undefined, but state.auth. loggedIn is returning the default state which is null. In the login component that is true but in any other component return null Commented Feb 15, 2021 at 12:05
  • did you import your action in your main file like in app.js? Commented Feb 16, 2021 at 4:10
  • @sayalok no, not really cause I'm not using them in the app.js Commented Feb 16, 2021 at 6:15
  • if you dont import you action how are you gonna find loggedIn ? Commented Feb 16, 2021 at 6:17

6 Answers 6

11
+250

You could simply define a custom ProtectedRoute component that'll be connected to redux state. In your case it should map state.auth.loggedIn and state.auth.user to props and perform a Redirect if those values are falsy:

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


const ProtectedRoute = (props) => {
  const { redirectPath, component, user, loggedIn, ...routeProps} = props;
  const Component = component;
  const isAccessible = Boolean(user) && loggedIn;

  return (
    <Route
      {...routeProps}
      render={props => {
        if (isAccessible) return <Component {...props} />;
        return <Redirect to={{ pathname: redirectPath || "/Login" }} />;
      }}
    />
  );
};

ProtectedRoute.propTypes = {
  path: PropTypes.string.isRequired,
  redirectPath: PropTypes.string,
  component: PropTypes.oneOfType([
    PropTypes.shape({ render: PropTypes.func.isRequired }),
    PropTypes.func
  ]),
};

const mapStateToProps = (state) => {
  return { 
    loggedIn: state.auth.loggedIn, 
    user: state.auth.user 
  };
};

export default connect(mapStateToProps)(ProtectedRoute);

With this in place your MainRoutes won't need connect anymore:

const MainRoutes = props => {
  return (
    <Router history={history}>
      ...
      <Container maxWidth="md">
        <Switch>
          <Route exact path="/Login" component={Login} />
          <ProtectedRoute exact path="/Carousel" component={Carousel} />
          <ProtectedRoute exact path="/Stepper" component={Stepper} />
          <Route component={NotFound} />
        </Switch>
      </Container>
    </Router>
  );
}

export default MainRoutes;

Update:

If you want to keep auth state after page refresh you'll need to perform some extra setup of your redux store. The main idea is to subscribe on every action and put a fresh copy of state into localStorage or cookies and also to persist initialState from selected storage before your app boots up. Here is an example:

function getFromStorage(key) {
  try {
    return JSON.parse(window.localStorage.getItem(key)) || {};
  } catch (err) {
    return {};
  }
}

const initialState = getFromStorage("APP_STATE");

const store = createStore(
  rootReducer,
  initialState, // should go after root reducer
  ...
);

store.subscribe(() => {
  window.localStorage.setItem("APP_STATE", JSON.stringify(store.getState()));
});

And here's the working sandbox (bootstrapped by SuleymanSah)

Sign up to request clarification or add additional context in comments.

6 Comments

Using the render prop instead of component is much worse for performance. Be sure to implement performance optimizations like memo if using this method.
@Slbox do you have a source for this? I don't see why there would be a significant difference.
@Slbox Thanks for the source. However, in the article you provided, the author claims that there is no difference in performance between the component or render prop of a Route component.
I guess I should have read the conclusion. Here's a Stack answer on the topic.
|
4

As others already described, you had better to create a Protected Route to accomplish what you want. You simply redirect the user to the Login route if he/she is not logged in.

Here is my implementation: (codesandbox)

import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";

const ProtectedRoute = ({
  path,
  component: Component,
  render,
  loggedIn,
  ...rest
}) => {
  return (
    <Route
      path={path}
      {...rest}
      render={(props) => {
        if (loggedIn) {
          return Component ? <Component {...props} /> : render(props);
        }
        return (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: props.location }
            }}
          />
        );
      }}
    />
  );
};

const mapStateToProps = (state) => {
  const { loggedIn } = state.auth;
  return {
    loggedIn
  };
};

export default connect(mapStateToProps)(ProtectedRoute);

2 Comments

the thing is I have used your example and not really understand why on browser refresh the route become unknown, not sure why :|
@RulerNature redux state is lost when browser refreshes, that's normal. You can check redux-persist package. github.com/rt2zz/redux-persist
2

You can create a Guard which gets login status from Redux store and renders children if it is logged in, otherwise redirect to somewhere else. To get the state from the store, you can use useSelector React Hook.

import { useSelector } from 'react-redux';

function AuthGuard({children}) {
    const loggedIn = useSelector(store => store.loggedIn);
    
    if (loggedIn) {
        return children;  
    } else {
        return <Redirect to="/login" />
    }
}

and in the route

    <Container maxWidth="md">
      <Switch>
        <Route exact path="/Login" component={Login} />
        <AuthGuard>
            <Route exact path="/Carousel" component={Carousel} />
            <Route exact path="/Stepper" component={Stepper} />
        </AuthGuard>
        <Route component={NotFound} />
      </Switch>
    </Container>

Comments

2

This approach uses the official example of react router, basically you need to connect your redux store with this ProtectedRoute component

import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";

function PrivateRoute({ children, loggedIn, ...rest }) {
  return (
    <Route
      {...rest}
      render={({ location }) =>
        loggedIn ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )
      }
    />
  );
}

const mapStateToProps = (state) => {
  return { loggedIn: state.auth.loggedIn };
};

export default connect(mapStateToProps)(PrivateRoute);

As you can see you're passing the loggedIn value to this new route component, so it this is false it will return you to the login page.

In order to use it just call the component in your router:

import PrivateRoute from "./PrivateRoute";

const MainRoutes = () => {

return (
  <Router history={history}>
    <Baseline />
    <Menu/>
    <Container maxWidth="md">
      <Switch>
        <Route exact path="/Login" component={Login} />
        <PrivateRoute exact path="/Carousel" component={Carousel} />
        <PrivateRoute exact path="/Stepper" component={Stepper} />
        <Route component={NotFound} />
      </Switch>
    </Container>
  </Router>
);
}

It is a nice approach because you're not connecting the MainRoutes component to the store, which in my opinion is not something you would need in that component, you'll end up using it in the components inside the Routes.

Also you forgot to add the state.auth.loggedIn to your Main Routes mapStateToProps, that's why is undefined when you're trying to use it.

Here's a small sandbox that I did to illustrate the process: https://codesandbox.io/s/unruffled-keller-pwlib

Comments

1

What I would do in this scenario is to create a private route basically, which would only be accessible if you are logged in, or in your case, if the loggedIn in your loggedIn: state.loggedIn is true and if a user exists in state.auth.user. And if both are false, it will redirect you to the /login page to login or signup first before accessing those private routes.

So to create a private route or a <PrivateRoute />,

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


const PrivateRoute = ({ 
  component: Component,
  auth: { user, loggedIn },
  ...rest 
}) => (
  <Route 
    {...rest} 
    render={props => 
      loggedIn && Boolean(user) ? (
        <Component {...props} />  
      ) : (
        <Redirect to="/login" />
      )
    } 
  />
);

PrivateRoute.propTypes = {
  auth: PropTypes.object.isRequired
}

const mapStateToProps = state => ({
  auth: state.auth
});

export default connect(mapStateToProps)(PrivateRoute);

This will create that private route or <PrivateRoute /> for you which you can import into your MainRoutes file and then you can pass on the components to it which are supposed to be protected by the private route.

const MainRoutes = props => {
  return (
    <Router history={history}>
      <Baseline />
      <Menu/>
      <Container maxWidth="md">
        <Switch>
          <Route exact path="/Login" component={Login} />
          <PrivateRoute exact path="/Carousel" component={Carousel} />
          <PrivateRoute exact path="/Stepper" component={Stepper} />
          <Route component={NotFound} />
        </Switch>
      </Container>
    </Router>
  );
}

export default MainRoutes;

This way the components to be protected will check if the user exists and if you are logged in or not and if true, it will let you access those components.

Comments

1

The below code worked for me.

const SecretRouter = ({ component: Component, location, loggedIn , ...rest }) => (
  <Route
    location={location}
    {...rest}
    render={(props) =>
      loggedIn === true ? (
        <Component {...props} />
      ) : (
          <Redirect
            to={{
              pathname: "/Login"
            }}
          />
      )
    }
  />
);

const mapStateToProps = (state) => {
return { loggedIn: state.loggedIn };
};

export const PrivateRouter = connect(mapStateToProps)(
  SecretRouter
);

const MainRoutes = props => {

return (
  <Router history={history}>
    <Baseline />
    <Menu/>
    <Container maxWidth="md">
      <Switch>
        <Route exact path="/Login" component={Login} />
        <PrivateRouter exact path="/Carousel" component={Carousel} />
        <PrivateRouter exact path="/Stepper" component={Stepper} />
        <Route component={NotFound} />
      </Switch>
    </Container>
  </Router>
);
}


export default MainRoutes;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.