Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strange state behavior when calling an actionCreator in a redux subscriber #364

Closed
happypoulp opened this issue Jul 30, 2015 · 2 comments
Closed
Labels

Comments

@happypoulp
Copy link

Hi,

I came across a strange state behavior using redux 0.12 and I don't know if it's a bug or a misuse. The code above does not reflect my real code but is the best minimal example I could came up with that highlight the "bug".

// test.js
import { createRedux } from 'redux'

var stores = {
    connectStore(state = {}, action) {
      console.log('store.connectStore(), action received: state', state, 'action', action)
      switch (action.type) {
        case 'CONNECT':
          return {
            ...state,
            logged: true
          }
        default: return state
      }
    },
    profileStore (state = {}, action) {
      console.log('store.profileStore(), action received: state', state, 'action', action)
      switch (action.type) {
        case 'PROFILE':
          return {
            ...state,
            description: 'test desc'
          }
        default: return state
      }
    }
}

var actionCreators = {
    connect() {
      console.log("\n", 'actionCreators.connect()', "\n")
      return { type: 'CONNECT' }
    },
    getProfileData() {
      console.log("\n", 'actionCreators.getProfileData()', "\n")
      return { type: 'PROFILE' }
    },
    aLastAction() {
      console.log("\n", 'actionCreators.aLastAction()', "\n")
      return { type: 'ANOTHER_ACTION' }
    }
}

var redux = createRedux(stores)
var fetchedProfileData = false

redux.subscribe(() => {
  console.log('SIMPLE REDUX SUBSCRIBER - connectStore:', redux.getState().connectStore)
  if (redux.getState().connectStore.logged) {
    if (!fetchedProfileData) {
      redux.dispatch(actionCreators.getProfileData())
      fetchedProfileData = true
    }
  }
})

redux.dispatch(actionCreators.connect())

setTimeout(() => { redux.dispatch(actionCreators.aLastAction()) }, 500)

Running this script using babel-node --stage=0 test.js produce the following output:

store.connectStore(), action received: state {} action { type: '@@INIT' }
store.profileStore(), action received: state {} action { type: '@@INIT' }

 actionCreators.connect()

store.connectStore(), action received: state {} action { type: 'CONNECT' }
store.profileStore(), action received: state {} action { type: 'CONNECT' }
SIMPLE REDUX SUBSCRIBER - connectStore: { logged: true }

 actionCreators.getProfileData()

store.connectStore(), action received: state {} action { type: 'PROFILE' }
store.profileStore(), action received: state {} action { type: 'PROFILE' }
SIMPLE REDUX SUBSCRIBER - connectStore: {}

 actionCreators.aLastAction()

store.connectStore(), action received: state { logged: true } action { type: 'ANOTHER_ACTION' }
store.profileStore(), action received: state {} action { type: 'ANOTHER_ACTION' }
SIMPLE REDUX SUBSCRIBER - connectStore: { logged: true }

What I interpret as a bug are the connectStore value being {} just after calling actionCreators.getProfileData().

...
SIMPLE REDUX SUBSCRIBER - connectStore: { logged: true }

 actionCreators.getProfileData()

store.connectStore(), action received: state {} action { type: 'PROFILE' }
store.profileStore(), action received: state {} action { type: 'PROFILE' }
SIMPLE REDUX SUBSCRIBER - connectStore: {}

...

My understanding of redux was that after having filled the connectStore with {logged: true}, a new action triggered should be applied on this new store value. But unfortunately I get an empty connectStore when calling actionCreators.getProfileData()...

I was expecting to have:

...
SIMPLE REDUX SUBSCRIBER - connectStore: { logged: true }

 actionCreators.getProfileData()

store.connectStore(), action received: state { logged: true } action { type: 'PROFILE' }
store.profileStore(), action received: state {} action { type: 'PROFILE' }
SIMPLE REDUX SUBSCRIBER - connectStore: {}

...

After some console.log in redux I found out that this behavior seems to be due to the state being kept in 2 different places:

  1. in createDispatcher.js via a closure on dispatch
  2. in Redux.js via this.state

so when calling dispatch(action) (in createDispatcher.js), if another action is triggered in a redux listener (in Redux.js), the second action does not benefit from the update that the first action triggered (because it was not written yet in closured state in createDispatcher.js). Here is some redux code above to explain what I mean.

    // createDispatcher.js
    var state = setState(store(initialState, INIT_ACTION)); // the closure state variable
    function dispatch(action) {
      // state = ... is only effective AFTER all listeners in Redux.setState have been called
      state = setState(store(state, action)); 
      return action;
    }

  // REDUX.js
  Redux.prototype.setState = function setState(nextState) {
    this.state = nextState; // the state property of Redux
    this.listeners.forEach(function (listener) {
      // if another action is called in 'listener', we'll end up with Redux.state != createDispatcher.state
      return listener();
    });
    return nextState;
  };

Maybe this is the normal behavior but I don't feel like calling an action from a listener is a wrong since it is pretty common when you realize that the listener may call a React component lifecycle method (thanks to @connect decorator).

Thanks!

@gaearon
Copy link
Contributor

gaearon commented Jul 30, 2015

This indeed is a bug. It was fixed in 1.0 RC.
1.0 will be released in a few days.

You can try 1.0 RC now: https://github.com/gaearon/redux/releases/tag/v1.0.0-rc

Related: #110, #119

@gaearon gaearon closed this as completed Jul 30, 2015
@gaearon gaearon added the bug label Jul 30, 2015
@happypoulp
Copy link
Author

Thanks @gaearon and sorry for the duplicate!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants