Search code examples
reactjsreduxreact-reduxreact-router-reduxreact-router-dom

react-router-redux 5: Maintaining store state history across navigation


I'm currently developing a react redux application. When i navigate with previous and next buttons in the browser the url is changing and routes gets correctly rendered. However, the redux store keeps the latest state and doesn't time travel across navigation, the only state that gets changed is the router location state.

Here is my redux store:

import {applyMiddleware, combineReducers, createStore} from "redux";
import reducers from "../reducers";
import history from '../history'
import {routerMiddleware, routerReducer} from "react-router-redux";

const middleware = routerMiddleware(history)

const store = createStore(
  combineReducers({
    ...reducers,
    router: routerReducer
  }),
  applyMiddleware(middleware)
)

export default store

The index file of the application is:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import 'semantic-ui-css/semantic.min.css';
import {registerObserver} from "react-perf-devtool";
import {Provider} from "react-redux";
import store from "./store";
import {ConnectedRouter} from "react-router-redux";
import history from './history'

if (module.hot) {
  module.hot.accept()
}

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <App />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);
registerObserver();
registerServiceWorker()

The history is as follows:

import createHistory from 'history/createBrowserHistory'

const history = createHistory()

export default history

Finally, here are the reducers:

import {SET_USER_LOCATION, SET_NEARBY_SHOPS} from "../constants/action-types";

const initialState = {
  userLocation: {
    latitude: 0.,
    longitude: 0.
  },
  nearbyShops: {
    shops: [],
    radiusOfSearch: 0.
  }
}

const userLocation = (state = initialState.userLocation, action) => {
  switch (action.type) {
    case SET_USER_LOCATION:
      return { latitude: action.payload.latitude, longitude: action.payload.longitude }
    default:
      return state
  }
}

const nearbyShops = (state = initialState.nearbyShops, action) => {
  switch (action.type) {
    case SET_NEARBY_SHOPS:
      return { shops: [...action.payload.shops], radiusOfSearch: action.payload.radiusOfSearch }
    default:
      return state
  }
}

const reducers = { userLocation, nearbyShops }

export default reducers

I navigate to another route using push:

const mapDispatchToProps = dispatch => bindActionCreators({
  setNearbyShops: nearbyShops => setNearbyShops(nearbyShops),
  goToNearbyShops: (latitude, longitude, radius) => push(`/nearby/@${latitude},${longitude},${radius}`)
}, dispatch)

What i want is that userLocation and nearbyShops state gets synced when i navigate back and forth through history so the components gets renderd with the correct state and not with the latest one.

Here is a gif to explain further the issue i'm trying to resolve:

The history location is updated when pressing back but not the data state


Solution

  • So you want to make your UI directly dependent on the history state. Unfortunately, this is rather fiddly with the existing redux wrappers for react-router.

    So when the history changed, your App component will receive a prop update. You'll have to forward these new props in some way to the components that should react to them. You can e.g. dispatch the new information to the store in the method componentWillReceiveProps of you App component.

    Or you can forward the prop through multiple component layers to where they are needed, but I'd advice against this.

    Alternatively, try https://github.com/mksarge/redux-first-routing instead, which makes imho much more sense. It makes routing independent of components. See also this article: https://medium.freecodecamp.org/an-introduction-to-the-redux-first-routing-model-98926ebf53cb

    One way or the other, userLocation and nearbyShops should not be reducers, but selectors, as their information can and should be derived from the geo location in your history.