samiskin / redux-electron-store

⎋ A redux store enhancer that allows automatic synchronization between electron processes
MIT License
375 stars 32 forks source link

Redux with middlewares #31

Closed shir closed 7 years ago

shir commented 7 years ago

Hi. Thanks for your library.

But I'm confused a bit because can't make the library to work the right way with middlewares.

At first a bit of code. The main process:

const { combineReducers, applyMiddleware, createStore, compose } = require('redux');
const reduxLogger = require('redux-logger');
const { electronEnhancer } = require('redux-electron-store');
const createSagaMiddleware = require('redux-saga').default;

const reducers = require('reducers');
const rootSaga = require('sagas');
const { loadTasks } = require('actions');

const sagaMiddleware = createSagaMiddleware();

let enhancer = compose(
  applyMiddleware(
    sagaMiddleware,
    reduxLogger()
  ),
  electronEnhancer()
);

let store = createStore(combineReducers(reducers), {}, enhancer);

sagaMiddleware.run(rootSaga);

Don't looks that redux-saga is used. It doesn't depend on which middleware is used because redux-logger doesn't work too.

The renderer process:

const { applyMiddleware, createStore, compose } = require('redux');
const reduxLogger = require('redux-logger');
const { electronEnhancer } = require('redux-electron-store');

const filter = {
  tasks: true,
};

let enhancer = compose(
  applyMiddleware(
    reduxLogger()
  ),
  electronEnhancer({filter})
);

const store = createStore((s) => s, {}, enhancer);

I call store.dispatch(loadTasks()) on the renderer process. I expect that the action will not do anything on renderer's store, but just passed to the main process (that's why renderer reducer doesn't do anything). On the main process this action will be handled by middlewares (in my case mainly it's redux-saga), the state will be changed and changes will be propagated to the renderer's store.

And this doesn't work. I added some debug info and I see that the action is passed to the main process and store.dispatch() is called. But no middlewares are applied. In same time if I run same action from the main process (just for test purpose), all works as expected: middlewares are applied and the state is propagated to the render's store. But not when the action is dispatched from renderer.

After some time of debuging I found that looks like store stored in global doesn't have any middlewares applied because enhancer is connected to store before any middlewares are applied:

compose(
  applyMiddleware(
    sagaMiddleware,
    reduxLogger()
  ),
  electronEnhancer()
);

I tried to change order and apply electronEnchancer after applyMiddleware and now renderer's action is processed with middlewares, but updated state is not propogated to renderer process anymore.

samiskin commented 7 years ago

Ah you're right! Surprised I didn't find this before.

The behavior I'm finding right now is that

let enhancer = compose(
  electronEnhancer(),
  applyMiddleware(thunk, logger)
)

Will result in the logger receiving actions from the renderer, but store enhancers/middlewares which call dispatch on their own (like saga or thunk) will not have those dispatches be forwarded.

Having

let enhancer = compose(
  applyMiddleware(thunk, logger),
  electronEnhancer()
)

Will result in the thunk dispatches to be forwarded, but the renderer dispatches would not pass through the middlewares like logger.

The problem here is that as a store enhancer, you only get access to the created store at whatever stage you're at in the compose. Since standard store enhancers can't work, I'd have to change the API in a very breaking way. I'll try to think of how best to approach this, and any ideas are very welcome. It might take a while to get to though, since I'm a bit swamped these days.

I suppose one idea would be splitting it into 2 enhancers, an electronReceiver and electronEmitter, and then requiring that it sandwich the rest of the enhancers

Sorry I couldn't be of more help in a more timely way. Thanks for submitting this post and letting me know of this issue!

samiskin commented 7 years ago

Alright so the way I decided to do it was using a dispatchProxy option:

import { createStore, applyMiddleware, compose } from 'redux';
import { electronEnhancer } from 'redux-electron-store';

let enhancer = compose(
  applyMiddleware(...middleware),
  // Must be placed after any enhancers which dispatch
  // their own actions such as redux-thunk or redux-saga
  electronEnhancer({
    // Allows synched actions to pass through all enhancers
    dispatchProxy: a => store.dispatch(a),
  })
);

// Note: passing enhancer as the last argument to createStore requires redux@>=3.1.0
let store = createStore(reducer, initialState, enhancer);
shir commented 7 years ago

@samiskin thank you!