Protected Routes in React using React Router v6
Learn to create Protected Routes known also as Private Routes in React using React Router v6
What is React Router?
React Router is the standard routing library for React. From the docs:
What is Protected Routes?
Protected Routes known also as Private Routes and also Authorized Routes in React Router require a user being authorized to visit a route. So if a user is not authorized for a specific page, they cannot access it.
The most usual example is authentication in a React application where a user can only access the protected web pages when they are authorized (which means in this situation being authenticated).
Authorization goes beyond authentication though. For example, a user can also have roles and permissions which give a user access to specific areas of the application.
About this Tutorial
This is a React Router tutorial which teaches you how to use Private Routes with React Router 6.
Using a bundler
Feel free to use your bundler of choice like:
🐧 Vite
Using YARN 👷♂️
This will clone the JavaScript react template.
$ yarn create vite krython-app --template react
This will clone the TypeScript react template.
$ yarn create vite krython-app --template react-ts
Using NPM 👷♀️
This will clone the JavaScript react template.npm 6.x
$ npm create vite@latest krython-app --template react
npm 7+, extra double-dash is needed:
$ npm create vite@latest krython-app -- --template react
🐧 Create React App
If you've previously installed create-react-app globally via npm install -g create-react-app
, we recommend you uninstall the package using npm uninstall -g create-react-app
or yarn global remove create-react-app
to ensure that npx always uses the latest version.
Using YARN 👷♂️
This will clone the JavaScript react template.
$ yarn create react-app krython-app
This will clone the TypeScript react template.
$ yarn create react-app krython-app --template typescript
Using NPM 👷♀️
This will clone the JavaScript react template.
$ npx create-react-app krython-app
This will clone the TypeScript react template.
$ npx create-react-app krython-app --template typescript
Verify that your setup works
Navigate to your app folder which is krython-app
in our case then start it
Using YARN 👷♂️
🐧 Vite
navigate to our app
$ cd krython-app
install node modules
$ yarn
start react app
$ yarn dev
🐧 Create React App
$ cd krython-app
$ yarn start
Using NPM 👷♀️
🐧 Vite
navigate to our app
$ cd krython-app
install node modules
$ npm i
start react app
$ npm run dev
🐧 Create React App
$ cd krython-app
$ npm run start
Using Vite navigate to http://localhost:3000/
then you should see:
Using Create React Route navigate to http://localhost:3000/
then you should see:
Add React Router to existing project
Navigate to your app folder then install React Router
Using YARN 👷♂️
yarn add react-router-dom@6
Using NPM 👷♀️
npm install react-router-dom@6
Let's Begin
Now we should have everything needed to start using React Router.
Create React App && Vite bundlers have the same concepts for adding React Router. So we will stick with Vite for the rest of this tutorial.
🌟 Connect the URL
First things first, we want to connect your app to the browser's URL: import BrowserRouter
and render it around your whole app.
In our case we will import it and use it inside main.jsx
file.
After adding, it should look like this :
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import AppRouter from "./AppRouter";
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<AppRouter />
</BrowserRouter>
</React.StrictMode>,
document.getElementById("root")
);
Remove your App.jsx
file we no longer need it.
🌟 Create App Router
Now we need to create an App Router known as App Container and App Navigator. we will put inside it our layout and our pages and it will teach React Router how to render our app at different URLs.
Create a AppRouter.jsx
inside our src
folder and put:
import React from "react";
import { Routes, Route } from "react-router-dom";
// Layout
import Layout from "./Layout";
// Pages
import PublicPage from "./pages/Public";
import LoginPage from "./pages/Login";
import PrivatePage from "./pages/Private";
const AppRouter = () => {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<PublicPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/private" element={<PrivatePage />} />
</Route>
</Routes>
);
};
export default AppRouter;
🌟 Create Our Layout
Layout
is the wrapper component (THE PARENT COMPONENT) of our pages, we will add the navigations links inside it so we don't have to add it inside all of our pages.
import React from "react";
import { Link, Outlet } from "react-router-dom";
const Layout = () => {
return (
<>
<h1>Protected Routes in React using React Router v6</h1>
<ul>
<li>
<Link to="/">Public Page</Link>
</li>
<li>
<Link to="/private">Private Page</Link>
</li>
</ul>
<div>
<Outlet />
</div>
</>
);
};
export default Layout;
🌟 Create Our Pages
Let's create our pages, as you saw before inside AppRouter.jsx
we have three different pages. PublicPage
page and PrivatePage
page and LoginPage
, they located inside pages
folder.
Create Public.jsx
inside pages
folder.
import React from "react";
const PublicPage = () => {
return <p>Public Page. I'm located at /.</p>;
};
export default PublicPage;
Create Private.jsx
inside pages
folder.
import React from "react";
const PrivatePage = () => {
return <p>Private Page. I'm located at /private.</p>;
};
export default PrivatePage;
🌟 Create Auth Provider
Create Auth Provider and wrap it around our app, so we can keep track our authentication status.
Now create AuthProvider.jsx
import React from "react";
const AuthContext = React.createContext(null);
const AuthProvider = ({ children }) => {
const [isAuthenticated, seIsAuthenticated] = React.useState(false);
const login = (email, password, callback) => {
// Do something to login
seIsAuthenticated(true);
// Simulate a delay
setTimeout(() => {
callback();
}, 500);
};
return (
<AuthContext.Provider
value={{
isAuthenticated,
login,
}}
>
{children}
</AuthContext.Provider>
);
};
const useAuth = () => {
const context = React.useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth must be used within a AuthProvider");
}
return context;
};
export { AuthProvider, useAuth };
Wrap our app with Auth Provider. Update Layout.jsx
import React from "react";
import { Link, Outlet } from "react-router-dom";
// Auth Provider
import { AuthProvider } from "./AuthProvider";
const Layout = () => {
return (
<AuthProvider>
<h1>Protected Routes in React using React Router v6</h1>
<ul>
<li>
<Link to="/">Public Page</Link>
</li>
<li>
<Link to="/private">Private Page</Link>
</li>
</ul>
<div>
<Outlet />
</div>
</AuthProvider>
);
};
export default Layout;
🌟 Create Require Auth Component
This component will redirect user to Login
page if user not authenticated, else it will render the Private
page.
Update AppRouter.jsx
import React from "react";
import { Routes, Route, Navigate } from "react-router-dom";
// Layout
import Layout from "./Layout";
// Pages
import PublicPage from "./pages/Public";
import LoginPage from "./pages/Login";
import PrivatePage from "./pages/Private";
// Auth Provider
import { useAuth } from "./AuthProvider";
const RequireAuth = ({ children }) => {
const { isAuthenticated } = useAuth();
return isAuthenticated ? children : <Navigate to="/login" replace />;
};
const AppRouter = () => {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<PublicPage />} />
<Route path="/login" element={<LoginPage />} />
<Route
path="/private"
element={
<RequireAuth>
<PrivatePage />
</RequireAuth>
}
/>
</Route>
</Routes>
);
};
export default AppRouter;
🌟 Create Our Login Page
Finally, let's create our login
page:
import React from "react";
import { useNavigate } from "react-router-dom";
// Auth Provider
import { useAuth } from "../AuthProvider";
const LoginPage = () => {
const navigate = useNavigate();
const { login } = useAuth();
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const { email, password } = {
email: formData.get("email"),
password: formData.get("password"),
};
login(email, password, () => {
// Redirect after login
navigate("/private", { replace: true });
});
};
return (
<div>
<p>You must log in to view the private page</p>
<form onSubmit={handleSubmit}>
<label>
Email: <input name="email" type="text" />
</label>
<label>
Password: <input name="password" type="password" />
</label>
<button type="submit">Login</button>
</form>
</div>
);
};
export default LoginPage;