From 8cf9d360dcf60d63517c4c29a58afabea6fe8b76 Mon Sep 17 00:00:00 2001 From: Alex Bettadapur Date: Sun, 21 Oct 2018 13:02:01 -0700 Subject: [PATCH 1/7] Add page --- docs/recipes/CodeSplitting.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/recipes/CodeSplitting.md diff --git a/docs/recipes/CodeSplitting.md b/docs/recipes/CodeSplitting.md new file mode 100644 index 0000000000..81cf8d0131 --- /dev/null +++ b/docs/recipes/CodeSplitting.md @@ -0,0 +1 @@ +# Code Splitting From 418082a190564927ff76430f27ce8edce5543e68 Mon Sep 17 00:00:00 2001 From: Alex Bettadapur Date: Sun, 21 Oct 2018 13:08:21 -0700 Subject: [PATCH 2/7] Outline --- docs/recipes/CodeSplitting.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/recipes/CodeSplitting.md b/docs/recipes/CodeSplitting.md index 81cf8d0131..6bd9f0b840 100644 --- a/docs/recipes/CodeSplitting.md +++ b/docs/recipes/CodeSplitting.md @@ -1 +1,13 @@ # Code Splitting +Introduction + +## Basic Principle + +## Libraries and Frameworks +There are a few good libraries out there that can help you add the above functionality automatically: + * [`redux-dynamic-reducer`](https://github.com/ioof-holdings/redux-dynamic-reducer) + * explanation +* [`redux-dynamic-modules`](https://github.com/Microsoft/redux-dynamic-modules) + * explanation +* [`redux-dynamic-middleware`] + * explanation From d2b793152bde9017f262a0d067582a517fc4d432 Mon Sep 17 00:00:00 2001 From: Alex Bettadapur Date: Sun, 21 Oct 2018 13:13:30 -0700 Subject: [PATCH 3/7] Add to readme --- docs/recipes/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/recipes/README.md b/docs/recipes/README.md index 075d610321..7239bfc22b 100644 --- a/docs/recipes/README.md +++ b/docs/recipes/README.md @@ -6,6 +6,7 @@ These are some use cases and code snippets to get you started with Redux in a re * [Migrating to Redux](MigratingToRedux.md) * [Using Object Spread Operator](UsingObjectSpreadOperator.md) * [Reducing Boilerplate](ReducingBoilerplate.md) +* [Code Splitting](CodeSplitting.md) * [Server Rendering](ServerRendering.md) * [Writing Tests](WritingTests.md) * [Computing Derived Data](ComputingDerivedData.md) From f66a44d78ac9fb4cc1c8195df0be83da5d2ac222 Mon Sep 17 00:00:00 2001 From: Alex Bettadapur Date: Sun, 21 Oct 2018 13:55:49 -0700 Subject: [PATCH 4/7] implementation example --- docs/recipes/CodeSplitting.md | 124 ++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/docs/recipes/CodeSplitting.md b/docs/recipes/CodeSplitting.md index 6bd9f0b840..5711040ce8 100644 --- a/docs/recipes/CodeSplitting.md +++ b/docs/recipes/CodeSplitting.md @@ -2,6 +2,130 @@ Introduction ## Basic Principle +To dynamically add Reducers to a Redux store, there are two approaches that can be taken + +### Using `replaceReducer` +The Redux store exposes the `replaceReducer` function, which replaces the current active reducer function with a new reducer function. We can leverage this function to add a reducer as follows: + +```javascript +import { createStore } from 'redux'; + +// Define the Reducers that will always be present in the appication +const staticReducers = { + users: usersReducer, + posts: postsReducer +}; + +// Configure the store +export default function configureStore(initialState) { + const store = createStore(createReducer(), initialState); + + // Add a dictionary to keep track of the registered async reducers + store.asyncReducers = {}; + + // Create an inject reducer function + // This function adds the async reducer, and creates a new combined reducer + store.injectReducer = (key, asyncReducer) => { + this.asyncReducers[key] = asyncReducer; + this.replaceReducer(createReducer(this.asyncReducers)); + }; + + // Return the modified store + return store; +} + +function createReducer(asyncReducers) { + return combineReducers({ + ...staticReducers, + ...asyncReducers + }); +} + +``` +Now, one just needs to call `store.injectReducer` to add a new reducer to the store. + +### Using a 'Reducer Manager' +Another approach is to create a 'Reducer Manager' object, which keeps track of all the registered reducers and exposes a `reduce()` function. Consider the following example: + +```javascript +export function createReducerManager(initialReducers) { + // Create an object which maps keys to reducers + const reducers = { ...initialReducers }; + + // Create the initial combinedReducer + let combinedReducer = combineReducers(reducers); + + // An array which is used to delete state keys when reducers are removed + const keysToRemove = []; + + return { + getReducerMap: () => reducers, + + // The root reducer function exposed by this object + // This will be passed to the store + reduce: (state, action) => { + // If any reducers have been removed, clean up their state first + if (keysToRemove.length > 0) { + state = { ...state as any }; + for (let key of keysToRemove) { + delete state[key]; + } + keysToRemove = []; + } + + // Delegate to the combined reducer + return combinedReducer(state, action); + }, + + // Adds a new reducer with the specified key + add: (key, reducer) => { + if (!key || reducers[key]) { + return; + } + + // Add the reducer to the reducer mapping + reducers[key] = reducer; + + // Generate a new combined reducer + combinedReducer = combineReducers(reducers); + }, + + // Removes a reducer with the specified key + remove: (key: string) => { + if (!key || !reducers[key]) { + return; + } + + // Remove it from the reducer mapping + delete reducers[key]; + + // Add the key to the list of keys to clean up + keysToRemove.push(key); + + // Generate a new combined reducer + combinedReducer = combineReducers(rm); + } + }; +} + +const staticReducers = { + users: usersReducer, + posts: postsReducer +}; + +export function configureStore(initialState) { + const reducerManager = createReducerManager(staticReducers); + + // Create a store with the root reducer function being the one exposed by the manager. + const store = createStore(reducerManager.reduce, initialState); + + store.reducerManager = reducerManager; +} +``` + +To add a new reducer, one can now call `store.reducerManager.add("asyncState", asyncReducer)`. + +To remove a reducer, one can now call `store.reducerManager.remove("asyncState")` ## Libraries and Frameworks There are a few good libraries out there that can help you add the above functionality automatically: From 4d20d094131743096963d0bffbebf465dd4d15bd Mon Sep 17 00:00:00 2001 From: Alex Bettadapur Date: Sun, 21 Oct 2018 14:08:52 -0700 Subject: [PATCH 5/7] Code splitting --- docs/recipes/CodeSplitting.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/recipes/CodeSplitting.md b/docs/recipes/CodeSplitting.md index 5711040ce8..ac4c90b9c3 100644 --- a/docs/recipes/CodeSplitting.md +++ b/docs/recipes/CodeSplitting.md @@ -1,5 +1,7 @@ # Code Splitting -Introduction +In large web applications, it is often desirable to split up the app code into multiple JS bundles that can be loaded on-demand. This strategy, called 'code splitting', helps to increase performance of your application by reducing the size of the initial JS payload that must be fetched. + +To code split with Redux, we want to be able to dynamically add reducers to the store. The default usage of Redux mandates that a single root reducer should to be passed to the `configureStore` call, which makes dynamically adding new reducers more difficult. Below, we discuss some approaches to solving this problem and reference two libraries that provide this functionality. ## Basic Principle To dynamically add Reducers to a Redux store, there are two approaches that can be taken @@ -119,6 +121,7 @@ export function configureStore(initialState) { // Create a store with the root reducer function being the one exposed by the manager. const store = createStore(reducerManager.reduce, initialState); + // Optional: Put the reducer manager on the store so it is easily accessible store.reducerManager = reducerManager; } ``` @@ -130,8 +133,6 @@ To remove a reducer, one can now call `store.reducerManager.remove("asyncState") ## Libraries and Frameworks There are a few good libraries out there that can help you add the above functionality automatically: * [`redux-dynamic-reducer`](https://github.com/ioof-holdings/redux-dynamic-reducer) - * explanation + * This library exposes the `addReducer` function on the Redux store to accomplish the behavior we described above. It also has React bindings which make it easier to add reducers within the React component lifecycle. * [`redux-dynamic-modules`](https://github.com/Microsoft/redux-dynamic-modules) - * explanation -* [`redux-dynamic-middleware`] - * explanation + * This library introduces the concept of a 'Redux Module', which is a bundle of Redux artifacts (reducers, middleware) that should be dynamically loaded. It also exposes a React higher-order component to load 'modules' when areas of the application come online. Additionally, it has integrations with libraries like `redux-thunk` and `redux-saga` which also help dynamically load their artifacts (thunks, sagas). From 23386556f1e9357a7dceeea9fc26d13d8c3a188e Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 16 Dec 2018 15:03:49 -0500 Subject: [PATCH 6/7] Rework code splitting page per review feedback and add page --- docs/recipes/CodeSplitting.md | 41 +++++++++++++++++++++++++++++------ website/sidebars.json | 1 + 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/docs/recipes/CodeSplitting.md b/docs/recipes/CodeSplitting.md index ac4c90b9c3..d549d9ec4d 100644 --- a/docs/recipes/CodeSplitting.md +++ b/docs/recipes/CodeSplitting.md @@ -1,13 +1,37 @@ +--- +id: code-splitting +title: Code Splitting +sidebar_label: Code Splitting +hide_title: true +--- + # Code Splitting In large web applications, it is often desirable to split up the app code into multiple JS bundles that can be loaded on-demand. This strategy, called 'code splitting', helps to increase performance of your application by reducing the size of the initial JS payload that must be fetched. -To code split with Redux, we want to be able to dynamically add reducers to the store. The default usage of Redux mandates that a single root reducer should to be passed to the `configureStore` call, which makes dynamically adding new reducers more difficult. Below, we discuss some approaches to solving this problem and reference two libraries that provide this functionality. +To code split with Redux, we want to be able to dynamically add reducers to the store. However, Redux really only has a single root reducer function. This root reducer is normally generated by calling `combineReducers()` or a similar function when the application is initialized. In order to dynamically add more reducers, we need to call that function again to re-generate the root reducer. Below, we discuss some approaches to solving this problem and reference two libraries that provide this functionality. ## Basic Principle -To dynamically add Reducers to a Redux store, there are two approaches that can be taken ### Using `replaceReducer` -The Redux store exposes the `replaceReducer` function, which replaces the current active reducer function with a new reducer function. We can leverage this function to add a reducer as follows: + +The Redux store exposes a `replaceReducer` function, which replaces the current active root reducer function with a new root reducer function. Calling it will swap the internal reducer function reference, and dispatch an action to help any newly-added slice reducers initialize themselves: + +```js +const newRootReducer = combineReducers({ + existingSlice : existingSliceReducer, + newSlice : newSliceReducer, +}) + +store.replaceReducer(newRootReducer) +``` + +## Reducer Injection Approaches + +### Defining an `injectReducer` function + +We will likely want to call `store.replaceReducer()` from anywhere in the application. Because of that, it's helpful +to define a reusable `injectReducer()` function that keeps references to all of the existing slice reducers, and attach +that to the store instance. ```javascript import { createStore } from 'redux'; @@ -131,8 +155,11 @@ To add a new reducer, one can now call `store.reducerManager.add("asyncState", a To remove a reducer, one can now call `store.reducerManager.remove("asyncState")` ## Libraries and Frameworks + There are a few good libraries out there that can help you add the above functionality automatically: - * [`redux-dynamic-reducer`](https://github.com/ioof-holdings/redux-dynamic-reducer) - * This library exposes the `addReducer` function on the Redux store to accomplish the behavior we described above. It also has React bindings which make it easier to add reducers within the React component lifecycle. -* [`redux-dynamic-modules`](https://github.com/Microsoft/redux-dynamic-modules) - * This library introduces the concept of a 'Redux Module', which is a bundle of Redux artifacts (reducers, middleware) that should be dynamically loaded. It also exposes a React higher-order component to load 'modules' when areas of the application come online. Additionally, it has integrations with libraries like `redux-thunk` and `redux-saga` which also help dynamically load their artifacts (thunks, sagas). + + * [`redux-dynostore`](https://github.com/ioof-holdings/redux-dynostore): + Provides tools for building dynamic Redux stores, including dynamically adding reducers and sagas, and React bindings to help you add in association with components. +* [`redux-dynamic-modules`](https://github.com/Microsoft/redux-dynamic-modules): + This library introduces the concept of a 'Redux Module', which is a bundle of Redux artifacts (reducers, middleware) that should be dynamically loaded. It also exposes a React higher-order component to load 'modules' when areas of the application come online. Additionally, it has integrations with libraries like `redux-thunk` and `redux-saga` which also help dynamically load their artifacts (thunks, sagas). +* [Redux Ecosystem Links: Reducers - Dynamic Reducer Injection](https://github.com/markerikson/redux-ecosystem-links/blob/master/reducers.md#dynamic-reducer-injection) diff --git a/website/sidebars.json b/website/sidebars.json index 103a4e5dcf..c30184ece0 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -41,6 +41,7 @@ "recipes/implementing-undo-history", "recipes/isolating-redux-sub-apps", "recipes/using-immutablejs-with-redux", + "recipes/code-splitting", { "type": "subcategory", "label": "Structuring Reducers", From 5a7259404d495d17c77454c8eb4048246222ffcf Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 16 Dec 2018 15:07:29 -0500 Subject: [PATCH 7/7] Fix formatting --- docs/recipes/CodeSplitting.md | 54 ++++++++++++++++++----------------- docs/recipes/README.md | 1 - 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/docs/recipes/CodeSplitting.md b/docs/recipes/CodeSplitting.md index d549d9ec4d..51fe9e5e0e 100644 --- a/docs/recipes/CodeSplitting.md +++ b/docs/recipes/CodeSplitting.md @@ -6,20 +6,21 @@ hide_title: true --- # Code Splitting + In large web applications, it is often desirable to split up the app code into multiple JS bundles that can be loaded on-demand. This strategy, called 'code splitting', helps to increase performance of your application by reducing the size of the initial JS payload that must be fetched. -To code split with Redux, we want to be able to dynamically add reducers to the store. However, Redux really only has a single root reducer function. This root reducer is normally generated by calling `combineReducers()` or a similar function when the application is initialized. In order to dynamically add more reducers, we need to call that function again to re-generate the root reducer. Below, we discuss some approaches to solving this problem and reference two libraries that provide this functionality. +To code split with Redux, we want to be able to dynamically add reducers to the store. However, Redux really only has a single root reducer function. This root reducer is normally generated by calling `combineReducers()` or a similar function when the application is initialized. In order to dynamically add more reducers, we need to call that function again to re-generate the root reducer. Below, we discuss some approaches to solving this problem and reference two libraries that provide this functionality. ## Basic Principle ### Using `replaceReducer` -The Redux store exposes a `replaceReducer` function, which replaces the current active root reducer function with a new root reducer function. Calling it will swap the internal reducer function reference, and dispatch an action to help any newly-added slice reducers initialize themselves: +The Redux store exposes a `replaceReducer` function, which replaces the current active root reducer function with a new root reducer function. Calling it will swap the internal reducer function reference, and dispatch an action to help any newly-added slice reducers initialize themselves: ```js const newRootReducer = combineReducers({ - existingSlice : existingSliceReducer, - newSlice : newSliceReducer, + existingSlice: existingSliceReducer, + newSlice: newSliceReducer }) store.replaceReducer(newRootReducer) @@ -29,48 +30,49 @@ store.replaceReducer(newRootReducer) ### Defining an `injectReducer` function -We will likely want to call `store.replaceReducer()` from anywhere in the application. Because of that, it's helpful +We will likely want to call `store.replaceReducer()` from anywhere in the application. Because of that, it's helpful to define a reusable `injectReducer()` function that keeps references to all of the existing slice reducers, and attach -that to the store instance. +that to the store instance. ```javascript -import { createStore } from 'redux'; +import { createStore } from 'redux' // Define the Reducers that will always be present in the appication const staticReducers = { - users: usersReducer, - posts: postsReducer -}; + users: usersReducer, + posts: postsReducer +} // Configure the store export default function configureStore(initialState) { - const store = createStore(createReducer(), initialState); + const store = createStore(createReducer(), initialState) // Add a dictionary to keep track of the registered async reducers - store.asyncReducers = {}; + store.asyncReducers = {} // Create an inject reducer function // This function adds the async reducer, and creates a new combined reducer store.injectReducer = (key, asyncReducer) => { - this.asyncReducers[key] = asyncReducer; - this.replaceReducer(createReducer(this.asyncReducers)); - }; + this.asyncReducers[key] = asyncReducer + this.replaceReducer(createReducer(this.asyncReducers)) + } // Return the modified store - return store; + return store } function createReducer(asyncReducers) { - return combineReducers({ - ...staticReducers, - ...asyncReducers - }); + return combineReducers({ + ...staticReducers, + ...asyncReducers + }) } - ``` + Now, one just needs to call `store.injectReducer` to add a new reducer to the store. ### Using a 'Reducer Manager' + Another approach is to create a 'Reducer Manager' object, which keeps track of all the registered reducers and exposes a `reduce()` function. Consider the following example: ```javascript @@ -158,8 +160,8 @@ To remove a reducer, one can now call `store.reducerManager.remove("asyncState") There are a few good libraries out there that can help you add the above functionality automatically: - * [`redux-dynostore`](https://github.com/ioof-holdings/redux-dynostore): - Provides tools for building dynamic Redux stores, including dynamically adding reducers and sagas, and React bindings to help you add in association with components. -* [`redux-dynamic-modules`](https://github.com/Microsoft/redux-dynamic-modules): - This library introduces the concept of a 'Redux Module', which is a bundle of Redux artifacts (reducers, middleware) that should be dynamically loaded. It also exposes a React higher-order component to load 'modules' when areas of the application come online. Additionally, it has integrations with libraries like `redux-thunk` and `redux-saga` which also help dynamically load their artifacts (thunks, sagas). -* [Redux Ecosystem Links: Reducers - Dynamic Reducer Injection](https://github.com/markerikson/redux-ecosystem-links/blob/master/reducers.md#dynamic-reducer-injection) +- [`redux-dynostore`](https://github.com/ioof-holdings/redux-dynostore): + Provides tools for building dynamic Redux stores, including dynamically adding reducers and sagas, and React bindings to help you add in association with components. +- [`redux-dynamic-modules`](https://github.com/Microsoft/redux-dynamic-modules): + This library introduces the concept of a 'Redux Module', which is a bundle of Redux artifacts (reducers, middleware) that should be dynamically loaded. It also exposes a React higher-order component to load 'modules' when areas of the application come online. Additionally, it has integrations with libraries like `redux-thunk` and `redux-saga` which also help dynamically load their artifacts (thunks, sagas). +- [Redux Ecosystem Links: Reducers - Dynamic Reducer Injection](https://github.com/markerikson/redux-ecosystem-links/blob/master/reducers.md#dynamic-reducer-injection) diff --git a/docs/recipes/README.md b/docs/recipes/README.md index 428cfa7274..8655c9666c 100644 --- a/docs/recipes/README.md +++ b/docs/recipes/README.md @@ -9,7 +9,6 @@ hide_title: true These are some use cases and code snippets to get you started with Redux in a real app. They assume you understand the topics in [basic](../basics/README.md) and [advanced](../advanced/README.md) tutorials. - - [Configuring Your Store](ConfiguringYourStore.md) - [Migrating to Redux](MigratingToRedux.md) - [Using Object Spread Operator](UsingObjectSpreadOperator.md)