Modal Routing in NextJS
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?
-
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.
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.
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.
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.
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 useuseRouter
to get the countryName query from the URL and display it to the user.
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.
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.
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 thefetchCountry
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
.
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.
Modal
-
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 inindex.js
with the one below.
<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 acountryName
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 tohttps://localhost:3000/?countryName=Ethiopia
but in the URL bar, it will displayhttps://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:
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.
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.