zalmoxisus / remote-redux-devtools

Redux DevTools remotely.
http://zalmoxisus.github.io/monitoring/
MIT License
1.8k stars 138 forks source link

Problem with multiple stores (redux-saga) #53

Open tvedtorama opened 7 years ago

tvedtorama commented 7 years ago

Hi, I use multiple stores on a serverside Redux projects. I also use the redux-saga middleware.

When I enable remote-redux-devtools, I get a mixup of states. It seems the middleware is merging all actions dispatched on any store into the last store. I only see the latest store in remotedev.io and the redux generator function (main) of the last store seems to "take" all the actions.

I initialize with different names for the "instances", all in the same process.

Could there be an issue with multiple remote-redux-devtools in the same process, or something with sagas?

I tried deleting the node "require-cache" between calls, to no avail.

My source (typescript) below, work in progress. I have commented out the devtools and things work perfectly.

const RemoteDevTools = require('remote-redux-devtools')
const {composeWithDevTools} = RemoteDevTools

function* mainLoop() : any {
    const {scenarioId} = yield take(SET_SCENARIO)
    console.log("got scenario: ", scenarioId)
    const channel = yield actionChannel('*')
    yield delay(2000)
    yield put(initialized())
    const testState = (yield select()) as IState
    console.log(`state scenario: ${testState.initialized.scenarioId}`)
    while (true) {
        const action = yield take(channel)
        console.log(`got action (${scenarioId}): `, action)
    }
}

export function createScenarioStore(scenarioId: string) {

    // This causes the remote dev tool to use remotedev.io, which can be accessed at remotedev.io/local
    const composeEnhancers = composeWithDevTools({ realtime: true, name: `scenario_${scenarioId}` }) 

    const middleware = createSagaMiddleware()
    const store = createStore(mainReducer, applyMiddleware(middleware)) // composeEnhancers(applyMiddleware(middleware)))

    middleware.run(mainLoop)
    store.dispatch(setScenario(scenarioId))

    return store
}

BTW: This project / tool is absolutely excellent!

zalmoxisus commented 7 years ago

@tvedtorama I didn't updated the web version for a while (as we're using it from the extension mainly). Updated it now. Could you please give it another try?

Thanks for the details and the feedback!

tvedtorama commented 7 years ago

@zalmoxisus Sorry for my ignorance, but what do you mean by "update it now"? I tried to npm install again, but seemingly got the same version (0.5.1) and the same problem.

tvedtorama commented 7 years ago

@zalmoxisus BTW: The problem is the same even if I don't connect to remotedev.io (i get mixed stores locally).

zalmoxisus commented 7 years ago

@tvedtorama, I meant that I updated the web version (remotedev.io/local). Just refresh the page there. Not sure it will fix the issue, but a lot was changed.

zalmoxisus commented 7 years ago

Could you try if using just the enhancer instead of composeWithDevTools works:

import devToolsEnhancer from 'remote-redux-devtools';
const store = createStore(reducer, compose(applyMiddleware(middleware), devToolsEnhancer()));
tvedtorama commented 7 years ago

I can't get this to work with typescript. I tried:

const RemoteDevTools = require('remote-redux-devtools')
const devToolsEnhancer = RemoteDevTools

// Result: devToolsEnhancer is not a function

And:

const RemoteDevTools = require('remote-redux-devtools')
const {composeWithDevTools, devToolsEnhancer} = RemoteDevTools

// Result: devToolsEnhancer is not a function

Where is the function located?

zalmoxisus commented 7 years ago

It's here. Use it like RemoteDevTools.default if you don't use ES6 imports.

tvedtorama commented 7 years ago

Ok, sorry.

With the following setup it works, no mixup of states:

import { createStore, applyMiddleware, compose } from 'redux'

const RemoteDevTools = require('remote-redux-devtools')
const devToolsEnhancer = RemoteDevTools.default

export function createScenarioStore(scenarioId: string) {

    const middleware = createSagaMiddleware()
    const store = createStore(mainReducer, compose(applyMiddleware(middleware), devToolsEnhancer()))

I use compose from redux and the function you gave me as it's second argument.

zalmoxisus commented 7 years ago

I see. I'll investigate that. Thanks for the details!

zalmoxisus commented 7 years ago

@tvedtorama, in your first example you commented the usage with devtools, was it like bellow?

const store = createStore(mainReducer, composeEnhancers(applyMiddleware(middleware)))

I cannot reproduce the problem. Maybe it was a confusion due to the typo in docs corrected yesterday. If not, could you please change our example to reproduce it?

tvedtorama commented 7 years ago

@zalmoxisus, I took up the fight with ES5 and made a plain node.js script with 2 stores, possibly more. I intended to do sagas, like I have in my example - but the Generators does not play easy with ES5. Instead I used Thunks, and although I don't see any state mixing in the stores, I do see only one instance over at RemoteDev.io. This is an issue, but my biggest concern is the mixing of states. It might be that the problem is in the sagas implementation?

Here is my multi store thunk implementation, showing only one instance in RemoteDev.io (feel free to use for whatever purpose you want):

var redux = require('redux')

var thunk = require('redux-thunk').default

var createStore = redux.createStore
var applyMiddleware = redux.applyMiddleware
var compose = redux.compose
var devTools = require('remote-redux-devtools')

function counter(state, action) {
  if (state === undefined) state = {num: 0, }
  switch (action.type) {
  case 'INCREMENT':
    return {num: state.num + 1, scenarioId: state.scenarioId}
  case 'DECREMENT':
    return {num: state.num - 1, scenarioId: state.scenarioId}
  case 'SCENARIO_ID':
    return {num: state.num, scenarioId: action.scenarioId}
  default:
    return state
  }
}

var composeWithDevTools = devTools.composeWithDevTools

function aThunk() {
  return function(dispatcher) {
    var fun = function() {
      dispatcher({ type: 'INCREMENT' })
      setTimeout(fun, 1000)
    }
    setTimeout(fun, 1000)
  }
}

function createScenarioStore(scenarioId) {

  // This causes the remote dev tool to use remotedev.io, which can be accessed at remotedev.io/local
  var composeEnhancers = composeWithDevTools({ realtime: true, name: `scenario_${scenarioId}` }) 

  var store = createStore(counter, composeEnhancers(applyMiddleware(thunk))) 

  store.dispatch({type: 'SCENARIO_ID', scenarioId: scenarioId})
  store.subscribe(function() { console.log(store.getState().scenarioId, store.getState().num) })

  store.dispatch(aThunk())

  return store
}

var store = createScenarioStore('Scenario1') // createStore(counter, devTools({realtime: true}))
var store2 = createScenarioStore('Scenario2') // createStore(counter, devTools({realtime: true}))

/*function incrementer() {
  setTimeout(function() {
    store.dispatch({ type: 'INCREMENT' })
    incrementer()
  }, 1000)
}

incrementer() */

Some wilds attempts to bring in sagas:

/*var createSagaMiddleware = requre('redux-saga')
var delay = createSagaMiddleware.delay
var sagaEffects = require('redux-saga/effects')
var put = sagaEffects.put
var actionChannel = sagaEffects.actionChannel
var take = sagaEffects.take
var select = sagaEffects.select */

Full project on github

zalmoxisus commented 7 years ago

@tvedtorama, great example, thanks!

Yes, it doesn't support multiple instances. I misunderstood the problem thinking it's a problem only with composeWithDevTools.

The monitor app support multiple instances, we just need to implement it here.

Basically, except the socket id, we also need to send instanceId so the monitor could identify the instance. Here's an example of implementation. Also instanceName shouldn't be global.

I'm a bit busy with some work in progress for the monitor and extension. Would you like to submit a PR with these changes?

tvedtorama commented 7 years ago

@zalmoxisus I see. Do you think this lack of multi-instance-support could explain the state mixups I observed locally also?

I dont't know how to "submit a PR"? Can we just follow up on this thread later, when you have some spare time?

zalmoxisus commented 7 years ago

Do you think this lack of multi-instance-support could explain the state mixups I observed locally also?

No, it shouldn't affect the state in any way. Can you replicate this problem with your example?

If you want to try to send a PR, it's rather simple - just fork this repo, make your changes then you'll have a button "Compare & pull request". Then we can discuss and modify it together. Here's a detailed tutorial. Just in case you want to try.

tvedtorama commented 7 years ago

I can't reproduce it with "thunks" in my example, and I suspect it to be specific to redux-saga and it's middleware.

Thanks for the info on PRs. I'd love to contribute, but like you I am short on time right now. I'll see if I get the chance later.

zalmoxisus commented 7 years ago

@tvedtorama, anyway, thanks for the example and lots of details, it helps a lot. I'll take a peek on this next week, but if you get a chance to do it earlier it would be welcome.

jtag05 commented 7 years ago

Hi, I've also recently encountered this issue. Has there been any movement in why this would be occurring? My team is building a set of widgets that operate independent of whatever react application they're being utilized in so the possibility of encountering multiple stores is pretty high.

Thanks in advance, and thanks for making this awesome product.

jtag05 commented 7 years ago

Hi just to give an update. I have a feeling I have a very unique problem in that I'm currently running both stores from the same remote-redux-devtools node module. It then occurred to me that the module is a singeton and that would explain why using composeWithDevtools was causing conflicts between my two stores.

zalmoxisus commented 7 years ago

Hi, @jtag05. The module doesn't support multiple stores yet. You can use react-native-debugger, which implements that. I'll come back to it after get Redux DevTools Extension 3.0 released. If you want to contribute in implementing this functionality here, it would be much appreciated.

slaivyn commented 5 years ago

hi @zalmoxisus ! This issue is very old but I have the same problem : I am using several stores in a server-side app and all actions are mixed in a single redux history even if I set different instance names. Does the module still not support multiple stores or do I have to search more and find the real issue? Thank you!

slaivyn commented 5 years ago

I found out that this issue is fixed in master thank to @Lukermelesh (PR #132) and is only published to the beta channel. So for anyone interested: install remote-redux-devtools@beta (and use remote-redux-devtools) Thank you @zalmoxisus for this fantastic tool! Thank you @Lukermelesh for this fix absolutely necessary for my use case!