Routing is a fundamental part of every single-page-application. Within the React ecosystem react-router is the most popular package to implement client-side routing. With this library routing is simply done in a elegant and declarative way. But behind the scene there are a lot of advanced concepts going on to make it all work. In this article I will explore some of those and give an example implementation of a routing library for React.

I do have a little confession to make before we start: while the title says ‘from scratch’ I will be referencing two packages from NPM today because rewriting them will be at least an article on its own. But I still think this one fits in with ‘A Web Server From Scratch in TypeScript and Node’ and ‘A Workerpool From Scratch in TypeScript and Node’ (and I will actually only be using one).

Declarative routing in React

The challenge

A router library for a modern single-page-application has some major challenges to overcome. At first is has to hook into the browsers and hijack the routing. It also has to implement some advanced design patterns to create the declarative style we love of react-router. And we will have to make sure that we render client-side links with correct html (meaning still using <a> tags and render href ’s).

The history API

The history API in JavaScript allows developers to listen to events in and manipulate the router state. In this project I will use the history package from NPM that provides a cross-browser implementation of the HTML5 history API. It is the same as is used by react-router. In our library I will create one singleton instance that can be easily used from anywhere in an application.

The Router component

The Router component holds the state of the router. It has to listen to the events from the history for updating its state. Every time navigation happens the listen callback will be called and updates the state of the Router . Note that we don’t clean up our listener and live with the somewhat naive assumption that the Router component will always be rendered. For the sake of keeping it simple I will leave it as-is.

The Route component

Routes are declared with Route components that are inside a Router component. The Router will make sure to only render the routes of which the paths match with the current url. This is a bit different then react-router, because we don’t have a Switch component. This is because react-router uses the context of react to pass the state of the router to the switches so it can support nesting. We are not going to follow that road so we don’t have the need for a Switch component.

The Route component is a bit different from normal components. If you look to its implementation as shown bellow you will notice that it is doing exactly nothing at all, but still is implemented as a class. This is because the job of a Route component is to just simply be there as an indicator to the Router that is should be treated differently.

To make Route work we will have to update the Router component to look out for any routes in its children. Here we are going to loop through the children of Router and hide any routes with paths that don’t match the current url.

There are multiple checks that need to be executed here. The first one is to check if our children are an array. If so, we map over it and check if each child is a Route . For this we use a lesser known trick in react: if you want to check if an element is an instance of a certain component you can compare the type property of the element with the component class. If it is a Route we compare the path to the url in the state. Next to this we also have a check on null and undefined on the child to prevent crashes we we have a child that is not a ReactElement (like a string or null ).

The Link component

The Link component renders an <a> tag with a correct href , but does actually use onClick to push a new state to the history . We do need this because we don’t want a page reload when a user clicks a link but do still want links in our html for accessibility and SEO.

When we put it all together we can write an application with routing like show bellow. One important thing we don’t support yet is patterns in the paths for wildcards and other useful features. For implementing those I advise looking to path-to-regexp, which is also used by react-router.

Conclusion

In this article we have taken a look under hood of routing in React applications. I hope you learned something on how all those components work together to create the beautiful declarative router interfaces the React community has learned to love. The source code can be found on Github.