10

So I've just read up on redux middleware, and it sounds great. One thing is bugging me though - the return values of the middleware.

I understand that some instances of middleware return stuff (i.e. redux-promise) , and I get that other middleware (i.e. logging) doesn't - and simply returns the result of next(action).

My issue is what happens if I want to use two pieces of middleware that both return stuff - surely they will clobber each other and I will only get the outer-most middleware's return value.

express/connect middleware addresses this by letting middleware write its "results" to the req and res objects, but whats the solution with redux?

EDIT

Here is a more concrete example of my issue:

I have two pieces of middleware:

  1. Middleware that defers all actions being dispatched by 3 seconds. This middleware returns a function that can be called to cancel the dispatch
  2. Middleware that returns the number 5, because I need the number 5 for some reason.

Depending on the order I chain these two bits of middleware, the result of my dispatch(action) will either be the defer cancellation fn, or the number 5. But how do I get both of these results?

maambmb
  • 881
  • 1
  • 8
  • 18
  • That's pretty much how Redux middleware is designed to behave. Most middleware will simply pass back whatever return value they receive from `next()`, but a middleware is totally allowed to return something else if it wants to. This largely boils down to JS functions only returning a single value. – markerikson Mar 07 '17 at 22:42
  • 1
    A path to an good answer might start with the Redux Typescript definitions: https://github.com/reactjs/redux/blob/master/index.d.ts – contrebis Apr 10 '18 at 14:37
  • For reference, this is a comment in the Redux comments mentioning the arbitrary-return-value behavior, btw: https://github.com/reduxjs/redux/blob/9d3273846aa8906d38890c410b62fb09a4992018/src/createStore.ts#L219 – Venryx Oct 24 '19 at 08:21

3 Answers3

3

Take a look at the documentation on applyMiddleware. It explains that middlewares are to be written to be composable, so that it can be plugged into the chain of middlewares without worrying about the middlewares that are applied before and after it:

The key feature of middleware is that it is composable. Multiple middleware can be combined together, where each middleware requires no knowledge of what comes before or after it in the chain.

The documentation does a very good job of explaining the arguments that are to be passed into the middleware and the expected return.

https://redux.js.org/api/applyMiddleware

amankkg
  • 4,503
  • 1
  • 19
  • 30
Yo Wakita
  • 5,322
  • 3
  • 24
  • 36
  • 1
    Hi Yo, thanks for the response! Had a read through the linked page but still feel like my issue is unanswered. I've edited my original question to provide a concrete-ish example to improve clarity. Thanks! – maambmb Mar 07 '17 at 20:37
  • 1
    I'm not sure that I understand your examples. The middlewares are expected to take in two arguments, `getState` and `dispatch`, and return a function that returns `next(action)`. The middleware signature is the same for every middleware, so you wont have a middleware that returns `5`. From the doc: "the middleware signature is `({ getState, dispatch }) => next => action`" – Yo Wakita Mar 07 '17 at 20:50
  • @YoWakita I think maambmb is referring to the same "arbitrary returning" that is referenced in the Redux comment here: https://github.com/reduxjs/redux/blob/9d3273846aa8906d38890c410b62fb09a4992018/src/createStore.ts#L219 – Venryx Oct 24 '19 at 08:21
3

So below is a runnable script that demonstrates the problem I'm trying (and failing) to describe. It also includes a potential solution (using a middleware wrapper). Would love to know if there are any more elegant solutions out there....

var { createStore, applyMiddleware } = require( "redux" );
var dispatchResult;

// create the results object to be passed along the middleware chain, collecting
// results as it goes
const genesis = _store => next => action => {
    next( action );
    return {};
};

const wrapper = ( key, mware ) => store => next => action => {

    // extract the results object by storing the result of next(action)
    // when it is called within the middleware
    var extractedResult;
    function modifiedNext( action ) {
        extractedResult = next( action );
        return extractedResult;
    }

    // get the result of this middleware and append it to the results object
    // then pass on said results object...
    var newResult = mware( store )( modifiedNext )( action );
    extractedResult[ key ] = newResult;
    return extractedResult;
};

// create standard logging middleware
const logger = store => next => action => {
    let result = next( action );
    console.log( `value is: ${ store.getState() }.`);
    return result;
};

// create middleware that returns a number
const gimme = val => _store => next => action => {
    next( action );
    return val;
};

// create our super simple counter incrementer reduer
function reducer( state = 0, action ) {
    if( action.type === "INC" )
        return state + 1;
    return state;
}


// first lets try running this without the wrapper:
dispatchResult = createStore( reducer, applyMiddleware(
    gimme( 4 ),
    logger,
    gimme( 5 )
) ).dispatch( { type : "INC" } );

// will return only 4 (the result of the outermost middleware)
// we have lost the 5 from the gimme(5) middleware
console.log( dispatchResult );

// now we include the middleware wrapper and genesis middleware
dispatchResult = createStore( reducer, applyMiddleware(
    wrapper( "g4", gimme( 4 ) ),
    logger,
    wrapper( "g5", gimme( 5 ) ),
    genesis
) ).dispatch( { type : "INC" } );

// we will now return { g4 : 4, g5 : 5 }
// we have preserved the results of both middlewares
console.log( dispatchResult );
maambmb
  • 881
  • 1
  • 8
  • 18
0

Your missing the point of Middleware, its a pipeline for consuming and dispatching actions. Return values are typically ignored.

John Little
  • 778
  • 6
  • 14