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))
}
In order to inject reducers asynchronously, in the first step you need to write create store in the format that you mentioned:
Reducers
In reducers, the only difference is to get asyncReducers as the input of createReducer function and use it in the following way for combine reducers.
Configure Store
Your configureStore file should look like below. I made a few changes to your structure. First I applied middlewares in enhancers in order to be able to use chrome redux DevTool Extention if it is installed otherwise use redux compose, (and also use reducer hot-reloader for async reducers).
Component
A simple component will be like this. As you see in this component we first
connect
component to react-redux and can usemapStateToProps
andmapDispatchToProps
, and then in order to inject the reducer for this file we need two things:1) the reducer file, 2)inject reducer function
afterwards, we compose connect and reducerInjected to the component.
injectReducer.js
this file can be implemented in quite a few ways. one of the best practices is implemented by react-boilerplate. This is the file which is used to inject reducers into your components; however, this file has one other dependency (
getInjectors.js
) that can be put in a utils alongside injectReducer.jsgetInjectors.js
Now everything is set, You have all the functionality such as reducer injection and even support for hot module reducer load in the development stage. However, I highly suggest two things:
It might be a great idea to look at
react-boilerplate
as it offers a lot of great features implemented with best practices focused on large-scale applications.If you are planning to have code splitting it means that you are going to have an application with scalability issue. As a result, I recommend not to use redux-thunk and use redux saga instead. And the best solution is to
Inject saga middlewares asynchronously
and eject saga files as soon as the component is unMounted. This practice can improve your application in several ways.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:
Then, in your store.js file,
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:
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.
You could inject not only reducers but also sagas, load pages by chunks and make your components really in competent way with its own css and assets (images, icons) no thing global everything is dynamically attached to app. There is a whole philosophy about it - atomic design, and here is a boilerplate which pursues a similar idea:
https://github.com/react-boilerplate/react-boilerplate
I realize my answer is not sufficiently complete answer but it may give more idea for the next steps.