By Hemanta Sundaray on 2022-09-10
In this post, we will learn how to create protected routes in React Router v6.
Our example application looks like the following:
import React, { useContext } from "react"
import { NavLink, useNavigate } from "react-router-dom"
import { AuthContext } from "./UserInfo"
const Header = () => {
const { name, setName } = useContext(AuthContext)
const navigate = useNavigate()
const handleLogout = () => {
setName(null)
navigate("/")
}
return (
<>
{name ? <button onClick={handleLogout}>Logout</button> : ""}
<nav>
<ul>
<NavLink to="/about">
<li>About</li>
</NavLink>
<NavLink to="/blog">
<li>Blog</li>
</NavLink>
<NavLink to="/secret">
<li>Secret</li>
</NavLink>
</ul>
</nav>
</>
)
}
export default Header
import React from "react"
import { Routes, Route } from "react-router-dom"
import Header from "./components/Header"
import Home from "./components/Home"
import About from "./components/About"
import Blog from "./components/Blog"
import Secret from "./components/Secret"
const App = () => {
return (
<>
<header>
<Header />
</header>
<main>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/blog" element={<Blog />} />
<Route path="/secret" element={<Secret />}></Route>
</Routes>
</main>
</>
)
}
export default App
Let’s say we want to protect the /secret route.
We want the Secret page to be accessible only to authenticated users.
To do so, the first step we need to do is to create a <PrivateRoute /> component.
import React, { useContext } from "react"
import { Navigate, Outlet, useLocation } from "react-router-dom"
import { AuthContext } from "./UserInfo"
const PrivateRoute = () => {
const { name } = useContext(AuthContext)
const location = useLocation()
return name ? <Outlet /> : <Navigate to="/login" state={{ from: location }} />
}
export default PrivateRoute
The role of the PrivateRoute component is to check the authenticated status of the user and direct the user to either the Secret page (if the user is authenticated) or the Login page (if the user is not authenticated).
We are using React Context API to store the authentication status of the user and share that status across many components. Below, on line 6, notice that we have passed null to useState, which means that the user is not authenticated by default.
import React, { useState, createContext } from "react"
export const AuthContext = createContext()
const UserInfo = ({ children }) => {
const [name, setName] = useState(null)
const user = {
name,
setName,
}
return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>
}
export default UserInfo
We wrap the App component with the UserInfo component, so that the value prop of the AuthContext.Provider component becomes available to all of App’s descendant components.
import React from "react"
import ReactDOM from "react-dom/client"
import "./index.css"
import App from "./App"
import { BrowserRouter as Router } from "react-router-dom"
import UserInfo from "./components/UserInfo"
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<React.StrictMode>
<Router>
<UserInfo>
<App />
</UserInfo>
</Router>
</React.StrictMode>
)
After we create the PrivateRoute component, we wrap the secret route with the secret route, (the parent secret route) that renders the PrivateRoute component.
import React from "react"
import { Routes, Route } from "react-router-dom"
import About from "./components/About"
import Blog from "./components/Blog"
import Header from "./components/Header"
import PrivateRoute from "./components/PrivateRoute"
import Secret from "./components/Secret"
import Login from "./components/Login"
import Home from "./components/Home"
const App = () => {
return (
<>
<header>
<Header />
</header>
<main>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/blog" element={<Blog />} />
<Route path="/login" element={<Login />} />
<Route path="/secret" element={<PrivateRoute />}>
<Route path="/secret" element={<Secret />}></Route>
</Route>
</Routes>
</main>
</>
)
}
export default App
We have not created the Login component yet. Let’s do that now.
import React, { useContext } from "react"
import { AuthContext } from "./UserInfo"
import { useNavigate, useLocation } from "react-router-dom"
const Login = () => {
const { name, setName } = useContext(AuthContext)
const navigate = useNavigate()
const location = useLocation()
const handleLogin = () => {
setName("Hemanta")
navigate(location.state ? location.state.from.pathname : "/")
}
return !name && <button onClick={handleLogin}>Login</button>
}
export default Login
On line 12, notice that, upon login, we are hard-coding the value ("Hemanta") of the name variable. In a real-world application, however, you would most likely get this data (the name of the user or any other user attribute that you are using to keep track of the authentication status) from an API endpoint.
Now, if we click on the Secret link, we get directed to the Login page. We have successfully protected the /secret route.
Click on Login and we have access to the Secret page.
Click on Logout and we get directed to the Home page.
This is how we protect routes in React Router v6.