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
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
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;
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;
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;
}
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";
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;
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")
);
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;
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;
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;
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.
Top comments (2)
what happen when reload the web page?
the state is lost, to overcome this you can make use of packages that persist state in the browser. like redux-persist