4 min read

Building a Modal Route with React Router

react
react-router
modal
outlet

I don't know about you, but I love modals. Specifically, when they're used to display content without navigating away from the current page.

And today, I'll show you how to create a modal route in React with the help of the react-router-dom library.

if some of you are not understanding what we are going to build, then you can see the stackblitz live demo example.

Use Case ๐Ÿง

  • Suppose you have a list of items and you want to show the details of the item in a modal and without navigating to a new page.
  • You want to show a form in a modal and submit the form without navigating to a new page.

Imagine how cool it would be to navigating to a new link without leaving the current page.

Okay enough talking, let's start.

Getting Started ๐Ÿ“Œ

I'm assuming you have a basic understanding of React and React Router. If not, I recommend checking out the official documentation. and also you have a basic react project setup.

Step 1 โ€” Setup React Router

First, let's set up the React Router in our project. We'll be use the createBrowserRouter for define our routes.

App.js
// App.js
import * as React from 'react'
import { Dialog } from '@reach/dialog'
import {
  createBrowserRouter,
  Link,
  Outlet,
  RouterProvider,
  useNavigate,
  useParams,
} from 'react-router-dom'
 
import '@reach/dialog/styles.css'
 
import { getImageById, IMAGES } from './images'
 
const router = createBrowserRouter([
  {
    path: '/',
    Component: Layout,
    children: [
      {
        path: '/',
        Component: Home,
      },
      {
        path: 'gallery',
        Component: Gallery,
        children: [
          {
            path: 'img/:id',
            Component: ImageView,
          },
        ],
      },
    ],
  },
])
 
export default function App() {
  return <RouterProvider router={router} />
}

Step 2 โ€” Create the Layout

The Layout component acts as the main structure of our application. It includes a navigation menu and an Outlet to render child routes.

Layout.js
// Layout.js
export function Layout() {
  return (
    <div>
      <h1>Outlet Modal Example</h1>
      <p>
        This is a modal example using `createBrowserRouter` that drives modal displays
        through URL segments. The modal is a child route of its parent and renders in the
        `Outlet`.
      </p>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/gallery">Gallery</Link>
            </li>
          </ul>
        </nav>
        <hr />
      </div>
      <Outlet />
    </div>
  )
}

Now we have to create the Home Page.

Step 3 โ€” Defining the Home Page

The Home page is the default route of our application. It contains a link to the Gallery route. It's will be just a basic page with a link to the Gallery route.

Home.js
// Home.js
export function Home() {
  return (
    <div>
      <h2>Home</h2>
      <p>
        Click over to the <Link to="/gallery">Gallery</Link> route to see the modal in
        action.
      </p>
      <Outlet />
    </div>
  )
}

The Gallery component displays a grid of images. Each image is a link that opens a modal by changing the URL to a child route.

Gallery.js
// Gallery.js
export function Gallery() {
  return (
    <div style={{ padding: '0 24px' }}>
      <h2>Gallery</h2>
      <p>
        Click on an image to open a modal. You'll notice that the URL changes, and you
        still see this route behind the modal. The modal is a child route of{' '}
        <pre style={{ display: 'inline' }}>"/gallery"</pre>.
      </p>
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
          gap: '24px',
        }}
      >
        {IMAGES.map((image) => (
          <Link key={image.id} to={`img/${image.id}`}>
            <img
              width={200}
              height={200}
              style={{
                width: '100%',
                aspectRatio: '1 / 1',
                height: 'auto',
                borderRadius: '8px',
              }}
              src={image.src}
              alt={image.title}
            />
          </Link>
        ))}
        <Outlet />
      </div>
    </div>
  )
}

And finally, we have to create the modal component to display the image.

Step 5 โ€” The Modal Component

The ImageView component represents the modal itself. And for modal we gonna use the @reach/dialog package.

npm

bash
npm install @reach/dialog

yarn

bash
yarn add @reach/dialog

And here is the code for the ImageView component.

ImageView.js
// ImageView.js
export function ImageView() {
  let navigate = useNavigate()
  let { id } = useParams<'id'>()
  let image = getImageById(Number(id))
  let buttonRef = React.useRef<HTMLButtonElement>(null)
 
  function onDismiss() {
    navigate(-1)
  }
 
  if (!image) {
    throw new Error(`No image found with id: ${id}`)
  }
 
  return (
    <Dialog aria-labelledby="label" onDismiss={onDismiss} initialFocusRef={buttonRef}>
      <div
        style={{
          display: 'grid',
          justifyContent: 'center',
          padding: '8px 8px',
        }}
      >
        <h1 id="label" style={{ margin: 0 }}>
          {image.title}
        </h1>
        <img
          style={{
            margin: '16px 0',
            borderRadius: '8px',
            width: '100%',
            height: 'auto',
          }}
          width={400}
          height={400}
          src={image.src}
          alt=""
        />
        <button style={{ display: 'block' }} ref={buttonRef} onClick={onDismiss}>
          Close
        </button>
      </div>
    </Dialog>
  )
}

Woohoo! ๐Ÿฅณ You have successfully implemented the modal using @reach/dialog and react-router-dom.

Trust me, it's a great feeling to create something like this. ๐Ÿš€

Complete Code

If you need the complete code, you can find it here and find the live demo stackblitz here.

Thanks for reading! ๐Ÿ™Œ