Stop repeating yourself with Routes, Links and location parameter parsing in your React app

Routes require paths to app locations. Links that we sprinkle throughout our app code inevitably reference the same paths. When you hard-code those paths, you repeat yourself. If you subsequently decide to reorganize your Routes, you have to manually search through your code to find and fix the broken Links.

The solution is trivial: use path constants. But unfortunately the problem doesn’t stop there. We also duplicate ourselves in the logic that understands the parameters the paths support.

In the code sample below, the path to the Article location is /articles/:id, where id is the article identifier, is required, and of type integer. The code represents this fact in the Route on line 11. The code represents it again in the URL we are building on line 46. And it is represented a third time in lines 54–60, where we have logic that retrieves the id from match.params and casts it to integer.

The parameterized path on line 46 is easily fixed by importing generatePath from react-router-dom and using that to generate the parameterized path. But what if we need to generate a URL with a parameterized query string, as seen on line 64? The URL is manually built, and the query string parameter knowledge is duplicated in the parsing and validation logic in lines 18–39.

This code is wet! Let’s see if we can make it DRY.

Introducing react-app-location

react-app-location is a tiny package that treats app locations as first class citizens. Your Locations are defined in one place and then imported and used as needed throughout your code.

As you would expect, a Location is the single source of truth for the path itself. But it also provides the ability to fully specify the path and query string parameters that the Location supports. This knowledge enables the Location to directly handle all app location-related responsibilities, keeping your code clean and DRY.

Take another look at the code sample after being refactored to use react-app-location.

The code defines two Locations on lines 11 and 12. Let’s focus on ArticleLocation first.

On line 32, we are rendering a Link from ArticleLocation, and are providing a literal object for the parameters we want included in the URL. It is the Location’s responsibility to format the URL with the id value in the appropriate place. This is analogous to generatePath, but as you will see later, it works equally well with query string parameters. Nice!

Next, take a look at the Article component itself (lines 40–46). Notice that the component directly receives an id prop which has already been cast to integer. The boilerplate of validating and casting match.params has been deleted from the component code, and the responsibility has been shifted to the Location.

How does this work? Look at line 19. Here we are using the Location to render a Route. When the Location renders the Route, it also sets a special render prop. When the Route matches, the render prop takes care of parsing and validating the location params, and passing them as props to your component. It also takes care of rendering <NotFound /> if the params don’t satisfy the Location requirements. Very nice! More on this later.

Note: you are not required to use this capability. If you prefer your component to continue handling location params itself, you would simply define a Route like so:

<Route path={ArticleLocation.path} exact component={Article} />

Defining a Location

The Location constructor takes a path argument (required), as well as a path param schema and query string param schema (both optional). The schemas are defined using Yup (if you haven’t yet used Yup for form validation, you really should).

For ArticleLocation defined on line 12, we are specifying that there is a single path parameter named ‘id’. It is required, and must be an integer > 0.

For ArticleListLocation on line 11, we are specifying that there are four query string parameters. ‘authorID’ and ‘topicID’ are optional integers > 0. Valid values for ‘orderBy’ are ‘title’, ‘author’, ‘topic’, and ‘publishDate’. If not specified, it defaults to ‘publishDate’. Similarly, ‘order’ must be ‘asc’ or ‘desc’ defaulting to ‘desc’.

With this, the Location has the full metadata for each parameter: name, data type, whether or not it is required, default value (if any), and whether it corresponds to a path segment, or is part of the query string.

On line 44, we are generating a URL to the ArticleListLocation, with query string parameters. If the Article component were rendering an article with Author ID of 1, the URL would be generated as /articles?authorID=1.

Defining a Route with automatic location param parsing

Let’s take a closer look at how the Routes are generated. The Location.toRoute function takes a renderOptions argument, where you specify what the Route should render by setting either the component, render, or children property. These are analogous to the three render options that Route supports.

Location.toRoute doesn’t directly set these properties on the Route that gets rendered; rather, it essentially wraps them in a render prop. As described earlier, the render prop wrapper logic takes care of parsing and validating the parameters from the Location and merging them into the props that are ultimately passed on. Any params with default values that are missing from the URL are passed as well.

The renderOptions.invalid property specifies the component to render if the Route matches, but a required parameter is missing or of the wrong data type. This is analogous to rendering <NotFound /> in the case of a broken URL.

Finally, the exact, sensitive, and strict arguments are passed directly to the Route as props.

Conclusion

I enjoyed developing react-app-location, but I enjoy using it even more! Give it a try and let me know what you think. PRs are welcome.

Like what you read? Give Brad Stiff a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.