Skip to content
This repository has been archived by the owner on Sep 10, 2022. It is now read-only.

Merge rx-recompose into main project #196

Merged
merged 18 commits into from
Jun 16, 2016
Merged

Merge rx-recompose into main project #196

merged 18 commits into from
Jun 16, 2016

Conversation

acdlite
Copy link
Owner

@acdlite acdlite commented Jun 11, 2016

  • No RxJS dependency. Uses observables that conform to Observable spec and implement Symbol.observable.
  • Configurable interop with stream libraries like RxJS, most, xstream, and Bacon
  • Renames createComponent to componentFromStream

const identity = t => t

test('mapPropsStream maps a stream of owner props to a stream of child props', t => {
const SmartButton = mapPropsStream(props$ => {
const { handler: onClick, stream: increment$ } = createEventHandler()
const count$ = increment$
.startWith(0)
.scan(total => total + 1)
::startWith(0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bind operator is still at stage 0 so syntax may change in future and there are no guarantees it will be included in spec

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it's a test, so oh well for now :D

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I'm importing each helper separately is to ensure as much as possible that we're not depending on any RxJS-specific functionality.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I understand that

@chicoxyzzy
Copy link
Contributor

chicoxyzzy commented Jun 11, 2016

It'll be cool if we can use not only RxJS but also most and other libraries. But this means recompose should use Symbol.observable instead of Observable and I don't sure how to do it proper. Otherwise docs should refer to RxJS 5 specifically IMO.

@chicoxyzzy
Copy link
Contributor

also interesting thread
cujojs/most#160

const mapPropsStream = transform => BaseComponent => {
const factory = createEagerFactory(BaseComponent)
return componentFromStream(ownerProps$ =>
Observable.create(observer => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't see .create in proposal https://github.com/zenparsing/es-observable
may be better to use new Observable(observer => instead

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

@acdlite
Copy link
Owner Author

acdlite commented Jun 11, 2016

@chicoxyzzy

It'll be cool if we can use not only RxJS but also most and other libraries

As long as Observable is a global that follows the spec, it should work, right?

@chicoxyzzy
Copy link
Contributor

Hmmm.. Probably you are right and it will work. I think I can create PR with some tests for most as well if you are not opposed =)

@istarkov
Copy link
Contributor

istarkov commented Jun 11, 2016

Good idea at @chicoxyzzy ref cujojs/most#160

It will allow to remove global Observable ref, and the only downside
before using with rxjs without bind syntax is that you will need to convert
Rx.Observable.from(props$).hereICanUseRxJsOperatorsAsIs

@chicoxyzzy
Copy link
Contributor

chicoxyzzy commented Jun 11, 2016

We can still use bind syntax after converting ES-compatible stream to RxJS observable

@chicoxyzzy
Copy link
Contributor

Just checked out. RxJS API and Most.js API are not 100% compatible so current docs from PR will work only for Rx but not Most.
We really can remove global Observable ref but in that case we should add [Symbol.observable]() to Recompose streams to make an interop possible. This is good because not Rx nor Most are actually polyfills but libraries for streams (kind of lodash for streams).

@chicoxyzzy
Copy link
Contributor

chicoxyzzy commented Jun 12, 2016

The original Observable from spec just have two methods. But it's possible to easily convert them into Rx or Most like so:

import Rx from 'rxjs/Rx';
import { from } from 'most';

const RxObservable = Rx.Observable.from(SomeObservable);
const MostObservable = from(SomeObservable);

@chicoxyzzy
Copy link
Contributor

chicoxyzzy commented Jun 12, 2016

Eventually this also can make integration with mobx possible (for first time it will be possible using Rx.Observable.from and https://github.com/chicoxyzzy/rx-mobx)

@chicoxyzzy
Copy link
Contributor

Redux does the same thing reduxjs/redux#1632

However IMO it's not so trivial to add [Symbol.observable]() method for Rx-Recompose and maybe it's an idea for distinct library

@acdlite
Copy link
Owner Author

acdlite commented Jun 12, 2016

We really can remove global Observable ref but in that case we should add Symbol.observable to Recompose streams to make an interop possible. This is good because not Rx nor Most are actually polyfills but libraries for streams (kind of lodash for streams).

I'm not sure we're on the same page. What is a "Recompose stream"? Are you referring to props$, the stream of props that is provided to mapPropsStream and componentFromStream? My goal is avoid introducing a Recompose-specific stream type entirely by using the observable spec.

The purpose of [Symbol.observable]() is to turn a stream-like value into an Observable. I don't see how it applies here, given that we're using observables directly.

A code example explaining what you mean would be helpful.

@acdlite
Copy link
Owner Author

acdlite commented Jun 12, 2016

Oh wait, I think I see what you mean about removing the global Observable. I don't think it's necessary for most compatibility, but it's a good idea nonetheless. Yeah you're right, it effectively is.

@acdlite
Copy link
Owner Author

acdlite commented Jun 12, 2016

@chicoxyzzy @istarkov Alright, I just pushed a commit that removes the global Observable.

The one downside I discovered to this approach, rather than mandating that your favorite observable library be available as an Observable global, is that every observable will need to be wrapped with Observable.from or something similar. To see what I mean, try deleting these lines from the test file:

const mapPropsStream = transform =>
_mapPropsStream(props$ => transform(Observable.from(props$)))
const createEventHandler = () => {
const { stream, handler } = _createEventHandler()
return {
handler,
stream: Observable.from(stream)
}
}
.

I'm thinking maybe we could expose a global module like configureObservable that allows you to specify how to convert observables to the desired form:

import configureObservable from 'recompose/configureObservable'
configureObservable(observable => Rx.Observable.from(observable))

// Then...
mapPropsStream(props$ => {
  // props$ is an RxJS stream
})

What do you think of that idea?

@acdlite
Copy link
Owner Author

acdlite commented Jun 12, 2016

Pushed an implementation of configureObservable. Seems a little gross, but gets the job done.


const createEventHandler = () => {
const emitter = createChangeEmitter()
const transform = getTransform()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@acdlite Maybe allow pass to configureObservable subject-like anything? And use change-emitter if only passed Observable dont has method next?

Copy link
Contributor

@chicoxyzzy chicoxyzzy Jun 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@typeetfunc Subject is Rx-related thing. There are no subjects in most. There is https://github.com/TylorS/most-subject but @TylorS once said in most.js gitter chat that it's more like motorcycle/cycle-related thing.
Discussion about Subjects in most cujojs/most#164

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chicoxyzzy Thanks for links. Also observer dont has interop point like symbol-observable - tc39/proposal-observable#89. Perhaps my proposal was a bit hasty :)

@chicoxyzzy
Copy link
Contributor

@acdlite I think it's worth it 👍

@chicoxyzzy
Copy link
Contributor

chicoxyzzy commented Jun 12, 2016

One thing I can suggest is to add tests for most to be sure Observable iterop compatibility works fine

@istarkov
Copy link
Contributor

About 2 commits above:

  • Import fix is because of import { Observable } from 'rxjs' will import all operators.
  • Nobind test as a lot of people prefer rxjs operators without bind, and test is the best documentation.

@chicoxyzzy
Copy link
Contributor

Just trying to implement some most tests (actually an analogue of first one from mapStreamProps) but it seems like SmartButton doesn't get mounted by enzyme for some reason 😕

@miketembo
Copy link

help me understand

@acdlite
Copy link
Owner Author

acdlite commented Jun 12, 2016

@istarkov Please don't add commits to a PR that's not yours without approval. Best way to do that is to submit a PR to this branch :)

What is the value of the test file without bind syntax? I don't see any.

@typeetfunc
Copy link

@acdlite What about context? Maybe pass them second arg in componentFromStream? Context doesnt require subscription, becuase its constanst usually. Its maybe useful for pass global streams like as actions or state streams.

@acdlite
Copy link
Owner Author

acdlite commented Jun 13, 2016

@typeetfunc

const enhance = compose(
  getContext(contextTypes),
  mapPropsStream(props$ => {
    // context is part of props$
  })
)

@typeetfunc
Copy link

@acdlite oh sorry, i missed it. Thanks :)

this.subscription = this.vdom$.subscribe(
vdom => {
this.subscription = this.vdom$.subscribe({
next: vdom => {
this.didReceiveVdom = true
if (!this.componentHasMounted) {
this.state = { vdom }
Copy link
Contributor

@istarkov istarkov Jun 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why check on hasMounted? (It was a bug in react, but it solved)
Also looks like this.state = {} could be changed on this.setState as this method does not called from contructor

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a bug in react, but it solved

Link?

Also looks like this.state = {} could be changed on this.setState as this method does not called from contructor

You're right. This is left over from when it used to subscribe inside the constructor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link ?

One of:
facebook/react#5719
fixed in 15.1

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check is to prevent calling setState if the component has already unmounted. That issue seems unrelated? Could be wrong.

@acdlite
Copy link
Owner Author

acdlite commented Jun 13, 2016

Re-commenting down here because original comment is on outdated diff:

@istarkov The componentDidUnmount check is to prevent calling setState if the component has already unmounted. The issue you linked to seems unrelated? Could be wrong.

@istarkov
Copy link
Contributor

this.subscription.unsubscribe() should prevent any calls there, so I deceided that it is because of some bugs. But now I see that in bug case this.subscription.unsubscribe() should throw.

Looks like there is no need to check this.componentDidUnmount

@acdlite
Copy link
Owner Author

acdlite commented Jun 13, 2016

Oh duh that's right

@chicoxyzzy
Copy link
Contributor

chicoxyzzy commented Jun 14, 2016

Didn't made any progress with my most.js tests yet and sorry for sort of offtopic but I think you may be interested in future of function bind syntax tc39/proposal-bind-operator#24 (comment)

TL;DR

  • it will not be ::
  • probably proposal will be splitted into separate "method extraction" and "functional pipelining" proposals
  • it will not be added any soon


// Stream of vdom
vdom$ = propsToVdom(this.props$);
vdom$ = toObservable(propsToVdom(this.props$));

didReceiveVdom = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vdom: null could me moved into state initializer, so there will be no need in didReceiveVdom at all

@chicoxyzzy
Copy link
Contributor

chicoxyzzy commented Jun 15, 2016

Summing up my previous comment I propose to add test which @istarkov provided earlier (without bind syntax) so no one will suffer refactoring their code after getting inspiration from test files. Also IMO tests should be written in JavaScript, not some dialect no one will understand one day 😃

@acdlite
Copy link
Owner Author

acdlite commented Jun 15, 2016

Yeah I'll remove the :: operator. I wasn't too concerned about it because it's recommended by the RxJS docs, but I didn't consider that people might read the tests and get confused.

The tests in mapPropsStream need to be rewritten anyway, I think. Not too happy with them right now.

@acdlite
Copy link
Owner Author

acdlite commented Jun 16, 2016

@istarkov @chicoxyzzy Just added tests for compatibility with RxJS 4/5, most, xstream, and Bacon. I've also added modules that export the config for each library. Are there any other stream libraries that we should include?

@chicoxyzzy
Copy link
Contributor

Wow! Last commit is super cool!

@chicoxyzzy
Copy link
Contributor

chicoxyzzy commented Jun 16, 2016

@acdlite the only library I know besides these is Kefir. It's API should be similiar to Bacon

@acdlite
Copy link
Owner Author

acdlite commented Jun 16, 2016

@chicoxyzzy Huzzah, and it already does all the hard work for us! https://rpominov.github.io/kefir/#from-es-observable

@acdlite
Copy link
Owner Author

acdlite commented Jun 16, 2016

I actually Kefir's naming better, too: fromESObservable and toESObservable, rather than just fromObservable and toObservable. Much clearer. I'll change it.

@acdlite acdlite merged commit c32e089 into master Jun 16, 2016
@istarkov istarkov deleted the merge-rx-recompose branch April 16, 2017 16:15
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants