I am writing a store enhancer to expose my library API through the redux store.
// consumer of the library
import React from "react";
import ReactDOM from "react-dom";
import { Provider, useStore } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import { myEnhancer } from "myLib";
const reducer = () => ({});
const store = createStore(reducer, compose(
myEnhancer,
));
function SomeComponent() {
const { myLib } = useStore();
return (
<button onClick={() => myLib.doSomething()}>
click!
</button>);
}
function App() {
return (
<Provider store={store}>
<SomeComponent />
</Provider>);
}
ReactDOM.render(<App />, document.getElementById("root"));
This works well, but my library also contains middleware that I would like to add to the redux store. I know I could expose the middleware as well and let the consumer of the library add it to its store:
import { myEnhancer, myMiddleware } from "myLib";
const reducer = () => ({});
const store = createStore(reducer, compose(
applyMiddleware(myMiddleware),
myEnhancer,
));
But since I am already providing a store enhancer I wonder if could not just add the middleware directly through the enhancer?
Sadly I'm unsure what the correct approach to do that is. This is how I'm trying to add the middleware:
// part of the library
import { applyMiddleware } from "redux";
const myMiddleware = (store) => (next) => (action) => {
next(action);
if (action.type === "demo") {
console.log("I'm a side effect!");
}
};
export const myEnhancer = (createStore) => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState, applyMiddleware(
myMiddleware
));
store.myLib = {
doSomething: () => store.dispatch({ type: "demo" }),
};
return store;
};
And this works!... But I must be doing it wrong because it stops working when I try to combine my enhancer with other enhancer:
// consumer of the library
// ...
// some additional middleware that the user of the library
// would like to add to the store
const logger = (store) => (next) => (action) => {
console.group(action.type);
console.info("dispatching", action);
const result = next(action);
console.log("next state", store.getState());
console.groupEnd();
return result;
};
const reducer = () => ({});
const store = createStore(reducer, compose(
applyMiddleware(logger),
myEnhancer,
window.__REDUX_DEVTOOLS_EXTENSION__
? window.__REDUX_DEVTOOLS_EXTENSION__()
: (noop) => noop,
));
// ...
It works if applyMiddleware
is placed behind my enhancer in compose (it should always be first though). And it always fails if I add the devtool enhancer.
How can I apply a middleware through my enhancer, so that it doesn't conflict with other enhancers such as applyMiddleware
?
To apply a middleware in redux, we would need to require the applyMiddleware function from the redux library. import {createStore, applyMiddleware} from "redux"; In order to check that our middleware is hooked up correctly, we can start by adding a log to display a message when an action is dispatched.
You can use applyMiddleware() .
A store enhancer, ahem... "enhances" or adds some additional capabilities to the store. It could change how reducers process data, or how dispatch works. Middleware is a function that lets us "tap into" what's happening inside Redux when we dispatch an action.
Redux Middleware allows you to intercept every action sent to the reducer so you can make changes to the action or cancel the action. Middleware helps you with logging, error reporting, making asynchronous requests, and a whole lot more.
Alright so I figured this out eventually after going through the redux code for createStore
and applyMiddleware
step by step. Took me a while to wrap my head around what is going on but applyMiddleware
simply "enhances" the store.dispatch
function by chaining the middleware on top of it.
Something like this:
store.dispatch = (action) => {
middleware1(store)(middleware2(store)(store.dispatch))(action);
};
We can keep adding middleware to store.dispatch and we can do that in our enhancer. This is how the enhancer looks in the end:
export const myEnhancer = (createStore) => (...args) => {
// do not mess with the args, optional enhancers needs to be passed along
const store = createStore(...args);
// add my middleware on top of store.dispatch
// it will be called before all other middleware already added to store.dispatch
store.dispatch = myMiddleware(store)(store.dispatch);
// very important - store.dispatch needs to be enhanced before defining the functions below.
// or the version of store.dispatch that they will call will not contain the
// middleware we just added.
store.myLib = {
doSomething: () => store.dispatch({ type: "demo" }),
};
return store;
};
My guess as to why composing with the devtools failed previously is that I wasn't passing the enhancer
prop to createStore
.
Now the order in which the enhancers are passed to compose
doesn't mater anymore.
const store = createStore(reducer, compose(
applyMiddleware(logger), // adds the logger to store.dispatch
myEnhancer, // adds the api to store, extends store.dispatch with custom middleware
window.__REDUX_DEVTOOLS_EXTENSION__
? window.__REDUX_DEVTOOLS_EXTENSION__()
: (noop) => noop,
));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With