Declaratively code-split your redux store and make containers own entire redux flow using redux-store-manager
yarn add redux-store-manager
- rootReducer is traditionally created manually using combineReducers and this makes code-splitting reducers based on how widgets consuming their data are loaded(whether they are in the main bundle or on-demand bundles) hard.
- Bundler cant tree-shake or dead code eliminate the rootReducer to not include reducers whose data is not consumed by any container components
- Let the containers that are going to consume the data stored by a reducer and trigger actions take responsibility of adding a reducer to the store.
This makes the container owning the entire redux flow by linking
- Actions as component props via mapDispatchToProps
- Reducer responsible for updating the data via storeManager.registerReduers
- Data as component props via mapStateToProps
- Use the redux store's replaceReducer API whatever reducers are registered when an on-demand chunk loads the store gets refreshed with the new rootReducer.
storeManager is a singleton that has the following methods
- reducerMap is an object with reducer namespaces as keys and reducer definitions as values (similar to the object you pass to combineReducers).
- This method is used in container components to register the reducer on the store to be created.
- createStore method takes the same arguments that createStore function of redux library without the rootReducer argument.
- createStore is called to pass the store prop to component.
- both the initialState, storeEnhancer are optional.
- refreshStore is called when the on-demand build chunk is loaded, to replace the rootReducer of the store.
- The refreshed store will include the registered reducers in the on-demand chunk that was just loaded.
Root.js
import {Provider} from 'react-redux'; import storeManager from 'redux-store-manager'; import App from './containers/AppContainer'; export default function Root() { return ( // creates store with all the reducers registered by container components <Provider store={storeManager.createStore(initialState, storeEnhancer)}> <App> </Provider> ) } /* ReactDOM.render(<Root/>, document.getElementById('root')); */
App.js
import React, {Component} from 'react'; import {withRefreshedStore} from 'redux-store-manager'; import SimpleWidgetContainer from './containers/SimpleWidgetContainer'; export default class App extends React { state = { OnDemandWidgetContainer: null }; componentWillMount() { // when loading a widget on-demand along with the component codebase the reducers are also withRefreshedStore(import('./containers/SimpleWidgetContainer')).then((module) => { this.setState({OnDemandWidgetContainer: module.default}); }); } render() { const {OnDemandWidgetContainer} = this.state; return ( <> <SimpleWidgetContainer /> {OnDemandWidgetContainer ? <OnDemandWidgetContainer/> : null} </> ); } }
SimpleWidgetContainer.js
import storeManager from 'react-store-manager'; import {connect} from 'react-redux'; import SimpleDemandWidget from '../components/SimpleWidget'; import simpleDemandWidgetReducer from '../reducers/simopleWidgetReducer'; import {getSimpleWidgetData} from '../actions'; // reducer and its initialStatw will be added to the store. storeManager.registerReducers({ simpleWidgetData: simpleWidgetDataReducer }); const mapStateToProps = (state) => ({ simpleWidgetData: state.simpleWidgetData }); const mapDispatchToProps = { getSimpleWidgetData }; export default connect( mapStateToProps, mapDispatchToProps )(OnDemandWidget);
OnDemandWidgetContainer.js
import storeManager from 'react-store-manager'; import {connect} from 'react-redux'; import OnDemandWidget from '../components/OnDemandWidget'; import onDemandWidgetReducer from '../reducers/onDemandWidgetReducer'; import {getOnDemandWidgetData} from '../actions'; /* * reducers registered in on-demand chunks will be added to redux store * only when the on-demand chunk is loaded */ storeManager.registerReducers({ onDemandWidgetData: onDemandWidgetDataReducer }); const mapStateToProps = (state) => ({ onDemandWidgetData: state.onDemandWidgetData }); const mapDispatchToProps = { getOnDemandWidgetData }; export default connect( mapStateToProps, mapDispatchToProps )(OnDemandWidget);