In this blog we will talk about how an enhancer works and how it allows us to “create” a new Store Creator and “replace” the createStore
function provided by Redux.
Store creator
As we know, we create a Store using the createStore
function. This function takes a reducer as its first argument, and an initial state (optional) and an enhancer (also optional) as its second and third arguments, respectively. The createStore
function returns a Store.
type StoreCreator = (reducer: Reducer, initialState?: State, enhancer?:Enhancer) => Store
What is an enhancer?
An enhancer is simply a higher-order function, this means that it can take functions as arguments or return a function. In this case, an enhancer takes the current StoreCreator
as its argument and returns a new StoreCreator
. We can represent it as follows:
type StoreEnhancer = (currentStoreCreator: StoreCreator) => StoreCreator
Let's create a dummy enhancer.
import { createStore } from "redux"; // the enhancer takes a CreateStore as argument const dummyEnchancer = (createStore) => { return (reducer, state, enhancer) => { const store = createStore(reducer, state, enhancer) // and return a new Store return { ...store } } } const store = createStore(rootReducer, undefined, dummyEnchancer);
In the example above, we can see that we can customize the StoreCreator
function as we wish. The only rule is that we must return a new StoreCreator
function, which will replace the one provided by Redux.
In summary:
- The enhancer must return a new Store Creator
- You can add any new functionalities to the Store Creator or the Store, as we will see later
- The new Store Creator must return a new Store
- Add the enhancer as the last parameter to the
createStore
function
Examples:
1. Customize the store dispatch:
import { createStore } from "redux"; const greetEnhancer = (createStore) => { return (reducer, state, enhancer) => { // create a store const store = createStore(reducer, state, enhancer) // create a "Dispatch enhancer" const newDispatch = (action) => { const result = store.dispatch(action) // add some logic after dispatch any action console.log('Do something here :D') return result } return { ...store dispatch: newDispatch } } } const store = createStore(rootReducer, undefined, greetEnhancer);
In this example, we create a store and use its dispatcher inside a new one. This ensures that the action is dispatched and that we can add our own logic. Finally, we return the new StoreCreator
, which provides a new store with the dispatcher we just created.
2. Customize the store reducer (source):
import { createStore } from "redux"; const monitorReducerEnhancer = createStore => (reducer, initialState, enhancer) => { // create an "Reducer enhancer" const monitoredReducer = (state, action) => { // add time measurement when using the store reducer const start = performance.now() const newState = reducer(state, action) const end = performance.now() const diff = round(end - start) console.log('reducer process time:', diff) return newState } // return the new Store created using the new reducer return createStore(monitoredReducer, initialState, enhancer) } const store = createStore(rootReducer, undefined, monitorReducerEnhancer);
In the above example, we create an enhancer that contains a “new reducer” which wraps the original reducer. This allows new logic to be executed each time the reducer is used. Finally, we return the new StoreCreator
that creates a store which uses the monitoredReducer
as the reducer.