How We Used Connected React Router to Create Bookmarkable Pages

Angela Shi
Granular Engineering
4 min readOct 10, 2019

Granular Insights allows farmers to view their data from a variety of different angles in order to help them answer specific questions about their farm. For example, a user may want to know how much corn was harvested off a particular field last year. A common user experience throughout the product is to select from different dropdown options to create a customized view. Once a user has made their selections, they will often bookmark that page and share the link with other members of their organization.

In order for user selections to be bookmarkable or shareable, they needed to be incorporated as part of the page URL. We decided to use query parameters to store selections in the URL as this was the simplest and most flexible solution, allowing for different types of params depending on which page the user was on. A typical URL may look something like this:

/app?year=2018&crop=corn&metric=yield

This meant that we needed a way to 1) extract the relevant query params that our components need and 2) update the query params when a user selection changes.

Why Connected React Router?

Our application is built on React and Redux and utilizes React Router to handle routing. Connected React Router is a library that provides Redux bindings for React Router. It allows you to store the current router location in your Redux store and dispatch actions to change the location. Since we already store the majority of our application state in Redux, it made sense to also do the same for the router state. This meant that we could benefit from the general advantages of Redux, such as allowing for time travel debugging through Redux Dev Tools and being able to access state from anywhere in the component tree. In addition, since the query params are included in the router state, we automatically had a way to store and update user selections without having to create an additional part of the store for the dropdown state. The next sections dive deeper into how we accomplished this, following a user journey from when a bookmarked link is first loaded in the browser to when a dropdown selection is changed.

Accessing query params

Imagine that a user enters the app by clicking on a saved bookmark with the following URL: /app?year=2018&crop=corn&metric=yield. When the app first loads, our router store will be initialized with that URL . At this point, our router store will look something like this:

location: {
pathname: '/app'
search: '?year=2018&crop=corn&metric=yield'
hash: ''
key: 's3zpqn'
}
action: 'POP'

Note that the query string is stored in the search property of the location object. In order to access the query string, we wrote a general selector to extract the query string from the search property and parse it into an object. We used the query-string library to handle the parsing.

// selectors.jsexport const getQueryParams = (state) => (
queryString.parse(state.router.location.search)
);

In this example, getQueryParams would return

{
year: '2018',
crop: 'corn',
metric: 'yield'
}

We then wrote selectors to extract the value of specific params, using the general selector above. This was done using the Reselect library. For example, the following extracts the year value. Similar selectors were implemented for crop and metric.

// selectors.jsexport const getYearParam = createSelector(
[getQueryParams],
(params) => params.year;
);

A change in the router store will trigger these selectors to run and return the values of the year, crop, and metric query params. These values are then passed to our components as props via the React-Redux mapStateToProps function. The component, in turn, fetches and displays the correct data based on those values.

Updating query params

Now that the page is loaded, imagine that the user clicks on the Year dropdown and changes the selection to 2019. Connected React Router provides a push action that can be dispatched to update the location to a specified path. In our change handler for the Year dropdown, we created a new path using the new year value and existing crop and metric values and dispatched the push action with the new path.

// YearDropdown.tsximport { push } from 'connect-react-router';onYearChanged = (newYear) => {
const newPath =
`/app?year=${newYear}crop=${props.crop}metric=${props.metric}`;
dispatch(push(newPath));
}

The new user selection triggers onYearChanged, and now, the router store is updated to look something like this:

location: {
pathname: '/app'
search: '?year=2019&crop=corn&metric=yield'
hash: ''
key: 'iiozda'
}
action: 'PUSH'

Note that the value of the search property now reflects the 2019 year selection. This change to the router store will again trigger our selectors to run and return new values. Our components detect the change in props, causing them to fetch and display new data.

Summary

Connected React Router has allowed for an easy way to access query parameters throughout our app. By accessing and updating the router state through selectors and actions, similar to any other part of the store, we have been able to leverage the benefits of Redux to write code that is more consistent, maintainable, and debuggable.

--

--