user5505266
user5505266

Reputation: 603

React Router Redux going back causing an infinite loop

I have an app built with React + Redux. I am using React Router with React Router Redux for routing. I have noticed very odd behavior. When you click the back button once, from 0 to -1 it works. But when you click it a second time, intending to go from -1 to -2, you instead go back to 0. Subsequent clicks loop between 0, and -1. This behavior is consistent with, rather than popping from history pushing to the last location. Is this the expected behavior of these libraries? If not am I using them incorrectly?

Here are what I believe to be the relevant pieces of code. The files are not complete (much too big) but this is what I think is essential, willing to share more if needed.

root.tsx:

import { Router, browserHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'

const history = syncHistoryWithStore(browserHistory, store)

const renderAsyncConnect = props =>
  <ReduxAsyncConnect
    {...props}
    helpers={{ client }}
    filter={notDeferredItems}
  />

const Root = () =>
  <Provider store={store} key="provider">
    <Router render={renderAsyncConnect} history={history}>
      {routes}
    </Router>
  </Provider>

export default Root

I also have a middleware listening for push actions. I know I am advised to use history.listen, but this middleware is robust against race conditions, and is only there for compatibility for the time being as we transition our system.

Here is the net effect of the middleware that is after the routing middleware:

const browseStateMiddleware = ({ dispatch, getState }) => next => action => {
  if (action.type === '@@router/LOCATION_CHANGE') {
    // some dispatching of actions deleted here for clarity
    return next(action)
  }
}

create-store.js:

import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension/logOnly'
import { createStore as _createStore, applyMiddleware } from 'redux'
import { routerMiddleware } from 'react-router-redux'

import clientMiddleware from './middleware/client-middleware'
import browseStateMiddleware from './middleware/browse-state-middleware'

import combinedReducers from './modules/reducer'
import types from './modules/account/types'

export default function createStore(history, client, data, persister) {
  const storeData = data

  // Sync dispatched route actions to the history
  const reduxRouterMiddleware = routerMiddleware(history)

  const middleware = [
    browseStateMiddleware,
    reduxRouterMiddleware,
    clientMiddleware(client),
    analyticsMiddleware,
    thunk,
  ]

  const composeEnhancers = composeWithDevTools(
    {
      // options like actionSanitizer, stateSanitizer
    }
  )

  // See https://stackoverflow.com/questions/35622588/how-to-reset-the-state-of-a-redux-store/35641992#35641992
  const rootReducer = (state, action) => {
    if (action.type === types.LOGOUT) {
      if (persister) {
        persister.clear()
      }
      return combinedReducers(undefined, action)
    }

    return combinedReducers(state, action)
  }

  const store = _createStore(
    rootReducer,
    storeData,
    composeEnhancers(
      applyMiddleware(...middleware)
      // other store enhancers
    )
  )

  window.store = store

  if (__DEVELOPMENT__ && module.hot) {
    module.hot.accept('./modules/reducer', () => {
      store.replaceReducer(System.import('./modules/reducer'))
    })
  }

  return store
}

Is anything wrong here?

Upvotes: 3

Views: 1623

Answers (1)

Erez Cohen
Erez Cohen

Reputation: 1527

This issue is most likely caused by this:

const browseStateMiddleware = ({ dispatch, getState }) => next => action => {
  if (action.type === '@@router/LOCATION_CHANGE') {
    // some dispatching of actions deleted here for clarity
    return next(action)
  }
}

The actions you dispatch take place before the routing is updated in the store which causes this behavior originating from react-router-redux (I have seen it reproduced with version 4.0.8).

One way to handle it could be to do the following:

const browseStateMiddleware = ({ dispatch, getState }) => next => action => {
  if (action.type === '@@router/LOCATION_CHANGE') {
    next(action);
    // dispatch your actions here
    return;
  }
}

This way you are guaranteed to have the store updated correctly at the right time.

Upvotes: 3

Related Questions