Closed FunkMonkey closed 7 years ago
Good suggestion. However, doing that means we have to either ignore the changes to event listener props after mounted, or handle the subscription and disposal with componentWillReceiveProps
. The former is probably not a good idea. And the later will probably bring more overheads than we saved from this optimization.
I think I need some help here. We can put if (this.props[eventName])
before https://github.com/pH200/cycle-react/blob/master/src/create-react-class.js#L38 . However, I don't really have a good idea how to fix the problems I mentioned.
You can also check ES6 version by checking out branch template-component.
I see what you are saying. I believe, there are actually two different use-cases for cycle-react components:
The current cycle-react implementation serves the first use-case very well. Prop updates are internally rewired to Observables, custom event updates coming from observables can be listened to using normal callback functions.
For the second use-case I would actually prefer to interact with the cycle-react component in a purely reactive way. This means the following things:
However, doing that means we have to either ignore the changes to event listener props after mounted
In this second use-case I believe there is no necessity to ever update the props of the component from the outside after passing in the initial props. Observables are passed in for the input and thus don't need updating. Also in a reactive world I dont' really see a point in changing the event listener prop - necessary behaviour changes can be handled within the chain of Observables.
I am not really sure of the best way to implement this. Should the cycle-react API provide two types of components or should this be handled internally? If we have two types of components, we could certainly share a lot of the code...
When handling it internally, I see the following changes:
props.get
or even better: have them as members on the props
objectinteractions.toObservable
as an alternative for interactions.listener
and interactions.getObservable
as an alternative to interactions.get
getObservable
will create a Subject for the given name (if not yet created) and return subject.flatMap( x => x)
. We may need a replay feature here.toObservable
returns a function that, when called during _subscribeCycleEvents
will push the according custom event observable (which it received as an argument) into the subject created by getObservable
. The custom event will thus only be subscribed to, when the result of getObservable
was subscribed to. The function object returned from toObservable
must have a special js property to differentiate the function from a normal callback function (like the ones created by interactions.listener
).componentWillReceiveProps
is calledWhat do you think? Do you agree with those two use-cases and if so, how would you implement it: additional API or internal changes?
I am also not quite sure, where you are going with the template components... Could you please elaborate?
First of all, thank you very much for your valuable input. And sorry I didn't respond immediately, I was thinking about what's the best solution. I agree with both use-cases. In fact, I think what makes cycle-react different from other libraries is the way we handle events, by separating events from PropsSubject.
That being said, although I think we should probably change the name interactions.listener
to effects.dispatch
, store.dispatch
or source.emit
, I think overall this API design is in a good shape. Because, I don't think one should put observable into props (or event prop), that is the source where component fires the event. It should be a source of observable, not where we subscribe the observable.
For example, I think this is a good design and close to what we're doing right now https://github.com/Day8/re-frame/blob/master/examples/simple/src/simple/core.cljs#L67-L73. store.dispatch
from redux is the similar idea, too.
I think there are two actions we can take:
cycle-react
, like cyclejs
, can be isolated by component. On the other hand, redux
is more like an application framework (correct me if I am wrong). I think people are more likely to use cycle-react
to build application, instead of making components which is portable. Providing a way to handle the events for the whole application could be helpful, it saves us the trouble of passing events layers by layers throughout the components.Template component is my experiment of separating view out of observables. The name "template component" is not very good, so I will change that. However, I think it's a better way to write components.
sorry I didn't respond immediately, I was thinking about what's the best solution.
No problem. I prefer the thinking-approach ;)
Because, I don't think one should put observable into props (or event prop), that is the source where component fires the event. It should be a source of observable, not where we subscribe the observable.
Just to prevent a misunderstanding: I did not mean that cycle-react
should subscribe to the Observables passed in as props, but only forward them to the definition function directly. Coming from Cycle.js
, I like the approach of sources
and sinks
as input/output, which my proposal for the second use-case of components living in a reactive world is half based on. As props are the main communication layer for both component's inputs and outputs in React, they would have to serve for passing in sources and retrieving sinks. Maybe I should give a (stupid) example of how I imagine a solution for the second use-case could look like:
const Component = Cycle.component( 'Component', ( interactions, props ) => {
// props.count and props.text are exactly the observables that where passed in
const view = Rx.Observable.combineLatest( props.count, props.text,
( count, text ) => <div> {text}:{count} </div> );
const someEvent = Rx.Observable.interval( 100 );
return { view, someEvent };
} );
function someFunction()
const sinks = Cycle.sinks();
const count = Rx.Observable.interval( 1000 );
const text = Rx.Observable.just( 'foo' );
const someEventSink = sinks.get( 'someEventName' );
const subscription = someEventSink.subscribe( ev => console.log( 'Some event happened' );
return (
<Component
count={count} text={text} /* sources */
someEvent={sinks.forward( 'someEventName' )} /* sinks */
/>
);
}
I introduced Cycle.sinks
simply to be independent of interactions
as in the given example there is no outer component that could provide interactions
. Its purpose is to connect the cycle (using a Subject), so events can be declared beforehand. Maybe sinks
isn't the appropriate name though...
As I said - this would only be my imaginary solution for the second use-case. It also aims at a minimum alteration of the observables within cycle-react
. Do you think this React-adapted approach of sources and sinks is a suitable idea?
I think people are more likely to use cycle-react to build application, instead of making components which is portable.
I can imagine people doing both, though I personally am also more interested in using cycle-react
for building applications.
Providing a way to handle the events for the whole application could be helpful, it saves us the trouble of passing events layers by layers throughout the components.
I see the point, but I personally disagree. I prefer the Cycle-approach of not having a centralized store or event-system. Though no one is stopping anyone from using redux or even redux-rx
/ rx-redux
in combination with cycle-react
.
Template component is my experiment of separating view out of observables. The name "template component" is not very good, so I will change that. However, I think it's a better way to write components.
I think Frint
is trying something similar with Observed component
(here's the code) using higher order components. Maybe that's the way to go?
Sorry for my extensive response and thank you for your insights.
Cool, I see your point. I think cycle-react
does provide a similar solution right now. I changed your code just a little bit:
const Component = Cycle.component( 'Component', ( interactions, props ) => {
const view = Rx.Observable.combineLatest( props.count, props.text,
( count, text ) => <div> {text}:{count} </div> );
const someEvent = Rx.Observable.interval( 100 );
return {
view,
events: { someEvent }
};
} );
function someFunction()
const count = Rx.Observable.interval( 1000 );
const text = Rx.Observable.just( 'foo' );
const someEventSink = new Rx.Subject();
const subscription = someEventSink.subscribe( ev => console.log( 'Some event happened' );
return (
<Component
count={count} text={text} /* sources */
someEvent={someEventSink.onNext.bind(someEventSink)} /* sinks */
/>
);
The problem is that the sink is not provided by the framework, so I was thinking about the application-wide solution. I think it's a good idea to introduce sink
like your example. I will try do implement that idea.
OK. Good to see that my ideas aren't too far off ;)
Your example shows that what I intend is nearly possible, though it still has the problem of the original issue: subscribing to all custom events. Also there is currently no way of forwarding errors and completion of custom events in a reactive way.
I think I have a working plan in my mind of how we could achieve both our visions and handle both use-cases. In general I think, cycle-react
should actually provide two types of components - one that is closer to Cycle with sources and sinks in the way I proposed and one that is closer to React with updating props and custom event callbacks. I believe the second could easily be implemented on top of the first. I won't have much time the coming days, but I'll try and implement this idea early next week and we'll see if it makes sense or not.
Thanks again!
Fixed in f80fb3c. The idea off working with custom events is tracked in the backlog https://github.com/pH200/cycle-react/projects/1
Better late than never... while working on a solution that supports the ideas I proposed in this comment, I ended up rewriting everything as I wanted to support RX5 (or any ES Observable compatible library) and made a move to ES7, etc.
There isn't much left of your original code, even though it obviously was a big inspiration!
You can find the repository in reactive-react-component (yes the name is weird, but my idea is to support other frameworks in a similar fashion). It is not yet on npm.
Anyway. I hope it may be of use for you as well and maybe you even have further ideas or critique.
Thanks again for your great library, which as I said, was the main source / inspiration! :)
@FunkMonkey That's a really good job, and honestly I like the decision of not using some strange word for your library. TBH I think the most challenging part would be handling errors in the right way, and make it a lot simpler by using the framework. That's something I found recently, and I think maybe you will find it useful for your framework, too.
Currently in create-react-class#L144 all custom events are being subscribed to, even if nobody ever listens to them. As a performance optimization, a custom event observable should only be subscribed to, when a listener is passed as a property.
p.s. Thanks for the great library. I'm using it in multiple projects...