Reverse patterns to URL. React router Config.
Tags: react, router, javascript, webdev
Published: 2021-03-15 20:04:55

In this post, I would like to introduce my approach to "Named URLs" for [React Router](https://reactrouter.com/).

#Motivation
I used to use Django's URLs [`reverse`](https://docs.djangoproject.com/en/3.1/ref/urlresolvers/#reverse) function. This function is beneficial when you need to get an actual URL using a named URL pattern or callable view object.
I want to use a similar approach with [React Router](https://reactrouter.com/).

#Problems
The problems I am trying to solve are:
1. **Avoid hard-coded URLs**. In the most examples I found, URLs hard-coded in [`Link`](https://reactrouter.com/web/api/Link). I feel like this can lead us to broken links during refactoring, etc.
2. **Missing URL params**. It is difficult to identify Hard-coded links with missed params until you click on them.

#Solution
The project directories are structured [this way](https://dev.to/ihormaslov/yet-another-way-to-structure-react-project-28de). 
### The `Reverse` implementation.
```
// src/shared/utils/namedUrls.js

import { compile } from 'path-to-regexp';

export class RoutePath {
  reverse(params) {
    const reversed = compile(this.pattern);
    return reversed(params);
  }

  constructor(pattern, component) {
    if (!pattern || !component) throw new Error('`pattern` and `component` are required');
    this.pattern = pattern;
    this.component = component;
  }
}
```

This class helps us build a route that knows how to build a link using given parameters and patterns.

### The usage.
In the app directory.
```
// src/apps/account/routes.js

import SignInPage from 'apps/account/pages/SignIn';
import UserPage from 'apps/account/pages/User';
import { RoutePath } from 'shared/utils/namedUrls';

const basePath = '/account/';
export default {
  signIn: new RoutePath(`${basePath}sign-in/`, SignInPage),
  user: new RoutePath(`${basePath}user/:id/`, UserPage),
};
```

In the main `routes.js`.
```
// src/routes.js

import { prepareRoutes } from 'shared/utils/namedUrls';

import accountRoutes from './apps/account/routes';

export const routes = {
  accountRoutes,
};

export const preparedRoutes = prepareRoutes(routes);
```

In components
```
import React from 'react';
import { Link } from 'react-router-dom';
import { routes } from 'routes';

const SomeComponent = () => (
  <>
    <Link to={routes.accountRoutes.signIn.reverse()}>Sign In</Link>
    <Link to={routes.accountRoutes.user.reverse({ id: 1 })}>User</Link>
  </>
);

export default SomeComponent;
```

Rendered links are:
```
<a href="/account/sign-in/">Sign In</a>
<a href="/account/user/1/">User</a>
```

### Error handling
I didn't implement any additional error handling as I am satisfied with the `path-to-regexp` app's error handling. 
If you miss some URL pattern parameters, you will find errors like the below in the browser console.
```
index.ts:337 Uncaught TypeError: Expected "id" to be a string
    at index.ts:337
    at RoutePath.reverse (namedUrls.js:12)
    at App (App.js:62)
```

I suppose this will be enough to warn developers that they missed the URL pattern's parameters. And I feel like these errors should be helpful during e2e testing.

Feedback is welcome!