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

Use change-emitter module to handle subscriptions #1729

Closed
wants to merge 1 commit into from

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented May 15, 2016

createStore includes a really handy, bare-bones event subscription implementation that I found myself wanting in a few other projects. (E.g. here: acdlite/recompose#160). So I extracted it out into a separate module: https://github.com/acdlite/change-emitter

When I tweeted about it, @tappleby pointed me to this issue (which, by coincidence, was closed just three hours ago :D) tappleby/redux-batched-subscribe#8.

This PR rewrites createStore to use change-emitter. Not sure if it's something we want to do but I figured we should discuss it.

At the very least, I think it would be valuable to extract the subscription stuff into a separate file, even if it stays within the Redux project.

@acdlite
Copy link
Collaborator Author

acdlite commented May 15, 2016

The obvious disadvantage is that if we have subscription-related bugs, they'll have to be resolved in a separate project.

Potential upside is that if the package gets more libraries to use it (history, perhaps), we'll all benefit from its maintenance.

@splendido
Copy link

for an example of something that should be addressed within this package see #1626

@gaearon
Copy link
Contributor

gaearon commented May 18, 2016

I think I’d be confident extracting it if we define a strict contract that it promises to satisfy. There is a thousand ways to implement a change emitter, and some constraints that Redux cares about might not be generally applicable so it’s easy to imagine them being changed as implementation details of a module like this, potentially leading to breakage in Redux. If, on the contrary, we keep the tests in Redux to prevent problems like this, I don’t see much of a point of keeping that code outside, as this increases rather than decreases the maintenance burden.

@acdlite
Copy link
Collaborator Author

acdlite commented May 19, 2016

@gaearon

I think I’d be confident extracting it if we define a strict contract that it promises to satisfy.

Agreed.

There is a thousand ways to implement a change emitter, and some constraints that Redux cares about might not be generally applicable

Could you elaborate? I agree with the statement but I'm having trouble putting the contract that Redux follows into words. As far as I can tell, the important bits are

  1. subscribing or unsubscribing as the result of a change should not affect which listeners are called for that change.
  2. a nested change (change emitted as the result of another change) should be sent to the most recent list of listeners, taking into account any listeners were added or removed during the current change (point 1).

Is there anything else I'm missing?

@gaearon
Copy link
Contributor

gaearon commented May 19, 2016

I think so but I forget these rules all the time. The important part is to take a naïve connect implementation like

mount() {
  this.mostRecentState = store.getState()
  this.unsubscribe = store.subscribe(this.handleChange.bind(this))
}

unmount() {
  this.unsubscribe()
  this.unsubscribe = null
}

handleChange() {
  if (this.unsubscribe) {
    this.mostRecentState = store.getState()
  }
}

and make sure that this.mostRecentState always corresponds to the most recent state by the time the outermost dispatch exits.

@acdlite
Copy link
Collaborator Author

acdlite commented May 19, 2016

I think so but I forget these rules all the time

Haha me too. I think regardless of whether we extract it, we need to do a better job documenting this contract, either via better tests or an actual document. I'll look into this sometime soon.

@gaearon
Copy link
Contributor

gaearon commented May 19, 2016

We have some clarifications in createStore doc (they were fixed once) but I’m not 100% sure they’re correct. I think #1729 (comment) is the best starting point; whatever contract I imagined was influenced only by getting this invariant to be true in all cases: all connected components receive the latest state by the time we exit dispatch. The components that just subscribed don't need to be notified because they read the state synchronously, but if the state changed because of nested dispatch again, we should call them as well. (In fact I'm not quite sure we're handling this situation correctly right now.)

@acdlite
Copy link
Collaborator Author

acdlite commented May 19, 2016

all connected components receive the latest state by the time we exit dispatch

Yeah this seems like the most important part, and yet we don't have a test for it :D The only thing we currently test is how often listeners are called, not what the state is at that time.

@gaearon
Copy link
Contributor

gaearon commented May 19, 2016

We should rewrite the tests. 👍

@gaearon
Copy link
Contributor

gaearon commented May 19, 2016

(With a fake naïve connect() rather than the real one)

@timdorr
Copy link
Member

timdorr commented Sep 4, 2016

Hey @acdlite, any updates on this? This will also be helpful in React Redux, so this could kill a lot of birds with one stone 👍

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