Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux middleware design re: return values

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?

like image 714
maambmb Avatar asked Mar 07 '17 20:03

maambmb


People also ask

What does Redux middleware return?

This middleware returns a function that can be called to cancel the dispatch.

Which middleware is best in Redux?

Redux Thunk is recommended middleware that helps you to overcome the side effects of basic Redux. Thunk Redux helps you solve complex synchronous logic as well as simple async logic. Let me quote a redux-thunk example of synchronous logic- it can be access to the store.

How do you handle async actions in Redux?

Redux Async Data Flow​ Just like with a normal action, we first need to handle a user event in the application, such as a click on a button. Then, we call dispatch() , and pass in something, whether it be a plain action object, a function, or some other value that a middleware can look for.

Can I dispatch an action in reducer?

Dispatching an action within a reducer is an anti-pattern. Your reducer should be without side effects, simply digesting the action payload and returning a new state object. Adding listeners and dispatching actions within the reducer can lead to chained actions and other side effects.


2 Answers

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 );
like image 50
maambmb Avatar answered Oct 17 '22 15:10

maambmb


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

like image 38
Yo Wakita Avatar answered Oct 17 '22 17:10

Yo Wakita