jsforum / jsforum

A discussion forum for JavaScript, in the issues
Creative Commons Zero v1.0 Universal
113 stars 0 forks source link

How do you handle side effects in redux? #3

Open mattiamanzati opened 9 years ago

mattiamanzati commented 9 years ago

Well, I've tried many methods, mostly about routing side effects, like reducers, middleware and store enancher. How do you handle them? What kind of side effects have you faced in your experience?

I am wondering about them because I'd really want to handle them correctly to add a "remote desktop" feature inside my app, in order to provide customer support.

tomkis commented 9 years ago

I would still say side-effects should somehow be part of the domain. Therefore keep them in reducer and reducing them seems most natural to me. https://github.com/staltz/flux-challenge/blob/master/submissions/salsita/src/reducers/sithLoadingReducer.js#L110-L122

dzannotti commented 9 years ago

Elm has a beautiful solution for this, reducers can return either a new state or a new state and a side effect, i think it's a solution redux should embrace

mattiamanzati commented 9 years ago

Yeah, I saw that and I loved it! Maybe there's a way to implement it over a store enancher? Handling side effects in redux atm is kinda annoying :/

tomkis commented 9 years ago

@mattiamanzati check the repo above, it's exactly what I have implemented https://github.com/staltz/flux-challenge/blob/master/submissions/salsita/src/components/Root.jsx#L15-L22.

dzannotti commented 9 years ago

bingo! That's pretty much what i meant

tomkis commented 9 years ago

There is also an article, describing the concept http://blog.javascripting.com/2015/08/12/reduce-your-side-effects/

mattiamanzati commented 9 years ago

Yeah, but should'nt side effects being written 1:1 per reducer? I mean, the reducer itself should return the new state along the side effects to trigger, right? I have not understand correctly how you enqueue side effects, since each reducer can change only it's state branch, and not the effects key.

In your example side effects lives in a different reducer, and this is fine as long this works, not so much in terms of readability, right? Maybe I am just missing the whole point.

tomkis commented 9 years ago

In my example, I am not limiting effects list to particular reducer, they are global in the reduction, independent of any reducer.

mattiamanzati commented 9 years ago

Oh, now after a bit of testing and reproducing your code I got it. The only question is: is ther a reason to store to keep the effects queue in the store, or it is just a way to have a global variable? I have implemented it via a store enancher, do you think that this implementation can have problems or lacks of purity over time? :/ Note that I have decided to dispatch actions as a side effect handlers.

/*
    // inside your reducer you can do this

    return action.withEffect(authUser, (effects) => effects.concat([{
        type: REDIRECT,
        url: '/user/dashboard'
    }]));

    create the store with

    compose(
        sideEffectHandler
    )(createStore);
*/

// the store enancher which handles the reset of $effects
export default next => (reducer, initialState) => {
    // create the finalized store
    var store;
    // enanche the reducer you provide
    var enanchedReducer = (state, action) => {
        // array to store side effects generators in
        var sideEffectsStorage = [];        
        // attach on action a way to add a new side effect (found no better way :( )
        action = {
            ...action,
            // append the effects to the side effects store
            withEffect(state, ...effects){
                sideEffectsStorage.concat(effects);
                return state;
            }
        };
        // run the user reducer
        const result = reducer(state, action);
        // flatten the list of side effects
        sideEffectsStorage = sideEffectsStorage.reduce((effects, effectGenerator) => effectGenerator(effects), []);
        // now that side effects have been collected, trigger them!
        sideEffectsStorage.forEach(effectAction => store.dispatch(effectAction));
        // return the regular result
        return result;
    };

    // make the enanched store
    store = next(enanchedReducer, initialState);
    return store;
};
slorber commented 8 years ago

Flux/Redux are inspired by event-sourcing / CQRS in the backend which exist for many years.

In the backend there's a concept we use for this kind of problem that has not yet really shipped to the frontend world: Saga.

You can read an example I gave here on how this could be used in Redux: http://stackoverflow.com/a/33829400/82609

A presentation talk (backend): https://www.youtube.com/watch?v=xDuwrtwYHu8

tomkis commented 8 years ago

You might also find useful https://github.com/salsita/redux-side-effects, what is basically Elm like side effects reducing yet with a bit of syntax sugar.

(Yes, I am author)

slorber commented 8 years ago

@tomkis1 you might want to join this discussion: https://github.com/paldepind/functional-frontend-architecture/issues/20