DEV Community

Rohan Bagchi
Rohan Bagchi

Posted on

How to write a VanillaJS Router

Photo by <a href="https://unsplash.com/@juanjodev02?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Juanjo Jaramillo</a> on <a href="https://unsplash.com/s/visual/27a45f8b-2ba4-44c9-aa58-53f76f508a28?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a><br>

The ask is like this:

  1. React to link clicks by showing route specific content on the page.
  2. When user clicks on browser back or forward, the correct route and its content should be loaded.
  3. If user refreshes, content for the current route should be loaded.

To start with, make your HTML page look like this:

<body>
    <div id="nav"></div>
    <div id="app"></div>

    <script src="src/index.js"></script>
</body>
Enter fullscreen mode Exit fullscreen mode

Now in our index.js, lets add the following config:

const routes = {
  '/': {
    linkLabel: 'Home',
    content: `I am in home page`
  },
  '/about': {
    linkLabel: 'About',
    content: `I am in about page`
  },
  '/friends': {
    linkLabel: 'Friends',
    content: `I am in friends page`,
  },
};
Enter fullscreen mode Exit fullscreen mode

At this point it is clear that we are trying to create a config powered router. This means, we will need to create the nav items and then register the click handlers.

Let's add functions for them.

const app = document.querySelector('#app');
const nav = document.querySelector('#nav');

// function to create new nav items
const renderNavlinks = () => {
  const navFragment = document.createDocumentFragment();
  Object.keys(routes).forEach(route => {
    const { linkLabel } = routes[route];

    const linkElement = document.createElement('a')
    linkElement.href = route;
    linkElement.textContent = linkLabel;
    linkElement.className = 'nav-link';
    navFragment.appendChild(linkElement);
  });

  nav.append(navFragment);
};

// function to register click handlers
const registerNavLinks = () => {
  nav.addEventListener('click', (e) => {
    e.preventDefault();
    const { href } = e.target;
    history.pushState({}, "", href);
    navigate(e); // pending implementation
  });
};
Enter fullscreen mode Exit fullscreen mode

We now need to implement the navigate function. It will handle the actual navigation.

const renderContent = route => app.innerHTML = routes[route].content;

const navigate = e => {
    const route = e.target.pathname;
    // this is responsible for adding the new path name to the history stack
    history.pushState({}, "", route);
    renderContent(route);
};
Enter fullscreen mode Exit fullscreen mode

Now, all we have left to do is to is to handle the pop state event (handle browser back and forth) and to handle the initial page load.

const registerBrowserBackAndForth = () => {
  window.onpopstate = function (e) {
    const route = location.pathname;
    renderContent(route);
  };
};

const renderInitialPage = () => {
  const route = location.pathname;
  renderContent(route);
};
Enter fullscreen mode Exit fullscreen mode

Bringing it all together:

(function bootup() {
  renderNavlinks();
  registerNavLinks();
  registerBrowserBackAndForth();
  renderInitialPage();
})();
Enter fullscreen mode Exit fullscreen mode

Final demo:
This is what we are attempting to create: (check codesandbox demo)

...

Thank you for reading this far.

Top comments (3)

Collapse
 
jimmyroot profile image
jimmyroot

Thanks for putting this together, it's really helped me out.

For anyone else testing this, there's a sliiight bug...in the example given we're pushing the route to history twice, in the event listener and in the navigate function...the effect is that you have to click 'back' twice to actually navigate back. Removing either one of the history.push() lines will fix this!

Collapse
 
richardeschloss profile image
Richard Schloss

This is awesome. Simple and elegant. Thanks for putting this together.

Collapse
 
arjitgeo profile image
Arjit Geo

I don't know because I'm using it in localhost. when I manually typed the route in the address bar, it didn't work.