React Router v6 is a popular and powerful routing library for React applications. It provides a declarative, component-based approach to routing and handles the common tasks of dealing with URL params, redirects, and loading data.
React Router provides one of the most intuitive APIs available and enables lazy loading and SEO-friendly server-side rendering. This latest version of React Router introduced many new concepts, like <Outlet />
and layout routes, but the documentation is still sparse.
This tutorial will demonstrate how to create protected routes and add authentication with React Router v6.
So, fire up your favorite text editor, and let’s dive in!
Jump ahead:
Getting started
Open up the terminal and create a new React project by running the following command:
> npx create-react-app ReactRouterAuthDemo > cd ReactRouterAuthDemo
Next, install React Router as a dependency in the React app:
> npm install react-router-dom
Once the React Router dependency is installed, we’ll need to edit the src/index.js
file.
Import BrowserRouter
from react-router-dom
and then wrap the <App />
component with <BrowserRouter />
, like so:
import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import App from "./App"; const rootElement = document.getElementById("root"); const root = createRoot(rootElement); root.render( <StrictMode> <BrowserRouter> <App /> </BrowserRouter> </StrictMode> );
Now, we’re all set up to use React Router components and hooks from anywhere in our app.
Let’s replace the boilerplate code from the App.js
file with some routes.
Basic routing
React Router provides the <Routes />
and <Route />
components that enable us to render components based on their current location.
import { Routes, Route } from "react-router-dom"; import { LoginPage } from "./pages/Login"; import { HomePage } from "./pages/Home"; import "./styles.css"; export default function App() { return ( <Routes> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> </Routes> ); }
Basic routing with <Route />
<Route />
provides the mapping between paths on the app and different React components. For example, to render the LoginPage
component when someone navigates to /login
, we just need to provide the <Route />
, like so:
<Route path="/login" element={<LoginPage />} />
The <Route />
component can be thought of like an if statement; it will act upon a URL location with its element only if it matches the specified path.
Basic routing with <Routes
/>
The <Routes />
component is an alternative to the <Switch />
component from React Router v5.
To use <Routes />
, we’ll first create Login.jsx
and Home.jsx
files inside the pages directory with the following content:
// Login.jsx export const LoginPage = () => ( <div> <h1>This is the Login Page</h1> </div> ); // Home.jsx export const HomePage = () => ( <div> <h1>This is the Home Page</h1> </div> );
Next, we’ll run this command to start the app:
> npm run start
On the browser, we see the HomePage
component by default. If we go the /login
route we’ll see the LoginPage
component render on the screen.
Alternatively, we can use a plain JavaScript object to represent the routes in our app using the useRoutes
hook. This is a functional approach for defining routes and works in the same manner as the combination of <Routes
/>
and <Route />
components.
import { useRoutes } from "react-router-dom"; // ... export default function App() { const routes = useRoutes([ { path: "/", element: <HomePage /> }, { path: "/login", element: <LoginPage /> } ]); return routes; }
Now that the basic setup is completed, let’s look at how we can create protected routes so that unauthenticated users cannot access certain content in our application.
Creating protected routes
Before creating the protected route, let’s create a custom hook that will handle the authenticated user’s state using the Context API and useContext
hook.
import { createContext, useContext, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { useLocalStorage } from "./useLocalStorage"; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useLocalStorage("user", null); const navigate = useNavigate(); // call this function when you want to authenticate the user const login = async (data) => { setUser(data); navigate("/profile"); }; // call this function to sign out logged in user const logout = () => { setUser(null); navigate("/", { replace: true }); }; const value = useMemo( () => ({ user, login, logout }), [user] ); return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; }; export const useAuth = () => { return useContext(AuthContext); };
With the useAuth
hook, we are exposing the user’s state and a couple of methods for user login and logout. When the user logs out, we redirect them to the home page using React Router’s useNavigate
hook.
To persist the user’s state even on page refresh, we‘ll use the useLocalStorage
hook which will sync the state value in the browser’s local storage.
import { useState } from "react"; export const useLocalStorage = (keyName, defaultValue) => { const [storedValue, setStoredValue] = useState(() => { try { const value = window.localStorage.getItem(keyName); if (value) { return JSON.parse(value); } else { window.localStorage.setItem(keyName, JSON.stringify(defaultValue)); return defaultValue; } } catch (err) { return defaultValue; } }); const setValue = (newValue) => { try { window.localStorage.setItem(keyName, JSON.stringify(newValue)); } catch (err) {} setStoredValue(newValue); }; return [storedValue, setValue]; };
The <ProtectedRoute />
component will simply check the current user state from the useAuth
hook and then redirect to the Home screen if the user is not authenticated.
import { Navigate } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const ProtectedRoute = ({ children }) => { const { user } = useAuth(); if (!user) { // user is not authenticated return <Navigate to="/" />; } return children; };
To redirect the user, we use the <Navigate />
component. The <Navigate />
component changes the current location when it is rendered by the parent component. It uses the useNavigate
hook internally.
In the App.js
file, we can wrap the page component with the <ProtectedRoute />
component. For example below, we are wrapping the <SettingsPage />
and <ProfilePage />
components with <ProtectedRoute />
. Now when unauthenticated users try to access /profile
or /settings
path they will be redirected to the home page.
import { Routes, Route } from "react-router-dom"; import { LoginPage } from "./pages/Login"; import { HomePage } from "./pages/Home"; import { SignUpPage } from "./pages/SignUp"; import { ProfilePage } from "./pages/Profile"; import { SettingsPage } from "./pages/Settings"; import { ProtectedRoute } from "./components/ProtectedRoute"; export default function App() { return ( <Routes> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> <Route path="/register" element={<SignUpPage />} /> <Route path="/profile" element={ <ProtectedRoute> <ProfilePage /> </ProtectedRoute> } /> <Route path="/settings" element={ <ProtectedRoute> <SettingsPage /> </ProtectedRoute> } /> </Routes> ); }
Well, the above approach works fine if there are a limited number of protected routes, but if there are multiple such routes we would have to wrap each one, which is tedious.
Instead, we can use the React Router v6 nested route feature to wrap all the protected routes in a single layout.
Using nested routes and <Outlet />
One of the most powerful features in React Router v6 is nested routes. This feature allows us to have a route that contains other child routes. The majority of our layouts are coupled to segments on the URL, and React Router supports this fully.
For example, we can add a parent <Route />
component to the <HomePage />
and <LoginPage />
routes, like so:
import { ProtectedLayout } from "./components/ProtectedLayout"; import { HomeLayout } from "./components/HomeLayout"; // ... export default function App() { return ( <Routes> <Route element={<HomeLayout />}> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> </Route> <Route path="/dashboard" element={<ProtectedLayout />}> <Route path="profile" element={<ProfilePage />} /> <Route path="settings" element={<SettingsPage />} /> </Route> </Routes> ); }
The parent <Route />
component can also have a path and is responsible for rendering the child <Route />
component on the screen.
When the user navigates to /dashboard/profile
the router will render the <ProfilePage />
. In order for this to occur, the parent route element must have an <Outlet />
component to render the child elements. The Outlet
component enables nested UI to be visible when child routes are rendered.
The parent route element can also have additional common business logic and user interface. For example, in the <ProtectedLayout />
component we have included the private route logic and also a common navigation bar that will be visible when the child routes are rendered.
import { Navigate, Outlet } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const ProtectedLayout = () => { const { user } = useAuth(); if (!user) { return <Navigate to="/" />; } return ( <div> <nav> <Link to="/settings">Settings</Link> <Link to="/profile">Profile</Link> </nav> <Outlet /> </div> ) };
Instead of the <Outlet />
component, we can also opt to use the useOutlet
hook which serves the same purpose:
import { Link, Navigate, useOutlet } from "react-router-dom"; // ... export const ProtectedLayout = () => { const { user } = useAuth(); const outlet = useOutlet(); if (!user) { return <Navigate to="/" />; } return ( <div> <nav> <Link to="/settings">Settings</Link> <Link to="/profile">Profile</Link> </nav> {outlet} </div> ); };
Similar to protected routes, we do not want authenticated users to access the /login
path. Let’s handle that in the <HomeLayout />
component:
import { Navigate, Outlet } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const HomeLayout = () => { const { user } = useAuth(); if (user) { return <Navigate to="/dashboard/profile" />; } return ( <div> <nav> <Link to="/">Home</Link> <Link to="/login">Login</Link> </nav> <Outlet /> </div> ) };
You can check out the complete code and demo in this CodeSandbox.
Conclusion
It’s worth investing some time to better understand how React Router v6 works, particularly for the common use case of user authentication.
React Router v6 is a huge improvement over previous versions. It’s fast, stable, and reliable. In addition to being easier to work with, it has many new features like <Outlets />
and an improved <Route />
component that have greatly simplified routing in React apps.
I hope you found this guide helpful and now have a better understanding of how to handle user authentication with React Router v6.
The post Complete guide to authentication with React Router v6 appeared first on LogRocket Blog.
from LogRocket Blog https://ift.tt/DEBSAnz
via Read more