DEV Community

Cover image for Protected routes in react with react router and redux
Akpan Akan
Akpan Akan

Posted on • Updated on

Protected routes in react with react router and redux

Protected routes can only be accessed by authenticated users in an application. React-router and redux have been winning a combination for a lot of SPA(single page applications), but for a newbie, figuring how to combine these two packages to implement a protected route can seem a bit complex. We'll be looking at how to implement protected routes with react-router and redux in a simplified manner.

We'll assume you are familiar with react. If however, you're unfamiliar with react you can checkout https://reactjs.org/docs/getting-started.html.

SETUP

We'll start off by spinning up a react app with CRA(create-react-app). To learn more about CRA checkout https://reactjs.org/docs/create-a-new-react-app.html.

npx create-react-app my-protected-app
Enter fullscreen mode Exit fullscreen mode

Dependencies

Redux is an open source library for managing state in centralised manner, it is very popular in the frontend community and a must for many dev roles.

React router provides declarative routing for react. It is the go to library for react SPAs routing.

Install these dependencies to get going started

yarn add react-router-dom redux react-redux
or 
npm install react-router-dom redux react-redux --save
Enter fullscreen mode Exit fullscreen mode

Setting up our app

NB: you can skip this part if your app is already setup

First we'll create a Home component.

import React from "react";

const Home = () => {
  return (
    <div className="App">
      <h1>Welcome to my protected route!</h1>
      <h2>We've got cookies</h2>
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

Home view on browser
Home page view

Then we'll create a Login component from which the user logs in to access the home page.

import React from "react";

const Login = () => {

  return (
    <div className="App">
      <div className="login-form">
        <h4 className="form-title">Login</h4>
        <div className="form-control">
          <input type="text" name="username" placeholder="Username" />
        </div>
        <div className="form-control">
          <input type="password" placeholder="Enter password" name="password" />
        </div>
        <button className="login-btn">
          Login
        </button>
      </div>
    </div>
  );
};

export default Login;
Enter fullscreen mode Exit fullscreen mode

Login view
Login view

Then we add the style.css for the app styling.

html {
  box-sizing: border-box;
}
*,
*:before,
*:after {
  box-sizing: inherit;
}

.App {
  font-family: sans-serif;
  text-align: center;
  height: 100vh;
  width: 100%;
}

.login-form {
  width: 450px;
  padding: 20px 25px;
  border-radius: 10px;
  border: solid 1px #f9f9f9;
  text-align: left;
  margin: auto;
  margin-top: 50px;
  background: #b88bf72f;
}

.form-title {
  margin-top: 0;
  margin-bottom: 15px;
  text-align: center;
}

.form-control {
  margin-bottom: 15px;
  width: 100%;
}

.form-control input {
  border-radius: 5px;
  height: 40px;
  width: 100%;
  padding: 2px 10px;
  border: none;
}

.login-btn {
  padding: 5px 10px;
  border-radius: 5px;
  border: none;
  background: rgb(60, 173, 239);
  color: #fff;
}

Enter fullscreen mode Exit fullscreen mode

Setting up redux

Let's create store directory, then a types.js file in src/store to export our different store action types

export const LOGIN_USER = "LOGIN USER";
Enter fullscreen mode Exit fullscreen mode

Next, we'll create a store.js file in our src/store folder. Here we instantiate our store and it's initial state.

import { createStore } from "redux";
import { LOGIN_USER } from "./types";

const intitialState = {
  authenticated: false
};

const reducer = (state = intitialState, action) => {
  switch (action.type) {
    case LOGIN_USER:
      return { ...state, authenticated: true };

    default:
      return state;
  }
};

const store = createStore(reducer);

export default store;
Enter fullscreen mode Exit fullscreen mode

Our initial state object contains an authenticated state which is false by default, indicating a user is not logged in. We see more on changing this state. Checkout createStore to learn more about setting up redux.

Setting up react-router

In the src/index.js file

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>,
  document.getElementById("root")
);

Enter fullscreen mode Exit fullscreen mode

We import BrowserRouter and wrap our App component in it.

At this point we create our ProtectedRoute to handle verifying a user is authenticated before rendering the component. If the user is not authenticated we want to redirect them to the login page.

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

const ProtectedRoute = ({ path, exact, children }) => {
  const auth = useSelector((store) => store.authenticated);

  return auth ? (
    <Route path={path} exact={exact}>
      {children}
    </Route>
  ) : (
    <Redirect to="/login" />
  );
};

export default ProtectedRoute;
Enter fullscreen mode Exit fullscreen mode

We check the authenticated state in our redux store and render the component on the condition that it authenticated is true.

Next, in our App.js we add Switch giving our app ability to switch components between routes. We also bring in our components, our protected route and set up our store in our App component.

import React from "react";
import "./styles.css";
import { Provider } from "react-redux";
import store from "./store";
import { Route, Switch } from "react-router-dom";
import ProtectedRoute from "./ProtectedRoute";
import Home from "./Home";
import Login from "./Login";

const App = () => {
  return (
    <Provider store={store}>
      <Switch>
        <Route path="/login">
          <Login />
        </Route>
        <ProtectedRoute exact path="/">
          <Home />
        </ProtectedRoute>
      </Switch>
    </Provider>
  );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

Finishing up

To finish things up, we modify our Login component with the ability to change the state at login.

import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { useHistory } from "react-router";

import { LOGIN_USER } from "./store/types";

const Login = () => {
  const dispatch = useDispatch();
  const history = useHistory();
  const [inputs, setInput] = useState({ username: "", password: "" });

  function inputChange(e) {
    setInput({ ...inputs, [e.target.name]: e.target.value });
  }

  function login(event) {
    event.preventDefault();
    if (!inputs.username || !inputs.password) return;
    dispatch({ type: LOGIN_USER });
    history.push("/");
  }

  return (
    <div className="App">
      <form onSubmit={login} className="login-form">
        <h4 className="form-title">Login</h4>
        <div className="form-control">
          <input
            type="text"
            name="username"
            placeholder="Username"
            onChange={inputChange}
          />
        </div>
        <div className="form-control">
          <input
            type="password"
            placeholder="Enter password"
            name="password"
            onChange={inputChange}
          />
        </div>
        <button type="submit" className="login-btn">
          Login
        </button>
      </form>
    </div>
  );
};

export default Login;
Enter fullscreen mode Exit fullscreen mode

We use the useDispatch hook to dispatch actions to our redux store. Notice we use the LOGIN_USER type we create in our store/types in dispatch. We finally round by routing to the home route with the useHistory from react-router. Now as far as our inputs aren't empty we can login to the home page.

From here, more can be done to add extra features, congrats on your protected route.

Oldest comments (2)

Collapse
 
irushan22 profile image
Irushan Bandara

what happen when reload the web page?

Collapse
 
akanstein profile image
Akpan Akan • Edited

the state is lost, to overcome this you can make use of packages that persist state in the browser. like redux-persist