Once you add redux simple router (or redux router) to your project you have two places in your state that need to be kept in sync.

import { combineReducers } from 'redux'
import { routeReducer } from 'redux-simple-router'
import tree from './tree'

const rootReducer = combineReducers({
  tree: tree,
  routing: routeReducer

export default rootReducer

Now every time you change tree you need to update the routing part. That’s easy because your action creators may push a new state into the browser history.

The other way around, -changing the URL and reduce correctly- is a bit more complex because the router dispatches only one action (UPDATE_LOCATION if using redux-simple-router) and based on this action you need to reduce your state correctly.

Matching -and sometimes parsing- the url in your reducers is something that feels wrong, it is a logic that doesn’t belong to the reducer.

Redux Sagas

Sagas in redux are a way to listen for redux actions and spawn side effects accordingly and because url changes and async requests can be treated as side effects I created a saga to handle both.

import { routeActions, UPDATE_LOCATION } from 'redux-simple-router'
import { take, put, fork, call } from 'redux-saga'
import { show, retrieve } from './actionCreators'

* Small tree structure in a plain hash that works as my backend server.
const fakeDB = {
  '/': [
    { id: '/1',
      title: '1'
    { id: '/2',
      title: '2'
  '/1': [
    { id: '/1/a',
      title: 'a'
    { id: '/1/b',
      title: 'b'
  '/1/a': [
    { id: '/1/a/I',
      title: 'I'

* This saga `take`s the RETRIEVE_NODE action, then executes in order:
*   1. Changing the URL with the redux simple router `routeActions.push` method
*   2. Request a list of nodes from the server
*   3. Once the nodes are back call `show` a custom action that will reduce the state with new nodes
export function* retrieveNode() {
  let action = null
  while(action = yield take('RETRIEVE_NODE')) {
    yield put(routeActions.push(action.payload.nodeId))
    let nodes = yield call(fetchNodes, action.payload.nodeId)
    yield put(show(nodes))

* This saga `take`s the UPDATE_LOCATION action, then checks if the update is a push or a pop. 
* A pop means hiting the back/forward button, in this case -and only in this case- I want to 
* retrieve new nodes, this will put a RETRIEVE_NODE andthat will be handled by the previous saga.
export function* urlChanged() {
  let action = null
  while(action = yield take(UPDATE_LOCATION)) {
    if(action.location.action === 'POP') {
      yield put(retrieve(action.location.pathname))

function fetchNodes (nodeId) {
  return new Promise(resolve =>
    setTimeout( () => resolve(fakeDB[nodeId]), 1000 )

export default function* root() {
  yield fork(retrieveNode)
  yield fork(urlChanged)

The first saga retriveNode watches for the RETRIEVE_NODE action that I dispatch on the connected components, once the action is fired it executes in order some side effects, one of them being the url change provided by the push function.

Now we need to tackle another problem, what happens when someone changes the url like as a result of clicking the back or forward button on the browser?

In that case, the routing part of our state changes but the tree part remains the same. The solution is in the last saga called urlChanged.

I listen to the UPDATE_LOCATION action and check if this action has been a POP, a POP action is our way to say: Did this action occur has a result of a back / forward navigation? This is because I don't want to react to url pushes made from the previous saga.

Once the url changed as a result of the back / forward button we send the action to retrieve the node, this will wake up the retriveNode saga and rebuild our state correctly.