Skip to content

Instantly share code, notes, and snippets.

@brandonbloom
Last active August 29, 2015 14:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brandonbloom/56be8fcd4989f7396a1a to your computer and use it in GitHub Desktop.
Save brandonbloom/56be8fcd4989f7396a1a to your computer and use it in GitHub Desktop.
/** @jsx React.DOM */
var Bubbler = React.createClass({
render: function() {
return <input type="button" onClick={this.props.onFoo} value="Click me"/>;
}
});
var Handler = React.createClass({
onFoo: function() {
alert("handled!");
},
render: function() {
return <Bubbler onFoo={this.onFoo}/>;
}
});
React.renderComponent(<Handler/>, document.body);
/** @jsx React.DOM */
var Bubbler = React.createClass({
render: function() {
return <input type="button" onClick={this.props.onFoo} value="Click me"/>;
}
});
var Handler = React.createClass({
effects: {
foo: function() {
alert("handled!");
}
},
render: function() {
return <Bubbler onFoo={this.effects.foo}/>;
}
});
React.renderComponent(<Handler/>, document.body);
/** @jsx React.DOM */
var Bubbler = React.createClass({
render: function() {
return <input type="button" onClick={this.props.onFoo} value="Click me"/>;
}
});
var Handler = React.createClass({
handlers: function() {
return [
[this.props.eff, function() {
alert("handled!");
}]
];
},
render: function() {
return this.props.children;
}
});
var Wrapper = React.createClass({
effects: {
foo: newEffect()
},
render: function() {
return <Handler eff={this.effects.foo}><Bubbler onFoo={this.effects.foo}/></Handler>
}
});
React.renderComponent(<Wrapper/>, document.body);
@brandonbloom
Copy link
Author


[18:41:20]  <bbloom>     RE: https://twitter.com/jordwalke/status/490263345275346944 I have an idea... if petehunt or somebody else is around to chat abou tit
[18:42:14]  <@petehunt>  sort of
[18:42:22]  <@petehunt>  ifyou want to brain dump
[18:43:16]  <bbloom>     the idea is that a component can only raise a custom event if it has a unique ID for that event given to it by the parent
[18:43:29]  <bbloom>     and a parent component can only handle that event if it also has the ID
[18:43:55]  <bbloom>     the concept is similar to throw/catch where you catch a particular exception type
[18:44:03] -travis-ci-   [travis-ci] facebook/react#3635 (0.11-stable - d1e3e15 : Paul O’Shannessy): The build passed.
[18:44:03] -travis-ci-   [travis-ci] Change view : https://github.com/facebook/react/compare/0d5e5bee3c62^...d1e3e157c4f9
[18:44:03] -travis-ci-   [travis-ci] Build details : http://travis-ci.org/facebook/react/builds/30211133
[18:44:15]  <@petehunt>  interesting
[18:44:20]  <@petehunt>  how is that different than passing a callback
[18:44:37]  <bbloom>     it's subtle... but important
[18:44:45]  <@petehunt>  multiple catchers?
[18:44:51]  <@petehunt>  i think you can just wrap the callbacks at each level?
[18:45:03]  <bbloom>     multiple catchers is one thing, but more improtantly....
[18:45:45]  <bbloom>     you have to set up the callback "tree" to have the right shape
[18:45:54]  <bbloom>     usually you can skip large parts of the component tree & get the same result
[18:46:48]  <bbloom>     let's say you have some <Outer><Inner customEventHandle=123></Outer>
[18:47:01]  <bbloom>     and there's lots of stuff in the outer before it includes the inner
[18:47:06]  <bbloom>     then the event won't ever go to that stuff
[18:47:11]   jordjordjord (~jordjordj@mpk-nat-2.thefacebook.com) joined the channel.
[18:47:16]  <bbloom>     or rather the a callback wouldn't
[18:47:32]  <jordjordjord>   bbloom: What's the gist?
[18:47:38]  <bbloom>     ah, was wondering your handle
[18:48:49]  <jordjordjord>   I have no consistent handle
[18:48:52]  <bbloom>     heh
[18:48:56]  <jordjordjord>   I lurk - I could be anyone haha
[18:49:16]  <@petehunt>  im  only sort of here
[18:49:58]   sboudria_ (~sboudrias@2601:9:3480:11b9:b0a9:214f:5fa2:c716) joined the channel.
[18:50:15]  <bbloom>     it's hard to explain this idea, but it's pretty simple to implement/use -- it's also based on the solid theoretical foundation of unque exception/effect instances
[18:51:00]  <bbloom>     you basically have three operations: new, catch, and throw
[18:51:13]  <bbloom>     instead of catching/throwing types, you catch/throw instances
[18:51:25]   smurphy_ (~smurphy@17.114.47.6) joined the channel.
[18:51:44]  <bbloom>     and you use new to make sure nobody else can catch/throw that particular thing without being given the ID
[18:51:54]  <bbloom>     so rename catch/throw to handle/bubble
[18:51:54]   hareth (~kapit@ram75-1-78-192-223-109.fbxo.proxad.net) left IRC. (Quit: hareth)
[18:51:59]  <bbloom>     and the idea translates right over to UI
[18:53:28]  <jordjordjord>   I see.
[18:53:34]   rmosolgo (~rmosolgo@rrcs-24-43-253-238.west.biz.rr.com) joined the channel.
[18:54:02]  <jordjordjord>   How is this unique ID (that's an implementation detail) given to you as a user of a component.
[18:54:07]   wheeee (~Adium@cpe-72-226-71-247.nycap.res.rr.com) joined the channel.
[18:54:20]   sboudria_ (~sboudrias@2601:9:3480:11b9:b0a9:214f:5fa2:c716) left IRC. (Ping timeout: 240 seconds)
[18:54:28]  <bbloom>     the IDs are just like event names
[18:54:37]  <bbloom>     so if you have a custom event name foo, you'd just replace that with 1234
[18:54:40]  <bbloom>     but you want them encapsulated
[18:55:05]  <bbloom>     eg object identity, not value identity
[18:55:22]  <bbloom>     if i were prototyping this in clojure, i'd just use gensym ;-)
[18:55:58]   jessepollak (~jessepoll@204.28.115.126) left IRC. (Quit: My MacBook Pro has gone to sleep. ZZZzzz…)
[18:56:04]  <jordjordjord>   but how do I, as a user of <Panel /> be able to subscribe to the event names that <Panel /> broadcasts without being able to subscribe to the names of events in Panel's impl details
[18:56:05]   dariocravero (~dariocrav@62.218.17.95.dynamic.jazztel.es) left IRC.
[18:56:14]  <bbloom>     ah, yes, let me gist what a high-level API might look like
[18:56:17]  <jordjordjord>   Just asking for a potential API
[18:59:38]   cuadraman (~cuadraman@50-196-172-249-static.hfc.comcastbusiness.net) joined the channel.
[18:59:41]  <cuadraman>  hi
[19:00:38]   varioust (~varioust@cpe-76-84-8-145.neb.res.rr.com) left IRC.
[19:01:15]   marcopolo` (~marcopolo@204.28.125.223) joined the channel.
[19:02:19]   dtribble (~Lokrick@c-98-209-28-52.hsd1.mi.comcast.net) joined the channel.
[19:03:09]   sboudria_ (~sboudrias@2601:9:3480:11b9:45c8:db9:21e8:ea2f) joined the channel.
[19:03:23]  <jordjordjord>   Another reason why I really like explicitly passed callbacks, is that they can be statically typed.
[19:03:24] -GitHub21-    [react] jmandel opened pull request #1872: possessive "its" (master...patch-1) https://github.com/facebook/react/pull/1872
[19:03:49]  <bbloom>     jordjordjord: petehunt: https://gist.github.com/brandonbloom/56be8fcd4989f7396a1a
[19:03:52]  <bbloom>     the second file
[19:04:16]  <jordjordjord>   It's much harder to confirm that something only catches the events that *some* rendered descendent emits and has been somehow negotiated as acceptable to depend upon
[19:04:23]  <jordjordjord>   (statically that is)
[19:04:36]  <bbloom>     so, actually, it can be done, but it's somewhat active research
[19:04:42]  <bbloom>     it's called an "effect system"
[19:05:48]  <bbloom>     jordjordjord: anyway, that's just a sketch, but the concept is that this.effects.foo would return the ID, not the handler
[19:06:00]   marcopolo` (~marcopolo@204.28.125.223) left IRC. (Ping timeout: 255 seconds)
[19:06:16]  <bbloom>     this example is contrived and doesn't show the interesting bit: which is that bubbling would happen through multiple intermediate components
[19:06:17]  <jordjordjord>   Okay, but in this example, wouldn't it be as easy as explicitly passing the handler?
[19:06:22]  <bbloom>     yes
[19:06:42]   biscarch (~biscarch@50-1-123-26.dsl.dynamic.sonic.net) left IRC. (Quit: WeeChat 0.4.3)
[19:07:32]   sboudria_ (~sboudrias@2601:9:3480:11b9:45c8:db9:21e8:ea2f) left IRC. (Ping timeout: 240 seconds)
[19:07:48]   jordjord_ (jordjordjo@64.125.189.90) joined the channel.
[19:08:01]  <bbloom>     but another way to do this may not create the handler immediately
[19:08:05]  <jordjord_>  flaky wifi - Yeah, my observation was that if you fix the bad parts (implicitness) of any bubbling, it becomes explicit handling
[19:08:35]  <bbloom>     no, not quite
[19:08:40]  <jordjord_>  All the approaches I've seen that make the contract not leak impl details, becomes just about the same as explicit callbacks
[19:08:41]  <bbloom>     consider if the handler wasn't defined here
[19:08:49]  <jordjord_>  which is why I'd like to see an example where it's not
[19:08:51]  <bbloom>     let me make one other example
[19:08:54]  <jordjord_>  thanks!
[19:08:59]  <jordjord_>  (Sorry if my wifi cuts out again)
[19:09:53]  <bbloom>     argh, stupid javascript w/ it's string-only keys :-/ heh
[19:10:28]  <jordjord_>  ES(something) will allow expressions
[19:10:34]  <jordjord_>  so just assume they're supported ;)
[19:10:48]  <bbloom>     but doesn't have proper hash codes or equalities, so it might as well be useless ;-)
[19:10:56]   jordjordjord (~jordjordj@mpk-nat-2.thefacebook.com) left IRC. (Ping timeout: 255 seconds)
[19:12:05]   jordjordjord (~jordjordj@2620:0:1cfe:18:6016:5c1f:7c58:9199) joined the channel.
[19:13:13]   jordjordjord (~jordjordj@2620:0:1cfe:18:6016:5c1f:7c58:9199) left IRC. (Remote host closed the connection)
[19:13:50] -GitHub19-    [react] zpao closed pull request #1869: getModifierState is case sensitive (master...modistatecase) https://github.com/facebook/react/pull/1869
[19:13:50] -GitHub115-   [react] zpao pushed 2 new commits to master: https://github.com/facebook/react/compare/f367785a7879...8439deadd026
[19:13:50] -GitHub115-   react/master 495d866 Andreas Svensson: getModifierState is case sensitive
[19:13:50] -GitHub115-   react/master 8439dea Paul O’Shannessy: Merge pull request #1869 from syranide/modistatecase...
[19:14:04]  <bbloom>     jordjord_: ok, last file here: https://gist.github.com/brandonbloom/56be8fcd4989f7396a1a
[19:14:26]  <bbloom>     argh, my bad, one sec
[19:14:59]   jordjordjord (~jordjordj@mpk-nat-2.thefacebook.com) joined the channel.
[19:14:59]   jordjord_ (jordjordjo@64.125.189.90) left IRC. (Ping timeout: 255 seconds)
[19:15:05]  <bbloom>     https://gist.github.com/brandonbloom/56be8fcd4989f7396a1a
[19:15:09]  <bbloom>     that's close enough
[19:15:28]  <bbloom>     the interesting bit is that wrapper creates a handler and a bubbler, but handler provides the handler function
[19:15:36]   kaos|work (~ddb@p5B299A7E.dip0.t-ipconnect.de) left IRC. (Quit: kaos|work)
[19:15:47]  <bbloom>     bubbler is given a capability object it can use to communicate with it's ancestor chain, w/o having a direct pointer to a parent
[19:15:51]   sboudria_ (~sboudrias@2601:9:3480:11b9:a9e2:de8b:8656:2cf7) joined the channel.
[19:16:01]  <bbloom>     or any ancestor for that matter
[19:16:01]   kassens (~kassens@mpk-nat-6.thefacebook.com) left IRC. (Quit: My MacBook Pro has gone to sleep. ZZZzzz…)
[19:16:23]  <bbloom>     obviously would require some API tuning, but that's the general concept
[19:17:03]  <bbloom>     preserves encapsulation, but you can also intentionally violate encapsulation at the framework level, if you wanted to do a handle-all or something for debugging purposes, to see all the events coming out of a sub tree
[19:17:05]   jordjordjord (~jordjordj@mpk-nat-2.thefacebook.com) left IRC. (Read error: Connection reset by peer)
[19:17:33]   jordjordjord (~jordjordj@mpk-nat-2.thefacebook.com) joined the channel.
[19:17:39]   johann (~johann@70-36-143-38.dsl.dynamic.sonic.net) joined the channel.
[19:19:11]   onr (~onr@pdpc/supporter/active/onr) left the channel.
[19:19:17]   kunalm (~kunalm@mpk-nat-3.thefacebook.com) left IRC. (Quit: My MacBook Pro has gone to sleep. ZZZzzz…)
[19:19:19]   fiatjaf (~fiatjaf@189-83-154-13.user.veloxzone.com.br) joined the channel.
[19:19:42]   andreio (~andreio@2620:101:80fb:224:84db:6127:f2bf:e9e9) left IRC. (Quit: Leaving.)
[19:19:43]   jordjordjord (~jordjordj@mpk-nat-2.thefacebook.com) left IRC. (Remote host closed the connection)
[19:19:56]   sboudria_ (~sboudrias@2601:9:3480:11b9:a9e2:de8b:8656:2cf7) left IRC. (Ping timeout: 240 seconds)
[19:20:48]   jordjordjord (~jordjordj@2620:0:1cfe:18:6016:5c1f:7c58:9199) joined the channel.
[19:20:58]  <bbloom>     jordjordjord: you're back. do you have logs? :-)
[19:21:22]   petehunt (~petehunt@mpk-nat-4.thefacebook.com) left IRC. (Quit: petehunt)
[19:21:42]   petehunt (~petehunt@mpk-nat-4.thefacebook.com) joined the channel.
[19:21:42] ChanServ sets mode +o petehunt
[19:21:44]  <rcs>    bbloom: Is there a specific example use case? I know you can go a long way by modeling any 'far reaching' changes pretty effectively with a combination of component state and changing a store on the outside that ends up getting passed down as props.
[19:22:42]   bmac (~bmac@gateway/shell/bocoup/x-vbnvpmysgfemizqi) left IRC. (Quit: My MacBook Pro has gone to sleep. ZZZzzz…)
[19:23:02]   andreio (~andreio@2620:101:80fb:224:a4c5:87a:e88f:c46d) joined the channel.
[19:24:03]   baranaby (uid36081@gateway/web/irccloud.com/x-taekzgcdvtmuyfum) left IRC. (Quit: Connection closed for inactivity)
[19:24:08]  <bbloom>     rcs: i don't have a particular use case in mind, but the general area where this is useful is when you want to abstract over events... that is you want reusable components that provide some event handling behavior or, at least, don't inhibit their contained components from providing event handling behavior
[19:24:10]   sboudria_ (~sboudrias@2601:9:3480:11b9:4553:958b:6443:ab95) joined the channel.
[19:24:23]  <@petehunt>  so you are passing an event emitter around?
[19:24:44]   jordjordjord (~jordjordj@2620:0:1cfe:18:6016:5c1f:7c58:9199) left IRC. (Ping timeout: 240 seconds)
[19:25:07]   andreio (~andreio@2620:101:80fb:224:a4c5:87a:e88f:c46d) left IRC. (Client Quit)
[19:25:15]  <bbloom>     i guess you can look at it that way, but the thing you're passing around is just an identifier, you'd probably have something like this.bubble(thatId, {payload: "here"})
[19:25:30]  <bbloom>     and onClick was doing that automatically in my example
[19:26:05]  <bbloom>     you ever deal with checked exceptions in java?
[19:26:10]  <bbloom>     that's the current state of event handling in react
[19:26:15]   sboudri__ (~sboudrias@2601:9:3480:11b9:b9a6:c3ee:a818:cf08) joined the channel.
[19:26:20]  <bbloom>     if anybody wants to let an event pass through them, they have to explicitly thread callbacks
[19:26:27]  <bbloom>     even if they don't care about that event
[19:26:47]  <bbloom>     or, they have to accept content already configured with handlers
[19:27:19]   vjeux (~vjeux@mpk-nat-4.thefacebook.com) left IRC. (Remote host closed the connection)
[19:27:22]  <bbloom>     i haven't looked, but i imagine that the synthetic event system is already doing something very similar to this
[19:28:20]   sboudria_ (~sboudrias@2601:9:3480:11b9:4553:958b:6443:ab95) left IRC. (Ping timeout: 240 seconds)
[19:29:11]  <bbloom>     may be worth looking at http://math.andrej.com/2012/03/08/programming-with-algebraic-effects-and-handlers/ (and a talk i gave about it here: http://www.mixcloud.com/paperswelove/bbloom_3_17_2014_programming_with_alegebraic_effectshandlers/ )
[19:29:42]  <bbloom>     seems very unrelated at first, but if you think about it for a while, it will be clear what the connection is
[19:29:53]  <bbloom>     anyway, just an idea
[19:30:12]  <bbloom>     might be worth thinking about places people have wanted custom event bubbling, and see if this idea would meet your goals
[19:30:20]   sboudri__ (~sboudrias@2601:9:3480:11b9:b9a6:c3ee:a818:cf08) left IRC. (Ping timeout: 240 seconds)
[19:30:24]  <bbloom>     gotta run now, but happy to chat about it more later

@DavidBruant
Copy link

Context

Sharing some context about my initial tweet. I'll answer some points about the gist afterwards.

I'll probably say things that seem obvious in this section, but that'll explain my thought process.

Events are an interesting concept as they allow external parts of a program to independently listen to what's happening to a given object whenever they want (and stop listening to the object events, whenever they want to as well).

"independently" is important, because it helps decoupling. Different components don't need to coordinate which reduces opportunity for them to interfere with one another. (Note that the DOM event model is terrible, because listeners can interfere with one another via event.stop(Immediate)Propagation. Anyway...). This is akin to different objects having a reference to a common object can independently call its methods.

"whenever they want" is important too. There is no reason to be forced to listen all the time from the beginning of a component life, nor no reason to listen until the end. For instance, everyone should remove their DOMContentLoaded listener since this event will only ever happen once during the entire document lifetime. Any other call to this listener would be a mistake (but very few people do).
In an application, it also happened to me recently to reimplement some sort of range picker. I would add an event listener to mousemove only after a mousedown on the element and remove the listener on mouseup.
Both of these examples are fairly impossible to implement in React as it is. The only way is to pass a callback unconditionally and make that function a noop based on some condition. I'd rather stop listening when relevant.

React lacks events in the sense that only one listener can be set to a component (via this.props. Why aren't props explicitely passed as an argument to render by the way? Anyway, question for another time) and only at component creation time. Several objects which want to listen to a particular event can, but they have to coordinate with the parent which will need to expose a way to add/remove event listeners, take care of all that internally so the callback passed to the child component does call the right set of callbacks. Not impossible, but burdensome.

On the proposal

I'm not sure the proposal solves the problem of multiple independant listeners, nor the "I want to listen whenever I want" constraint.

I understand the motivation behind how React is currently modeled.
However, I wonder if these motivation make completely impossible to have a (non-bubbling) event system with independent listeners that can be added and removed at any time.

@n1k0
Copy link

n1k0 commented Jul 20, 2014

I would add an event listener to mousemove only after a mousedown on the element and remove the listener on mouseup

Sounds complicated… Is this for performance reason? Anyway you're right, there's no obvious way to achieve this using event listeners passed as attributes in React. I'm just challenging the idea of this need being mainstream enough for the team to work on this… Honestly, I don't have a clue.

@DavidBruant
Copy link

I ended up implementing the range thing I was talking about https://davidbruant.github.io/inline-range-demo/ Click on the number and drag left and right to increase/decrease the value.
It can be implemented in React, but in a way that is more elegant in my opinion, because of the lack of a proper event system.

@n1k0
Copy link

n1k0 commented Jul 20, 2014

http://jsbin.com/vapay/4/edit

It's really matter of calling the handleMouseMove method and testing for this.state.dragging; it's probably less performent, but not that much I think (though I'm not sure). This could probably be microbenchmarked, oh well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment