https://unsplash.com/photos/1SAnrIxw5OY

Implementing the ideas behind React router

Stefan Kupresak
Nerd For Tech
Published in
5 min readFeb 23, 2021

--

I was building my Next.js web-site. Then I stumbled upon this simple feature that took me way longer to implement than I expected because of overlooking one of the component lifecycle fundamentals. I wanted to create a fade transition between routes on the page, but that has led me deep into the rabbit hole at how it all works. Pondering google a bit, I haven’t found a single in-depth explanation of how this all works, so I decided to follow up with my own.

Let’s get started by exploring what we’re building. To keep this exercise’s scope small, I decided to use create-react-app with tailwindcss and our trusty friend Code Sandbox.

The small toy application we’re building

Now that we have it figured out, let’s get started making this step by step and explaining the general ideas.

If you would like to follow along, all you have to do is create a new CodeSandbox project with the React template, and add Tailwinds via CDN.

(Just go to the External resources section on the left, and add this URL https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css)

Note, this is not recommended outside of prototype projects

Making the markup

Let’s make some markup! We’re going to need a left-side navigation bar and some placeholder content to render. We’re going to achieve this by creating two additional components to display as the pages.

Initial markup

Notice that for now we just hard-coded the <Home /> component.

Next, we want to think of a way to “switch” the component to the correct one, and this means that we’re going to need a piece of state to keep track of the current URL the user is on right now and a way to update that state when the user clicks on one of the links.

Given our current markup, the only logical place to put such a state would be inside the App component, but we want to take a step further! We want the state related to the route to be shared with the rest of the VDOM.

We don’t know where the user will put his navigation or where he will change it so, and we need it decentralized like that. Luckily, there’s a React feature for this kind of state sharing, and it’s the Context API.

The end result will look something like this:

Example of the context

The Context

To define the context, we need a Provider and a Consumer and, of course, the object in question that the context will carry over.

Router context

We also provided the default values for this context, mainly the route and indicating that the pushwill be a function.

Now, we need a component that will provide the state to the context, which will be responsible for actually handling the “routing” logic. Let’s make a component and initialize it with the proper values it needs.

Logic for route handling

It’s important to note that this will have to be wrapped around our application and will provide the routing state to the rest of the application. Finally, we need to create a component that will inject a component based on which route is active.

The most interesting bit here is that we also need to leverage useEffect hook in order to make the browser update the URL after we change the route.

Dynamically showing the child component

Simply put, we pass it an array of “routes,” and using the || operator, we figure out which component we need to render dynamically.

Let’s Recap

Here’s how we’re standing so far:

A brief look at the code so far

So far, we’ve created all the necessary structure for a functional router, now we just have to tie all the pieces together.

It’s worth noting that at this point, we have a partially working example. If you go to the browser dev tools and manually toggle the setState hook’s value, you can see that the page renders the appropriate component.

Housekeeping

Before we continue, we need to do some housekeeping to keep our codebase tidy.
Firstly, notice how the useContext function leaks internal implementation details by exposing the context. We don’t need to do that, and we should wrap it in our custom hook! That way, we will have a clean API to interact with from the outside world. Additionally, we should be moving all router-related functionality outside of our App.js module and into a separate one.

Next, let’s move all the functionality we have into a separate router/index.js module to keep our code clean.

after some housekeeping router/index.js

Clicking the routes

Let’s test this all out! In order to click the routes, we need to setup event listeners on the actual links, and make them actually navigate. We can do this by utilizing the useRouter hook we’ve written and a simple onClick event listener.

A working route

Keep in mind that we must declare useRouter somewhere inside the context or it won’t work. Because internally it relies on the context. This is exactly why we moved the buttons inside the <Navigation> component.

And that’s it! We have a fully functional small application with working routes.

Extras

So far, I consider the API done. However, We can improve some bits, and since they’re only quality of life improvements, I’ve decided to put it here in the extras, so feel free to read on to the conclusion.

One of the things provided by React router is a more excellent API for navigating, and our implementation only has a low-level API. To make a cleaner user experience, we should create our own <Link> component, which should automatically handle the clicking logic, and we should only provide a href and a child that’s either <a> or a <button>.

<Link href='/some/path/1'>
<a>My Link</a>
</Link>

Another thing we should provide is a way to style active links. The simplest way we can achieve this is by providing a prop for an active class.

Link component

The most unique code here is React.cloneElement and we use it here because we want to dynamically read and modify the children of the Link component and apply an event listener to them. The code is almost identical to our click listener, except now it’s encapsulated in the <Link> component.

And finally, we should handle the not found case for our <RouterView>

In Conclusion

Thank you so much for making it this far! It was an incredible experience, and hopefully, you learned something along the way as I did!

Disclaimer: I think React router is a great library, and you should always use it! The thing is, most of the blogs focus on exploring the existing APIs and explaining them, and I’d like to explore how they work internally, without actually using them.

--

--

Stefan Kupresak
Nerd For Tech

Hello. I’m a full-stack developer specializing in Elixir/Phoenix and React, and I love going into lots of details in any problem.