Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct way to add additional middleware through a redux store enhancer?

Tags:

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?

like image 586
au.re Avatar asked Aug 01 '19 22:08

au.re


People also ask

How do I add middleware to Redux?

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.

Which function is used to add multiple middleware Redux?

You can use applyMiddleware() .

What is Redux store enhancer?

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.

What are some of the valid middleware functions that we can use with Redux?

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.


1 Answers

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,
));
like image 65
au.re Avatar answered Oct 08 '22 20:10

au.re