Managing the URL in a Redux app

Posted 3 years ago by Mark Allison

Let’s say you’ve built an awesome app in React using Redux for state management. You decide you want to add URLs to it so you can deep link to different app states (for sharing, bookmarking, etc.). You could think of the URL as just another component — it renders something based on the current app state. With that in mind, is it easy to add URLs without significant changes to the code and in a way the fits the redux pattern? The answer is ‘yes’, using react-router-redux. However, this required some investigation to figure out so we’re sharing our solution here in case it’s helpful.

react-router-redux

With react-router-redux the location state is kept in your redux store as plain object. This means you can inject location information with connect as you would any other state. The LOCATION_CHANGE action is dispatched when the URL changes so you can listen for this in your reducers.

The location state can also be written to. This means you can update the URL in response to your own app’s actions as you would any other state! Note, doing this is not quite the official API. react-router-redux creates and enhanced history object that triggers LOCATION_CHANGE when you, for example, call history.push(...). However we preferred doing this directly in our reducers: this keeps the action creators ‘clean’ as you don’t need to convert them to, for example, thunks so you can call history.push as well as return the usual action object.

I think this is a more ‘pure’ redux solution. As Dan Abramov noted in a Stack Overflow answer: “The “default” way of doing several updates in response to something: handle it from different reducers. Most often this is what you want to do.

You can dispatch multiple actions and you can use, say, thunks to make your action creators update the URL but the cleanest, most redux way, is to update state in your reducers. That way, the the rest of the code is untouched.

A note on the naming of react-router-redux

Dan Abramov noted this library might be better called redux-history as it does not require React Router. It simply allows you to keep the location state in your redux store. Additionally it provides utilities for integrating with React Router.

The app we wanted to add URLs to didn’t have the kind of nested UI that React Router is designed for. The unfortunate naming of react-router-redux meant it took a little while to realise that this was the library we wanted and that we didn’t have to use React Router.

Let’s look at an example app to see how it works.

Maths puzzle

We’ll use this little maths puzzle.

Here’s the app code before we thought to add URLs (some might argue you should do this up-front. However, I mostly find it doesn’t work this way. In any case, adding URLs should not require significant changes).

components/Puzzle.js

constants.js

actionCreators.js

reducers/operation.js

app.js

Represent puzzle state in the URL

The puzzle works fine but what if you want to post your attempt on math.stackexchange.com to get help? We need to be able to link to any state of the app. For this simple puzzle all we need is the operation:

Let’s see what needs to change

app.js

reducers/operation.js

reducers/routing.js

Note that we have to copy and modify the routerReducer provided with react-router-redux as we’re not using the intended enhanced history API.
With these small modifications each game state now has its own URL. You can bookmark, share, and use the browser navigation buttons.

Conclusion

We added URLs for our game state with minimal changes to the code: location handling is all within the reducers. We didn’t have to start dispatching pairs of actions (one to update app state and one to update the URL) or use the thunk middleware so our action creators could call history methods as well as dispatch the required app action.

As noted above we think this is a more ‘pure’ redux solution. However, we had to copy and modify the routerReducer so it’s a hack. Though, with very few changes, react-router-redux could support this usage.

Note on usage with React Router

With React Router and react-router-redux, the location state is stored in the store but you cannot rely on it — you still have to use the params passed to your component from React Router. Only components wrapped in a Route get these and you can’t inject location state to other components using connect as you would normally. An alternative implementation that aims to fix this is redux-router.

We’re always looking for talented React developers at Marvel. Check out our roles here.

Follow us on Twitter and Facebook!

Stay connected – be the first one to get interesting updates from Marvel and the design world.

Front end developer at Marvel. React devotee. Python traitor.

  • Barry Low

    Awesome! Thank you so much for this. Clarified a huge amount of react/redux/routing for me in one simple but very detailed post.

  • Martin Stadler

    Are you aware that react-router-redux discourages reading location state from the store?

    From the README:
    “You should not read the location state directly from the Redux store. This is because React Router operates asynchronously (to handle things such as dynamically-loaded components) and your component tree may not yet be updated in sync with your Redux state. You should rely on the props passed by React Router, as they are only updated after it has processed all asynchronous code.”

    This is a good read: http://formidable.com/blog/2016/07/19/let-the-url-do-the-talking-part-2-bargaining-and-acceptance-with-redux-and-react-router/

    • Mark Allison

      Hi Martin, thanks for reading. We’re not using React Router. The problem lies in the confusingly named react-router-redux. This library provides syncing between browser history and your redux store which can then be used with React Router. We only use it for the history/store syncing (there was discussion of splitting out that part to its own tiny library). In this case it’s find to read state from the store. We didn’t need React Rotuer. In fact, being restricted to getting routing state from props defeats the purpose, in our opinion, of using redux. We are able to access routing state in connect/mapStateToProps as normal.

      • Martin Stadler

        Thank you for clarifying. I was actually not aware the quoted restriction does not apply without react-router but you’re stating that clearly. Thanks for your article. Let’s hope the mess will be cleaned up a bit over time 🙂

  • stevematdavies

    Such an overly convoluted example, for what essentially should be a simple implementation, still don’t grasp the context here.

  • Bogdan Negru

    I am sorry but this example does not work. It does not have the necessary context. You don’t know what versions to use for react-router-redux or for history. It seems that the tutorial is made for an old version of those and is doesn’t work anymore.
    Also, the example before the changes made for routing is not working.
    If you are going to write a tutorial you should write it to help someone.

    I just needed a clean example of how to use react-router-redux in a simple application to move it then to my application.

  • Ben Carp

    Reading the documentation of react-router-redux, it seems the authors mainly consider it as a compensatory library to react-router.

    • Ben Carp

      This is very interesting.
      Are you still using and recommending this method?
      The one part I don’t understand is the routing reducer. It seems `locationBeforeTransitions` is the property that is in sync with the url. Why is it called `locationBeforeTransitions` instead of just `location`? and in:
      “` location = { …location, pathname, action: ‘PUSH’ }; “`
      Why do we need to spread the location object? It seems
      “` return { locationBeforeTransitions: {pathname, action: ‘PUSH’} }; “`
      Would be enough and not cause any mutations.
      Can you please upload the src code to Github?