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

Gracefully handle nested dispatches #110

Closed
wants to merge 1 commit into from

Conversation

leoasis
Copy link
Contributor

@leoasis leoasis commented Jun 16, 2015

Currently, when we happen to have nested dispatches, the last action's returned state prevails, even if they belong to different stores. The nested dispatch still sees the state as if the outer dispatch wasn't executed yet, so it ends up overwriting whatever that outer dispatch did.

Perhaps it's more clear with the failing test that I'm providing here. I'd expect there to either have both dispatches applied, or the dispatcher to throw an exception or a warning, as the original Facebook's Flux dispatcher.

The use case for running into nested dispatches is the classic "need data in componentWillMount", which triggers an action creator that dispatches an action immediately, and is executed as a consequence of a previous dispatch.

I'm leaving this unsolved until we decide the way to go, after that I can fix the test with the expected behavior

@gaearon
Copy link
Contributor

gaearon commented Jun 16, 2015

Thanks for the failing test case. Best way to start a conversation :-)

The use case for running into nested dispatches is the classic "need data in componentWillMount"

Yes. Let's solve this somehow.

I'm currently busy with time travel but I'm sure @acdlite, @johanneslumpe, @goatslacker could provide valuable insight from their experience building Flux libraries (even on top of FB's dispatcher)..

@johanneslumpe
Copy link
Contributor

I do not have a solution for this now but I remember that nested dispatches are something that's difficult to handle on the server-side. Let's say we have the classic react-router example where we want to fetch some data prior to rendering. We added some static handlers to our container components, and inject redux etc etc. to fetch some data. Let's use a promise middleware too. It returns the promise from dispatch so we can wait for all async actions to be completed prior to rendering. If we now have dispatches in componentWillMount which might be async as well, then those would not be caught and we would render our application with possibly half the state we'd need/want.

@gaearon
Copy link
Contributor

gaearon commented Jun 17, 2015

We could do this:

  • Throw on nested dispatch just like FB dispatcher.
  • Let user specify a custom “update scheduler” function to createRedux. They can use it to wrap the dispatch in React.addons.batchedUpdates (Strategy for avoiding cascading renders #125) -or- to schedule all updates inside requestAnimationFrame. This way dispatching from lifecycle hooks won't be an issue.

Am I making sense?

@johanneslumpe
Copy link
Contributor

I think we should probably just throw for now, so that a user at least knows what's going on and isn't stumped by the fact that the state isn't what they expected.

I do like the the requestAnimationFrame idea too. The question is whether it is a good thing to allow that, because those applications would most likely just explode once somebody tries to change to a "normal" scheduler again. But then again, if you do that you most likely know what you're in for.

@leoasis
Copy link
Contributor Author

leoasis commented Jun 17, 2015

I think the problem with the last action overriding the entire atom of the first dispatch gets fixed with #119, since the problem is that the dispatcher is using its internal state to update, which is set after setting the state in the redux instance's state and calling the listeners.

I'm saying this because after that PR gets merged, the nested dispatches would set the whole state as expected, so it could be an option for desired behavior.

Having said that, I'm not sure if this would cause any edge cases, or if it will increase the mental model both on developing redux (taking into account supporting nested dispatches), and in userland.

@leoasis
Copy link
Contributor Author

leoasis commented Jun 17, 2015

@gaearon @johanneslumpe just to let you know that it seems rAF may have further issues: facebook/react#3570. In any case, that would be an optimization or a strategy to be decided by the user

@gaearon
Copy link
Contributor

gaearon commented Jun 17, 2015

I think we should probably just throw for now, so that a user at least knows what's going on and isn't stumped by the fact that the state isn't what they expected.

@leoasis Would you like to implement this as part of this PR?

@leoasis
Copy link
Contributor Author

leoasis commented Jun 17, 2015

@gaearon yes, sure! Have you considered what I said in this previous comment? Just to make sure we don't want to go with that route

@gaearon
Copy link
Contributor

gaearon commented Jun 17, 2015

@leoasis

Oh, I haven't. Hmm.

@acdlite Any opinion here?

acdlite added a commit that referenced this pull request Jun 17, 2015
@acdlite
Copy link
Collaborator

acdlite commented Jun 17, 2015

This is actually caused by a subtle bug in the current version where setState() triggers the subscribers before assigning to the dispatcher's internal copy of the current state. https://github.com/gaearon/redux/blob/master/src/createDispatcher.js#L9

This is kind of bug that #119 is intended to avoid by moving all state into the Redux instance. I've added the test to that PR to show that it passed. (Modified slightly — the original version causes an infinite loop because this line is always true: leoasis@e23b56b#diff-2dce452f5f745e87ca49080c71d81a21R87.)

See the passing test here: b71a7fc

@acdlite
Copy link
Collaborator

acdlite commented Jun 17, 2015

So long story short, nested dispatches in Redux aren't a problem like they are in other libraries, because our "dispatcher" is just a reduce operation.

@acdlite acdlite mentioned this pull request Jun 17, 2015
@leoasis
Copy link
Contributor Author

leoasis commented Jun 17, 2015

@acdlite awesome, yeah that's what I thought in #110 (comment), so let's keep nested dispatches supported and fix that bug in your PR!

Only thing curious about why you had an infinite loop in my test, since that was not happening without the fix. Just asking since it may be a hint of another bug perhaps

Edit: disregard the last paragraph, I see why it was causing an infinite loop now

@acdlite
Copy link
Collaborator

acdlite commented Jun 17, 2015

@leoasis The infinite loop never kicked off in the original case for the same reason the test failed: the call to getState() inside the subscriber returned the old (original) state, so the nested dispatch was never fired.

@leoasis
Copy link
Contributor Author

leoasis commented Jun 17, 2015

@acdlite yeah, realised that right after sending the comment :P

@acdlite
Copy link
Collaborator

acdlite commented Jun 17, 2015

@leoasis :)

@leoasis
Copy link
Contributor Author

leoasis commented Jun 17, 2015

Perfect then, since this will be fixed in #119, I'm closing this one

@leoasis leoasis closed this Jun 17, 2015
gaearon added a commit that referenced this pull request Jun 30, 2015
Naming:

* “Stateless Stores” are now called reducers. (#137 (comment))
* The “Redux instance” is now called “The Store”. (#137 (comment))
* The dispatcher is removed completely. (#166 (comment))

API changes:

* <s>`composeStores`</s> is now `composeReducers`.
* <s>`createDispatcher`</s> is gone.
* <s>`createRedux`</s> is now `createStore`.
* `<Provider>` now accepts `store` prop instead of <s>`redux`</s>.
* The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`.
* If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it.
* The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: #113 (comment).

Correctness changes:

* The `dispatch` provided by the default thunk middleware now walks the whole middleware chain.
* It is enforced now that raw Actions at the end of the middleware chain have to be plain objects.
* Nested dispatches are now handled gracefully. (#110)

Internal changes:

* The object in React context is renamed from <s>`redux`</s> to `store`.
* Some tests are rewritten for clarity, focus and edge cases.
* Redux in examples is now aliased to the source code for easier work on master.
gaearon added a commit that referenced this pull request Jun 30, 2015
Naming:

* “Stateless Stores” are now called reducers. (#137 (comment))
* The “Redux instance” is now called “The Store”. (#137 (comment))
* The dispatcher is removed completely. (#166 (comment))

API changes:

* <s>`composeStores`</s> is now `composeReducers`.
* <s>`createDispatcher`</s> is gone.
* <s>`createRedux`</s> is now `createStore`.
* `<Provider>` now accepts `store` prop instead of <s>`redux`</s>.
* The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`.
* If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it.
* The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: #113 (comment).

Correctness changes:

* The `dispatch` provided by the default thunk middleware now walks the whole middleware chain.
* It is enforced now that raw Actions at the end of the middleware chain have to be plain objects.
* Nested dispatches are now handled gracefully. (#110)

Internal changes:

* The object in React context is renamed from <s>`redux`</s> to `store`.
* Some tests are rewritten for clarity, focus and edge cases.
* Redux in examples is now aliased to the source code for easier work on master.
pandafulmanda pushed a commit to pandafulmanda/testing-example that referenced this pull request Aug 2, 2018
Naming:

* “Stateless Stores” are now called reducers. (reduxjs/redux#137 (comment))
* The “Redux instance” is now called “The Store”. (reduxjs/redux#137 (comment))
* The dispatcher is removed completely. (reduxjs/redux#166 (comment))

API changes:

* <s>`composeStores`</s> is now `composeReducers`.
* <s>`createDispatcher`</s> is gone.
* <s>`createRedux`</s> is now `createStore`.
* `<Provider>` now accepts `store` prop instead of <s>`redux`</s>.
* The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`.
* If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it.
* The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: reduxjs/redux#113 (comment).

Correctness changes:

* The `dispatch` provided by the default thunk middleware now walks the whole middleware chain.
* It is enforced now that raw Actions at the end of the middleware chain have to be plain objects.
* Nested dispatches are now handled gracefully. (reduxjs/redux#110)

Internal changes:

* The object in React context is renamed from <s>`redux`</s> to `store`.
* Some tests are rewritten for clarity, focus and edge cases.
* Redux in examples is now aliased to the source code for easier work on master.
timdorr pushed a commit to reduxjs/examples that referenced this pull request Sep 10, 2019
Naming:

* “Stateless Stores” are now called reducers. (reduxjs/redux#137 (comment))
* The “Redux instance” is now called “The Store”. (reduxjs/redux#137 (comment))
* The dispatcher is removed completely. (reduxjs/redux#166 (comment))

API changes:

* <s>`composeStores`</s> is now `composeReducers`.
* <s>`createDispatcher`</s> is gone.
* <s>`createRedux`</s> is now `createStore`.
* `<Provider>` now accepts `store` prop instead of <s>`redux`</s>.
* The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`.
* If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it.
* The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: reduxjs/redux#113 (comment).

Correctness changes:

* The `dispatch` provided by the default thunk middleware now walks the whole middleware chain.
* It is enforced now that raw Actions at the end of the middleware chain have to be plain objects.
* Nested dispatches are now handled gracefully. (reduxjs/redux#110)

Internal changes:

* The object in React context is renamed from <s>`redux`</s> to `store`.
* Some tests are rewritten for clarity, focus and edge cases.
* Redux in examples is now aliased to the source code for easier work on master.
lovelypuppy0607 added a commit to lovelypuppy0607/redux that referenced this pull request May 11, 2023
Naming:

* “Stateless Stores” are now called reducers. (reduxjs/redux#137 (comment))
* The “Redux instance” is now called “The Store”. (reduxjs/redux#137 (comment))
* The dispatcher is removed completely. (reduxjs/redux#166 (comment))

API changes:

* <s>`composeStores`</s> is now `composeReducers`.
* <s>`createDispatcher`</s> is gone.
* <s>`createRedux`</s> is now `createStore`.
* `<Provider>` now accepts `store` prop instead of <s>`redux`</s>.
* The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`.
* If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it.
* The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: reduxjs/redux#113 (comment).

Correctness changes:

* The `dispatch` provided by the default thunk middleware now walks the whole middleware chain.
* It is enforced now that raw Actions at the end of the middleware chain have to be plain objects.
* Nested dispatches are now handled gracefully. (reduxjs/redux#110)

Internal changes:

* The object in React context is renamed from <s>`redux`</s> to `store`.
* Some tests are rewritten for clarity, focus and edge cases.
* Redux in examples is now aliased to the source code for easier work on master.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants