Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically load redux reducers with react router 4

I'm splitting my code based on components and I want to inject my reducers only when a component loads, rather than stacking them all up from the start in the store.

In react router 3 it was pretty straight forward but I can't seem to get it to work with react router 4.

Here's the reducers and the store:

reducers.js

import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'

import modalReducer from '../modules/modal'

export default combineReducers({
  routing : routerReducer,
  modal   : modalReducer
})

store.js

import { createStore, applyMiddleware, compose } from 'redux'
import { routerMiddleware } from 'react-router-redux'
import thunk from 'redux-thunk'
import createHistory from 'history/createBrowserHistory'
import rootReducer from './reducers'

export const history = createHistory()

const initialState = {}
const enhancers = []
const middleware = [
  thunk,
  routerMiddleware(history)
]

if (process.env.NODE_ENV === 'development') {
  const devToolsExtension = window.devToolsExtension

  if (typeof devToolsExtension === 'function') {
    enhancers.push(devToolsExtension())
  }
}

const composedEnhancers = compose(
  applyMiddleware(...middleware),
  ...enhancers
)

const store = createStore(
  rootReducer(),
  initialState,
  composedEnhancers
)
export default store

And I'm using lazy load for the routes.

How do I implement split reducers?

I would like to inject the async reducers something like so:

function createReducer(asyncReducers) {
  return combineReducers({
    ...asyncReducers,
    system,
    router,
  })
}

function injectReducer(store, { key, reducer }) {
  if (Reflect.has(store.asyncReducers, key)) return

  store.asyncReducers[key] = reducer
  store.replaceReducer(createReducer(store.asyncReducers))
}
like image 351
S. Schenk Avatar asked Apr 23 '18 19:04

S. Schenk


People also ask

Why does react router not work with React Redux?

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.

How do I inject reducers in react-router V4?

In react-router v4, for async injection of reducers, do the following: In your reducer.js file add a function called createReducer that takes in the injectedReducers as arg and returns the combined reducer:

How to inject reducers in a react container async?

In your reducer.js file add a function called createReducer that takes in the injectedReducers as arg and returns the combined reducer: Now, in order to inject reducer in an async manner when your react container mounts, you need to use the injectReducer.js function in your container and then compose all the reducers along with connect.

Why can’t I access the router state in Redux?

The key problem is the inability of the Redux store to access the router state. Even if you somehow externally access the router from a reducer or an action, this violates the condition of a pure function by introducing a side effect.


1 Answers

In react-router v4, for async injection of reducers, do the following:

In your reducer.js file add a function called createReducer that takes in the injectedReducers as arg and returns the combined reducer:

/**
 * Creates the main reducer with the dynamically injected ones
 */
export default function createReducer(injectedReducers) {
  return combineReducers({
    route: routeReducer,
    modal: modalReducer,
    ...injectedReducers,
  });
} 

Then, in your store.js file,

import createReducer from './reducers.js';

const store = createStore(
  createReducer(),
  initialState,
  composedEnhancers
);
store.injectedReducers = {}; // Reducer registry

Now, in order to inject reducer in an async manner when your react container mounts, you need to use the injectReducer.js function in your container and then compose all the reducers along with connect. Example component Todo.js:

// example component 
import { connect } from 'react-redux';
import { compose } from 'redux';
import injectReducer from 'filepath/injectReducer';
import { addToDo, starToDo } from 'containers/Todo/reducer';

class Todo extends React.Component {
// your component code here
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);

const addToDoReducer = injectReducer({
  key: 'todoList',
  reducer: addToDo,
});

const starToDoReducer = injectReducer({
  key: 'starredToDoList',
  reducer: starToDo,
});

export default compose(
  addToDoReducer,
  starToDoReducer,
  withConnect,
)(Todo);

React-Boilerplate is an excellent source for understanding this whole setup.You can generate a sample app within seconds. The code for injectReducer.js, configureStore.js( or store.js in your case) and in fact this whole configuration can be taken from react-boilerplate. Specific link can be found here for injectReducer.js, configureStore.js.

like image 172
Anu Avatar answered Oct 19 '22 09:10

Anu