Introduction
Hi developers, in this tutorial, we will go through how to created protected routes in ReactJs. For this, I have create Node+Express+MongoDB server for user login/registration. You can use any other backend stack like PHP/Python or any other. For frontend state management I have used Zustand, you can choose any other library you prefer.
Requirements
You should have basic understanding of ReactJS.
Project Setup
npm create vite@latest
after above command, you will see wizard, choose react and javascript
For CSS and styling, I have used tailwindcss. I assume you know how to include tailwindcss in react projects. If not, please follow instruction on tailwindcss site.
Dependencies
npm i react-router-dom axios prop-types zustand
Code and Explanation
App.jsx
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Error from "./pages/Error";
import Layout1 from "./components/Layouts/Layout1";
import ProtectedRoute from "./components/Auth/ProtectedRoute";
import Dashboard from "./pages/member-pages/Dashboard";
import Login from "./pages/Login";
import Unauthorized from "./pages/Unauthorized";
function App() {
return (
<>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout1 />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="/unauthorized" element={<Unauthorized />} />
<Route path="*" element={<Error />} />
<Route element={<ProtectedRoute allowedRoles={['registered']}/>}>
<Route path="/dashboard" element={<Dashboard/>}/>
</Route>
</Route>
</Routes>
</BrowserRouter>
</>
);
}
export default App;
Auth/ProtectedRoute.jsx
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { useUserStore } from "../../store/useStore";
import PropTypes from "prop-types";
const ProtectedRoute = (props) => {
const user = useUserStore((state) => state.user);
const location = useLocation();
return props.allowedRoles.includes(user?.role)
? <Outlet />
: user ? <Navigate to="/unauthorized" state={{from: location}} replace />
: <Navigate to="/login" state={{ from: location }} replace />
};
ProtectedRoute.propTypes = {
allowedRoles: PropTypes.array,
};
export default ProtectedRoute;
Layouts/Layout1.jsx
import { Outlet } from "react-router-dom";
import Footer from "../page-parts/Footer";
import Header from "../page-parts/Header";
const Layout1 = () => {
return (
<>
<div className="m-auto max-w-5xl border border-black bg-blue-950 text-white my-10 rounded-xl">
<Header />
<div className="p-10">
<Outlet />
</div>
<Footer />
</div>
</>
);
};
export default Layout1;
page-parts/Header.jsx
import { Link } from "react-router-dom";
import { useUserStore } from "../../store/useStore";
const Header = () => {
const user = useUserStore((state) => state.user);
const signOut = useUserStore((state) => state.signOut);
return (
<div className="bg-gray-300 text-black rounded-t-xl">
<nav className="flex justify-between">
<img src="https://picsum.photos/100/50.jpg" />
<ul className="flex gap-5 p-3">
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
<li>
<Link to="/services">Services</Link>
</li>
{user ? (
<>
<li>
<Link to="/dashboard">Dashboard</Link>
</li>
<li>
<button onClick={() => signOut()}>Logout</button>
</li>
</>
) : (
<li>
<Link to="/login">Login</Link>
</li>
)}
</ul>
</nav>
</div>
);
};
export default Header;
page-parts/Footer.jsx
const Footer = () => {
return (
<div className="text-white w-full ">
<div className="flex items-center justify-center">Copyright © 2023</div>
</div>
);
};
export default Footer;
pages/Home.jsx
const Home = () => {
return (
<div className="text-justify">
<h1 className="text-4xl">Welcome to Home Page</h1>
</div>
)
}
export default Home
pages/About.jsx
const About = () => {
return (
<div>About</div>
)
}
export default About
pages/Error.jsx
const Error = () => {
return (
<div>404 - not found</div>
)
}
export default Error
pages/Login.jsx
import { useState } from "react";
import { useUserStore } from "../store/useStore";
import { useNavigate } from "react-router-dom";
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const signin = useUserStore((state) => state.signIn);
const navigate = useNavigate();
const handleLogin = async() => {
const status = await signin(email, password);
if (status) {
navigate("/dashboard");
}
};
return (
<>
<h3 className="text-2xl text-center">Login</h3>
<hr className="headingHr" />
<div className="m-auto w-[400px] rounded p-3 flex flex-row justify-center bg-slate-400">
<table className="">
<tbody>
<tr>
<td>
<label htmlFor="email">Email</label>
</td>
<td>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
name=""
id="email"
/>
</td>
</tr>
<tr>
<td>
<label htmlFor="password">Password</label>
</td>
<td>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
name=""
id="password"
/>
</td>
</tr>
<tr>
<td></td>
<td>
<button onClick={() => handleLogin()} className="button">
Login
</button>
</td>
</tr>
</tbody>
</table>
</div>
</>
);
};
export default Login;
pages/Unauthorized.jsx
import { useUserStore } from "../store/useStore"
const Unauthorized = () => {
const user = useUserStore(state => state.user);
console.log(user);
return (
<div>You are not allowed to do this action!!!</div>
)
}
export default Unauthorized
pages/member-pages/dashboard
const Dashboard = () => {
return (
<div>Dashboard</div>
)
}
export default Dashboard
store/axiosHTTP.js
import axios from "axios";
const axiosHttp = axios.create({
baseURL: 'http://localhost:3000'
});
export default axiosHttp;
store/BaseSlice.js
export const BaseSlice = (set) => ({
name: 'Base Slice',
loading: false,
setLoading: (stat) => {
set((state) => ({
...stat,
loading: state
}))
}
});
store/UserSlice.js
import axiosHttp from "./axiosHttp";
export const UserSlice = (set) => ({
name: 'User',
user: null,
token: null,
setUser: async(user) => {
set({user: user});
},
getUser: async(id) => {
const json = await axiosHttp.get(`/user/${id}`);
return await json.data;
},
signIn: async (email, password) => {
try {
//await timeout(2000);
const json = await axiosHttp.post(`${import.meta.env.VITE_SERVER_URL}/api/v1/auth/signin`, { email, password });
const data = await json.data;
//console.log(data.user);
set(() => ({
user: data.user,
token: data.token,
}));
return true;
} catch (error) {
return null;
}
},
signUp: async (form) => {
try {
const json = await axiosHttp.post("/register", form);
const data = await json.data;
set(() => ({
user: data.user,
token: data.accessToken,
}));
return {token: data.accessToken, error: null, message: 'User registered'}
} catch (error) {
const errMsg = await error.response;
console.log(errMsg.data);
return {message: errMsg.data, error: true, token: null};
}
},
signOut: () => {
set({
user: null,
token: null,
});
},
});
store/useStore.js
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware"
import { BaseSlice } from "./BaseSlice";
import { UserSlice } from "./UserSlice";
import { SongSlice } from "./SongSlice";
export const useBaseStore = create((set, get) => ({
...BaseSlice(set, get)
}));
export const useUserStore = create(
persist((set, get) => ({
...UserSlice(set, get)
}),{name: 'userStore',storage: createJSONStorage(() => sessionStorage)}
));
From backend API, we need following format response at time of login.
Once you completed these steps, check your website.
Happy Coding.
Top comments (0)