React Router 6 🚀

Udemy John React Router 6

Docs

React Router Docs

To install react-router-6

npm install react-router-dom@6

// For latest version
npm install react-router-dom

First page

import { BrowserRouter, Routes, Route } from 'react-router-dom'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<div>home page</div>} />
        <Route
          path="testing"
          element={
            <div>
              <h2>testing </h2>
            </div>
          }
        />
      </Routes>
    </BrowserRouter>
  )
}

export default App

Components

import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Products from './pages/Products'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="products" element={<Products />} />
      </Routes>
    </BrowserRouter>
  )
}

export default App

If we want to navigate through our app internally, then we use Link. Link is similar to <a href but is used to open different page in our app and not external URLs like google.com

Let's say I am in Home page, / and I want to navigate to About page, /about then we can either type the /about in URL or use Link (when clicked will take us to /about page).

import { Link } from 'react-router-dom'

const Home = () => {
  return (
    <section className="section">
      <h2>Home Page</h2>
      <Link to="/about" className="btn">
        About
      </Link>
    </section>
  )
}
export default Home

If we need to go to external URLs like google.com, then we still need to use <a href

Error Page

If we navigate to non-existing URL within our app then it shows a blank page by default which is not a good user experience. We can define a wildcard route (*) which goes to the page we say if none of the above routes match

import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Error from './pages/Error'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="about" element={<About />} />
        
        // DEFAULT ROUTE IF ABOVE ROUTES DON'T MATCH
        <Route path="*" element={<Error />} /> 
      </Routes>
    </BrowserRouter>
  )
}

export default App
import { Link } from 'react-router-dom'

const Error = () => {
  return (
    <section className="section">
      <h1>404</h1>
      <h2>Page not found</h2>
      <Link to="/" className="btn">
        Back Home
      </Link>
    </section>
  )
}
export default Error

We want a navbar/ footer that will be common to all the pages (Home, about, and products). We can achieve it by

  • nesting these routes inside common path. For example, we want all routes to start at /

    • /

    • /about

    • /products

  • Once we nest them, we also need to use outlet section inside main route / so that this <Outlet/> will be either Home, Or About or Products

Understand this diagrammatically here Explained Shared layout diagrammatically

We need to know which link is selected, so we often add an active class. We can make use of NavLink provided by react-router-dom instead of Link. The difference is, the NavLink will show us if selected link is active or not.

import { NavLink } from 'react-router-dom'

<nav className="navbar">
  <NavLink
    to="/about"
    style={({ isActive }) => {
      return { color: isActive ? 'red' : 'grey' }
    }}
  >
    Home
  </NavLink>
</nav>

So our Navbar can look like this, let's name it StyledNavbar

StyledNavbar.js
import React from 'react'
import { NavLink } from 'react-router-dom'

const active = (isActive) => {
  return isActive ? 'link active' : 'link'
}

export const StyledNavbar = () => {
  return (
    <nav className="navbar">
      <NavLink to="/" className={({ isActive }) => active(isActive)}>
        Home
      </NavLink>
      <NavLink className={({ isActive }) => active(isActive)} to="/about">
        About
      </NavLink>
      <NavLink className={({ isActive }) => active(isActive)} to="/products">
        Products
      </NavLink>
      <NavLink className={({ isActive }) => active(isActive)} to="/login">
        Login
      </NavLink>
    </nav>
  )
}

We can use inline style or className as above.

URL Params (useParams)

Let's say we have products page that contains a list of products, and when we click on a product it will take us to details of individual product. We can make use of useParam() hook to see dynamically what ID is passed

App.js
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<SharedLayout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          // These two routes below (products and it's id route can also be nested. Will see later how)
          <Route path="products" element={<Products />} />
          
          // this id will be passed as a parameter in url and we can know that id using useParam hook
          <Route path="products/:productId" element={<SingleProduct />} /> 
          <Route path="*" element={<Error />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}
Products.js
import { Link } from 'react-router-dom'
import products from '../data'
const Products = () => {
  return (
    <section className="section">
      <h2>products</h2>
      <div className="products">
        {products.map((product) => {
          return (
            <article key={product.id}>
              <h5>{product.name}</h5>
              <Link to={`/products/${product.id}`}>more info</Link>
            </article>
          )
        })}
      </div>
    </section>
  )
}

export default Products
SingleProduct.js
import { Link, useParams } from 'react-router-dom'
import products from '../data'
const SingleProduct = () => {

  // we get the dynamically passed parameter using useParams()
  const { productId } = useParams()

  return (
    <section className="section product">
      <h2>{productId}</h2>
      <Link to="/products">back to products</Link>
    </section>
  )
}

export default SingleProduct

Let's say we have a /login route that displays a login form, and when user logs in by filling creds, it should navigate us to Dashboard page. In Dashboard page, if user entered the creds, it should show "Hello, name", else it should display "Hello"

App.js
function App() {
  const [user, setUser] = useState(null)

  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<SharedLayout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          
          // products and products/:productId could be nested (we will see how to do this later)
          <Route path="products" element={<Products />} />
          <Route path="products/:productId" element={<SingleProduct />} />
          
          <Route path="login" element={<Login setUser={setUser} />} />
          <Route path="dashboard" element={<Dashboard user={user} />} />
          <Route path="*" element={<Error />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}
Login.js
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
const Login = ({ setUser }) => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!name || !email) return;
    setUser({ name: name, email: email });
    navigate('/dashboard');
  };
Dashboard.js
const Dashboard = ({ user }) => {
  return (
    <section className="section">
      <h4>Hello, {user?.name}</h4>
    </section>
  )
}
export default Dashboard

Protected Route

Let's say we don't want to show the Dashboard page if user is not logged in, then we can protect that route like this

We can use <Navigate to=""/> to navigate to any page.

Two ways we can navigate to different pages

  • useNavigate() can be used outside jsx

  • <Naviage/> can be used inside jsx

App.js
<Route
  path="dashboard"
  element={
    <ProtectedRoute user={user}>
      <Dashboard user={user} />
    </ProtectedRoute>
  }
/>
ProtectedRoute.js
import { Navigate } from 'react-router-dom'

const ProtectedRoute = ({ children, user }) => {
  if (!user) {
    return <Navigate to="/" />
  }
  return children
}

export default ProtectedRoute

Shared Layout for Products

We were using this structure for products and :productIdNavigate (useNavigate)

<Route path="/" element={<SharedLayout />}>
    <Route index element={<Home />} />
    <Route path="about" element={<About />} />
    
    // products and products/:productId could be nested (we will see how to do this later)
    <Route path="products" element={<Products />} />
    <Route path="products/:productId" element={<SingleProduct />} />
    
    <Route path="login" element={<Login setUser={setUser} />} />
    <Route path="dashboard" element={<Dashboard user={user} />} />
    <Route path="*" element={<Error />} />
    </Route>
</Routes>

Both of these routes "products" and "products/:productId" can be turned into nested route so that we know it is a group.

We do this similar to what we did in SharedLayout above.

SharedProductLayout.js
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import SharedLayout from './pages/SharedLayout'
import About from './pages/About'
import Products from './pages/Products'
import SingleProduct from './pages/SingleProduct'
import Login from './pages/Login'
import Dashboard from './pages/Dashboard'
import Home from './pages/Home'
import { useState } from 'react'
import { ProtectedRoute } from './pages/ProtectedRoute'
import SharedProductLayout from './pages/SharedProductLayout'

function App() {
  const [user, setUser] = useState(null)
  console.log('The user now is ', user)
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<SharedLayout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />

          {/* SHARED PRODUCT LAYOUT */}

          <Route path="products" element={<SharedProductLayout />}>
            <Route index element={<Products />} />
            <Route path=":id" element={<SingleProduct />} />
          </Route>

          <Route path="login" element={<Login setUser={setUser} />} />
          <Route
            path="dashboard"
            element={
              <ProtectedRoute user={user}>
                <Dashboard user={user} />
              </ProtectedRoute>
            }
          />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

export default App

The parent SharedProductLayout contains only the placeholder for underlying child components and that placeholder is <Outlet/>

import { Outlet } from 'react-router-dom'

const SharedProductLayout = () => {
  return (
    <>
      <div className="section">
        <Outlet />
      </div>
    </>
  )
}
export default SharedProductLayout
SingleProduct.js
import { Link, useParams } from 'react-router-dom'
import products from '../data'

const SingleProduct = () => {
  const { id } = useParams()
  const { name, image } = products.find((product) => product.id === id)
  return (
    <section className="section product">
      <h2>{name}</h2>
      <img src={image} alt="product" />
      <br />
      <Link to="/products" className="btn">
        Back to products
      </Link>
    </section>
  )
}

export default SingleProduct

Explained Shared layout diagrammatically

Last updated