-
Notifications
You must be signed in to change notification settings - Fork 54
fix(eventStack): make subscription logic to be always async #391
Conversation
Codecov Report
@@ Coverage Diff @@
## master #391 +/- ##
==========================================
- Coverage 91.72% 91.64% -0.08%
==========================================
Files 41 41
Lines 1341 1341
Branches 197 172 -25
==========================================
- Hits 1230 1229 -1
- Misses 107 108 +1
Partials 4 4
Continue to review full report at Codecov.
|
The declarative way on {this.state.open && <EventStack name="click" on={this.handleClick} target="window" />} EventStack was cutted from SUIR core to separate package to make sure that it works fine in IE11 :) It also was rewritten to TypeScript and published as I think that makes a sense to move it to Stardust org, publish as |
@layershifter, thanks for you reply. Agree with the points mentioned, specifically:
One thing that I would like to mention is mostly relates to the first point - while, definitely, EventStack in a form of React component will solve the issue, at the same time, given that Also, let me mention couple of other points that we are going to introduce short term fix for, as those are quite dangerous in a sense of opened possibilities for accidental mistakes: Case 1eventStack.sub('focus', this._handleOutsideFocus, { target: window })
...
// nothing is unsubscribed, by default `document` is used as a target
eventStack.unsub('focus', e => this._handleOutsideFocus(e)) Case 2eventStack.sub('focus', e => this._handleOutsideFocus(e))
...
// oops, nothing is unsubscribed
eventStack.unsub('focus', e => this._handleOutsideFocus(e)) Case 3private _handleOutsideFocus(e) {
...
// oops, I am not 'this' here (thoughts of the type's instance)
this.handle(e)
}
...
eventStack.sub('focus', this._handleOutsideFocus) Please, let me know what do you think and, most important, whether we could go for now with this set of changes, as it will unblock the possibility to introduce long-awaited functionality for the |
Just chiming in support for @layershifter's comment as well. The plan is to make Stardust a monorepo with a "core" package, essentially the /lib. In addition, full support to @kuzhelov's point regarding the declarative React component vs the JS API: "even on JS level it will provide expected behavior for the most cases and will expose only the safe API." I don't see Cases 1, 2, and 3 as valid problems against EventStack, however. These are true of web development in general, you must pass handlers by reference and properly use context bindings. This is how the DOM APIs work. I don't think we should try to solve some of these problems at the EventStack layer. We should not be too concerned with problems that do not originate from using Stardust. I'm also slightly concerned about "always async". I'd rather we accomplish "always SYNC". That said, I have not reviewed the details of the code here. Just expressing what I believe would be a more optimal direction, if possible. |
let me, please, respond to the points raised:
There is no other direction for us to implement Popup's logic reliably, so that there won't be quirky interactions with other components that are
This won't be possible to do in theory, as some of the calls to event stack result in
I don't see them as problems against EventStack either - but the thing is that we should strive to expose safe API to the client. I cannot even stress enough how often cases 2 and 3 drives devs in confusion on our side, and even those who are pretty much aware of arrow functions - even those might think about some refactoring that will make this to be a regular function, while accidentally forgetting the context of // either of these forms will work correctly - in contrast to the original approach:
const eventSubscription = EventStackListener.create('click', () => { .. some handling logic .. })
const eventSubscription = EventStackListener.create('click', this.handle.bind(this))
// please, note that now we are SAFE to modify this function -
// with this being arrow one, regular one everything will work
handle() { .. }
...
// and when we are done
eventSubscription.stop() Please, note that this API is much safer to use. I would strongly ask to consider to merge this or provide appealing reasons why we shouldn't do that - because this functionality is a basis for the forthcoming one of Popup. @levithomason, @layershifter , looking forward for your thoughts on that |
Thanks for clarification, I agree and support all these points 👍 |
TODO
This PR aims to fix the source of potential problems (that are very time consuming to debug) that might occur to
eventStack
clients - by making its subscription logic to always make async effect.Problem description
Lets take a
Popup
component as an example, and its the following behavioural aspect: it should close each time when there is a click outside the popup content.One might consider to implement this in a following way for the Popup:
Dev's idea here is that with Popup being opened, each click event that bubbles up to
document
level and is not processed should cause popup to close.The problem arises when there is another, lets say, component on the page that has subscribed to
document's
event viaeventStack
API earlier. In that case client, actually, will never see the popup in open state being rendered!The Cause
Root cause of the problem is that
eventStack
is not explicit about how subscriptions are handled - those could be handled either within the same JS frame, or implicitly delayed till the next one. The reason for this difference is the following snippet of theEventTarget
logic (executes on subscription):Solution
Solution is to make the logic consistent in terms of how subscription effect is produced - by making it always being async.