Modal Routing in NextJS

Abenezer Belachew

Abenezer Belachew · December 07, 2021

7 min read

  • Have you ever observed how on platforms like Reddit or Instagram, clicking on a post doesn't lead you to a new page but rather opens a modal containing the post's contents?
Kenenisa Bekele's Instagram
  • By implementing this feature, users of the web app can swiftly exit the modal and return to their exact previous location. However, sharing the link takes the user directly to the detailed page for that specific post.

  • In the preview demonstrated above, clicking on a post on the Instagram page displays related content in a modal. If I copy the link and open it in a new tab, or if I refresh the page, I am presented with a detailed view of the post, but without the modal.

Kenenisa Bekele Post Modal

Try it yourself

  • You would probably need to sign up or log in to try this.
  • Visit Kenenisa's Instagram Page, for example, and click on a post. This action triggers a modal that reveals detailed information about the chosen post. Now, copy the post's URL and open it in a new tab or simply refresh the page.
  • Rather than the initial detailed version of the post within a modal, you will now encounter a detailed page dedicated to the post, displayed without a modal.
Kenenisa Bekele Detail Page

This article will replicate this modal routing feature using NextJS.

What we are making

We are going to make a simple app that displays details about a country using the Rest Countries API.

Create the project

$ npx create-next-app countries
$ cd countries
$ yarn dev

Head to the index.js page and remove the default code and add the following.

index.js
import Link from 'next/link';

const countries = ["Ethiopia", "Eritrea", "Canada", "China", "Russia", "India", "Somalia"]

export default function index() {
    return (
        <div>
            <ul>
                {countries.map(countryName => (
                <li key={countryName}>
                    {countryName}
                </li>

                ))}
            </ul>
        </div>
    )

}


  • The above code maps every element in the countries array into a <li> tag.
Index Page

Dynamic Routes

  • We need to render a detailed page for each country when they are clicked and what better way to do this other than dynamic routes.
  • In the pages folder, create a folder called country and in it, create a file called [countryName].js For now, it will use useRouter to get the countryName query from the URL and display it to the user.
pages/country/[countryName].js

import { useRouter } from "next/router";

export default function CountryName() {
    const router = useRouter()
    const { countryName } = router.query;

    return (
        <div>
            { countryName }
        </div>
    )
}
  • Let's then go back to the index page and change what's inside the <li> tag.
index.js
import Link from 'next/link';

const countries = ["Ethiopia", "Eritrea", "Canada", "China", "Russia", "India", "Somalia"]

export default function index() {
    return (
        <div>
            <ul>
                {countries.map(countryName => (
                    <li key={countryName}>
                        <Link
                        href={`/country/${countryName}`}
                        <a>
                            {countryName}
                        </a>
                        </Link>
                    </li>

                ))}
            </ul>
        </div>
    )

}



  • When you now click one of the countries rendered, you'll see that you're taken to a new page that has the name of the country only.

  • This page is rendered from the pages/country/[countryName].js we recently created.

  • Now, let's add a bit more code to display other information about the country other than its name using the API.

Country Detail

  • Next, create a folder called components in the root folder to store different components.
  • In that folder, create a file called countryDetail.js and the following code to it.
components/countryDetail.js
import useSWR from 'swr';


const fetchCountry = (countryName) => {
    const url = `https://restcountries.com/v3.1/name/${countryName}`;
    return fetch(url).then(res => res.json());
}


export default function countryDetail({ countryName }) {
    const { data, error } = useSWR(countryName, fetchCountry);

    if (error) return <div>Failed to load</div>;
    if (!data) return <div>Loading...</div>;

    return (
        <div>
            {JSON.stringify(data, null, 2)}
        </div>
    )
}
  • In the component above, I am passing in the countryName as a prop and using SWR to fetch the data for that specific country from the API using the fetchCountry function.

  • SWR is a React Hooks library for data fetching that has a lot of features embedded in it.

You can add it using yarn:

$ yarn add swr
  • Let's use this component in country/[countryName].js.
country/[countryName].js
import { useRouter } from "next/router";
import CountryDetail from "../../components/countryDetail";

export default function CountryName() {
    const router = useRouter()
    const { countryName } = router.query;

    return (
            <CountryDetail countryName={countryName} />
    )
}

  • What we have done so far is the detail page. Now let's create the modal.
Index Page
  • We need to find a way to change the path in the URL without actually going to that page. Lucky for us, Next's Link component accepts an optional argument called as.

  • Change the previous <Link> tag in index.js with the one below.

index.js
<Link
  href={`/?countryName=${countryName}`}
  as={`/country/${countryName}`}>
  <a>
    {countryName}
  </a>
</Link>

  • href is the path or URL to navigate to. In this case, we're sending a query with a countryName key and a value of the {countryName} we got when we were mapping the countries.

  • as is the path that will be shown in the browser URL bar.

  • So, if a user clicks on Ethiopia, it will route to https://localhost:3000/?countryName=Ethiopia but in the URL bar, it will display https://localhost:3000/country/Ethiopia

Creating the modal using react-modal

  • We can use react-modal to create and display the modal.
  • Install it first:
$ yarn add react-modal
  • Go to the index page and add the following:
index.js
import { useRouter } from 'next/router';
import Link from 'next/link';
import Modal from 'react-modal';
import CountryDetail from "../components/countryDetail";



Modal.setAppElement('#__next');

const countries = ["Ethiopia", "Eritrea", "Canada", "China", "Russia"]

export default function Instastyleroute() {

  const router = useRouter();


  return (
    <div>
      <ul>
        {countries.map(countryName => (
          <li key={countryName}>
            <Link
              href={`/?countryName=${countryName}`}
              as={`/country/${countryName}`}>
              <a>
                {countryName}
              </a>
            </Link>
          </li>

        ))}
      </ul>


      <Modal
        isOpen={!!router.query.countryName}
        onRequestClose={() => router.push("/")}
      >
        <div>In the modal</div>
        <CountryDetail countryName={router.query.countryName} />
      </Modal>
    </div>
  )
}
  • isOpen={!!router.query.countryName} keeps the modal open as long as there is a countryName query in the URL. Remember that, when we click a country, the app routes this to this path: /?countryName=${countryName}.
  • onRequestClose={() => router.push("/") sends the user back to the / page when the user requests to close the modal by either pressing the escape key or clicking outside the modal.
Index Page

Conclusion

  • When you now click on a country from the list, a modal is open with the information for that country. I've left it to be just the JSON output because this is just for demonstration purposes but you can definitely style it the way you like.
  • If you refresh the page, the modal won't be there but the detail page for that country will instead be rendered.
  • This modal routing was made easily possible because Next's Link component accepts as as an optional argument to show what to output to the URL.
  • If you would like to see a video version of this article, head to Leigh Halliday's youtube channel. It's filled with very cool javascript tutorials.