arqex / curxor

A tree data structure that is always updated from the root, making easier to think in a reactive way.
GNU General Public License v2.0
2 stars 1 forks source link

Thoughts not just on Curxor but the broader aspects of React, Cursors, Streams and Channels #1

Open ArnoBuschmann opened 9 years ago

ArnoBuschmann commented 9 years ago

Hey Javi,

here my thoughts as promised...

I actually wondered, if we can say, that cursor solutions are replacing Flux, since Flux itself seems to be more the design pattern on how the communication is structured, while cursors are a great way to handle nested data structures one finds a lot in JSON.

Now for the question of how state and communication is/should get structured, these are the starting points from where one can proceed:

Where is the state being handled/set?

-> state handled and set within the React component (non Flux) -> the React component just takes events and emits actions/payload so stores outside of the component which act on them (Flux)

Where is the state living (not Flux related)?

-> in the React components via this.state -> in cursors

What leads to a new rendering?

-> setting state in the component: this.setState -> state change events emitted from cursors, bound to components

So where to go next? Actually even a bit surprisingly some time ago I found discussions stating, that React does a few things wrong and isn't itself as reactive as it pretends to be. The critique points were, that React has a lot of object oriented methods instead of functional reactive programming (frp) ones, so that React ... actively sets state in the components and imparatively passes down props instead of letting the state just flowing in reactively.

This article argues in depth on this topic: http://futurice.com/blog/reactive-mvc-and-the-virtual-dom As I don't want to change React for another framework, the question out of this for me is: How can we construct Reacts communication in a way that makes components as reactive as possible?

The author of this article created an alternative framework to React and it is based on Reactive Extensions (RxJS): https://github.com/Reactive-Extensions/RxJS

So Reactive Extensions are streams, which are basically by far more composeable than just, say, events.

Another development aside of streams now is the use of so called "channels". James Longster spoke about them on the Reactjsconf: https://www.youtube.com/watch?v=W2DgDNQZOwo&list=PLb0IAmt7-GS1cbw4qonlQztYV1TAW0sCr&index=6

Especially he spoke about csp-js for Javascript, which got ported from Closures' core.async: https://github.com/ubolonton/js-csp

His talk is a nice start, but when I found the initial presentation from Rich Hickey about Closure core.async, it truely went HOT: http://www.infoq.com/presentations/clojure-core-async

The nice thing about the csp-js thing is, that it is very small but also its api is not complicated at all, compared to the armada of tools and methods which RsJS is providing.

Long story short, to get the most powerful solution I think, we need a truely asynchronous solution of React + Cursors + Channels.

Concerning Relay and GraphQL you were mentioning I see these two main points:

https://gist.github.com/wincent/598fa75e22bdfa44cf47#why-arent-query-params-component-state "This is because setting query params is an inherently async operation..." and they built in an algorithm, that "...it won't try to render a component until all the data it requested are available, so it wouldn't do to only have, say, 10 objects in a list and to trigger a render "

Even this example leads to the question of how to handle asynchronity properly. But the good news (I guess) is, that csp-js will be able to handle all these aspects properly with ease.

I will have to have a closer look to your cursor solution (not done yet) and a comparison of it with Baobab might be helpful!?

arqex commented 9 years ago

Ey Arno!

You send me very informative content as usual :)

I agree completely with you in in the statement "Flux is a pattern", and as a pattern the important thing is rathern the idea behind (let the changes flow in only one direction) than the implementation itself.

I started to work in Curxor with that idea in mind, trying to take how easy is to manipulate the data with cursor libraries and making the changes flow always in the same direction. Curxor doesn't implement a traditional cursor or zipper as baobab ( you can't navigate the tree backwards ) but let's your components have and scope and work independently on it.

It is true that react is not as reactive as it pretends to be and I think they are trying to make it more declarative now: http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html It is not much, but they know that there are a big portion of the community that thinks that it is too much imperative sometimes.

From my point of view, that imperative part of react is rather a benefit than a problem. A source of truth is passed from the root component to their children, so the children don't have to listen or observe the store. Children are not reactive, they are completely re-rendered, so you can forget about setting listeners. The communication in that direction is done, nothing is needed.

The questions grow when a child component needs to require a change. How should that component communicate some user interaction?

In this part I don't like the way that libraries like Baobab or Cortex solves the problem. They encourage observing individual store nodes to react to the changes and letting the components update the store. I think that way it is easy to lose the track of where the changes happen and why.

I loved Andre Medeiros' article about reactive programming. It is true that with Flux pattern the components are really coupled to the dispatcher (or action creators) and it is a problem to make them really independent. As he said in the article, a controller (dispatcher) is still needed to be the glue of all the app, but instead of couple it to the components, it would be great that it were reactive. So components would just emit events ( or put a message on a channel ) and the controller would take those change request and modify the model, triggering a re-render.

He talks about making intends completely independent, emiting events to update the store, and it needs another piece of code for binding all the MVI. IMHO there is not much benefit making the intend/controller completely independent, because usually is where the app logic lives, and I think it is difficult to reuse app logic in a different application. So a direct communication between intend and model would be easier and more natural than making the model listen to the intend.

I found a big problem in the architecture I am describing, how to effectively make the controller add/remove listeners to the components?

Imagine a twitter app with a list of tweets. Every tweet on the list should be independent, so they can emit events like, 'favorite' or 'retweet'. The controller should add listeners to every tweet, and remove them when the tweet is removed from the list. I found that very difficult without the help of the component itself. Adding some code to do so in the component on mount/unmount is easier, but we are again coupling the view with the controller. Maybe putting a message on a channel would do the trick?

Eeeey, I am spending so much time here :dancer:

Last thing about Relay, I have thought the same as you. That's a lot of work in the backend to create the adapter!!!

But it solves a point that I was worry about when I was thinking about the router: Imagine you have an app with a menu and the content. When you navigate to another URL, the router would fetch the JSON data for that URL to update the store. The menu data comes with the load, because it is necessary to render the full page, but we don't want to re-render the menu, just the content, so it would be logical to fetch only the content part.

I am looking forward to the comparison of Curxor and Baobab! ;D

staltz commented 9 years ago

@ArnoBuschmann

Where is the state living (not Flux related)?

That is an interesting discussion. There are multiple different types of state in an app. Some are business-related data (persistent), other are view-specific state (transient: e.g., isToggled, isHovered). Some architectures have the approach of putting all state into one place, e.g., a tree data structure, such as Om. Other architectures allow you to spread them around your data, such as React/Flux, where React components can have internal state and Data Stores have application data. In https://github.com/staltz/cycle, the solution is similar to Hierarchical MVC. Inside a View, there can be an internal Model (managing state), Intent, and View. And inside that internal View, there can also be another MVI trio. The architecture is fractal-like, it doesn't make a distinction between persistent state and transient state, neither does it mash all types of state in the same tree. It allows for flexible layers of state based on their level of abstraction.

As I don't want to change React for another framework, the question out of this for me is: How can we construct Reacts communication in a way that makes components as reactive as possible?

Follow developments of Cycle, it is hard core reactive. I suspect you can actually replace virtual-dom with React, setState is imperative, but you can bypass it I think. The reason why I use virtual-dom instead is that once you strip down React of all of its imperative machinery, you are using very little of the React library (not using createClass, setState, props, etc), so it becomes unnecessarily heavy. React trashes the DOM with data-react-id attributes and container divs for every component, making the DOM deeper and hence heavier. virtual-dom is lighter (in multiple ways), more low level, and allows us to get rid of some React annoyances.

So Reactive Extensions are streams, which are basically by far more composeable than just, say, events. Another development aside of streams now is the use of so called "channels". James Longster spoke about them on the Reactjsconf: https://www.youtube.com/watch?v=W2DgDNQZOwo&list=PLb0IAmt7-GS1cbw4qonlQztYV1TAW0sCr&index=6 Especially he spoke about csp-js for Javascript, which got ported from Closures' core.async: https://github.com/ubolonton/js-csp

Reactive programming is simply about decoupling data production from data consumption, so a module (or some "part" of your app) can choose what data is it interested in consuming whenever that data changes. With typical imperative programming, when data is produced, the producer has to explicitly know its consumers and explicitly ask them to update.

Hence you can use simple event emitters with listeners to implement reactive programming. But as I said in the article, Event emitters as slingshots, while event streams with operators (Rx, Bacon, Kefir, Highland) are semi-automatic sub-machine guns.

CSP channels go beyond and decouple 3 things: data production, data medium, data consumption. It has its benefits, but you can also choose to decouple the data medium with Rx et al, so then CSP versus event stream processing becomes just a matter of taste.

@arqex

It is true that react is not as reactive as it pretends to be and I think they are trying to make it more declarative now: http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html

There's nothing in v0.13 that makes it more reactive.

@arqex

IMHO there is not much benefit making the intend/controller completely independent, because usually is where the app logic lives, and I think it is difficult to reuse app logic in a different application. So a direct communication between intend and model would be easier and more natural than making the model listen to the intend.

For most practical aspects, in MVI, Model is analogous to Store and Intent is analogous to Action. Most flux variants agree that keeping Action separated from View and Store is a good idea. In Cycle, Model is a function, View is a function, and Intent is a function. And they all follow the reactive pattern: consume event streams and output event streams. This provides you a consistent way of approaching any part of your app. And there are cases where you can easily skip the Intent and just hook up View to the Model. But those are corner cases. Another reason why you want to have Intent is to allow the Model to be agnostic to how the view looks like and anything about the DOM. In general, it is beneficial that:

In an upcoming version of Cycle, Intent will decouple interaction events from View in a way that no React/Flux variant that I currently know can do with Actions and Components: views completely agnostic to interaction events, in other words, no such thing as <div onClick={this.tick.bind(this)}>. The point being: View should ONLY be concerned on how things look (layout and styles). Interacting with them is another concern. This is what I'm talking about:

var HelloModel = Cycle.createModel(intent =>
  ({name$: intent.get('changeName$').startWith('')})
);

var HelloView = Cycle.createView(model =>
  ({
    vtree$: model.get('name$').map(name =>
      <div>
        <label>Name</label>
        <input className="foobar" type="text"></input>
        <h1>Hello ${name}</h1>
      </div>
    )
  })
);

// THIS will listen for input events on `.foobar` and maps to an appropriate value
// for the Model to consume.
var HelloIntent = Cycle.createIntent(view =>
  ({changeName$: view.events('.foobar', 'input').map(ev => ev.target.value)})
);

(^ Using JSX just for familiarity. Normally we use virtual-hyperscript.)

So for instance this

I found a big problem in the architecture I am describing, how to effectively make the controller add/remove listeners to the components?

becomes completely irrelevant if Intent selects events happening on the View.

ArnoBuschmann commented 9 years ago

@staltz

André, nice to see you joined our discussion, thanks for all your valuable and very interesting comments!

Persistent and transient state is a nice differentiation. On top of that, I found the concept of "state sources" used in Martyjs interesting: http://martyjs.org/guides/state-sources/index.html

As for what I got yet, tree/cursor structures were circumventing imparative calls such as setState by binding components to certain cursors of the tree.

So my idea of decoupling producer/consumer was:

  1. Catch UI events, give them (action)names and send them with the payload into a stream/channel
  2. Outside of the React component -> listen/act on the stream/channel, transform/handle data as needed and save it in the tree structure
  3. The view listens to tree structure changes and rerenders accordingly as needed.

Seems this relates closely to what you described with: (2) One thing handles data/state changes: Model ("Stores") (3) One thing handles how data/state is displayed: View (1) One thing defines what do interaction events do: Intent ("Actions")

A current Twitter discussion on this right here: https://twitter.com/sebmarkbage/status/562779855272837120

Using React, I can implement libraries, react-router for example to create an isomorphic app and I wonder how this is handled in Cycle, would I need to write everything on my own? What router are you using there?

CSP - RxJS -> Is there a blog post or something where I can read more about how each of them would decouple "data medium"? And yes, I agree, after having researched a bit further, it indeed looks like a matter of taste, which one to use.

Hey @arqex,

I'm not sure, can you give me an example on how to exactly pass a Curxor structure (or parts of it - cursor) to a React component? Is there any binding/mixin needed, the pureRenderMixin for example?

What I meant with comparing it to Baobab is, for example, to have a look at this page: https://github.com/Yomguithereal/baobab/pull/74 @christianalfoni is doing a nice job there on explaining React rerendering mechanisms and maybe you could get one or another creative inspiration for Curxor. On the other hand it might not be needed as Curxor is already perfect in rerendering, I don't know :)

Your api looks slick and intuitive already :)

christianalfoni commented 9 years ago

Hi guys,

Here are some references to the issue at hand:

Immutability helpers. This is what Facebook recommends to use in combination with the PureRenderMixin to control rendering. It basically makes sure that if you update your state object with the "update" method, any changes to arrays, objects etc. on the state object will also update the reference of the arrays/objects. That way the PureRenderMixin can do a shallow comparison and decide if it is time for a render or not.

An alternative render strategy with React JS is an article a wrote on understanding how React JS renders. Just ignore the solution at the end :-)

Great work on this lib!

staltz commented 9 years ago

Using React, I can implement libraries, react-router for example to create an isomorphic app and I wonder how this is handled in Cycle, would I need to write everything on my own? What router are you using there?

Not yet, we are discussing about it, and at some point I'll start building the features. https://github.com/staltz/cycle/issues/37 and https://github.com/staltz/cycle/issues/34

SP - RxJS -> Is there a blog post or something where I can read more about how each of them would decouple "data medium"? And yes, I agree, after having researched a bit further, it indeed looks like a matter of taste, which one to use.

Not many, but I can remember this being useful, although it's biased towards core.async. The Rx solution (even though it was hard to nail) is much smaller than the core.async one. http://www.jayway.com/2014/09/16/comparing-core-async-and-rx-by-example/ Also this upcoming presentation http://applicative.acm.org/speaker-MatthewPodwysocki.html And this past presentation https://www.youtube.com/watch?v=-vPFP-2Mkl8

arqex commented 9 years ago

This thread is sooo dense guys! I need to spend one hour in every comment to check the links and fully understand what you mean. I love it :D

@staltz Nice you came to show your innovative ideas!

It is very interesting the fractal structure you propose. So you are making micro components that have all the MVI in them, and that creates so many question in my head:

Sorry for all those questions, I think I need to give Cycle a try to understand it in depth, but my biggest concern about it now is the communication among modules. We still have the problem of let a part of the app know that something has changed in another part.

As for the router topic that @ArnoBuschmann brings out, Arno and I have been discussing it for a while and one of the things that we are sure about ( I think :) ) is the need of having the app state in one place. A big model that will receives updates on URL changes and all the views will update on that change. Since your model has not a big model, how do you think you will face the router problem?

BTW: I am enjoying so much your discussion with Sebastian Markbåge on twitter, he seems he likes react "state-machine way of being" instead of making it more reactive.

@christianalfoni

I am glad you join the discussion too. The reason of creating a new store for react applications instead of using immutable.js is the size of immutable. Curxor is much more lightweight (more than baobab too ), its API is simpler but uncompatible with immutable's one, so it is not possible to use React's official Immutability helpers with Curxor.

On the other hand, the pureRenderMixin that Arno comment is compatible out of the box with Curxor, since you can make direct comparison with the stores:

this.props.store === nextProps.store;

The point of immutable is that you don't have to listen to particular events in some part of the tree to update only that part of the UI. Just let flow the data from the top, and using pureRenderMixin you will have a really good performant UI without bother about events.

I need to have a deep look at your flux-react, it seems promising. I also have my own implementation of flux https://github.com/arqex/fluxify, maybe we can discuss them in a different place

@ArnoBuschmann I still see the communication problem difficult to solve making the view purely independent. If you use CSP or RxJs you are coupling the views to those libraries. The only thing that comes to my mind is trigger synthetic DOM events and let them propagate, and use the document object as a pubsub system where the controllers listen. It is a bit rough idea but you can still use RxJS to observe those events and make it prettier.

As for the way of using Curxor with react, I don't want to include mixins in the library, I think it needs to be agnostic in the way of using it and no react users can find it handy too, and mixin for them would be unnecessary.

But it is really use it, just adding a listener in the root component. Let's create a counter:

var store = new Curxor({ counter: 0 });

var App = React.createClass({
    render: function(){
        return <Counter store={this.props.store.getData()} />
    },

    componentDidMount: function(){
        var me = this;

        // This is the only boilerplate needed to make it work
        this.props.store.on( 'update', function(){
            me.forceUpdate();
        });
    }
});

var Counter = React.createClass({
    render: function(){
        return <span onClick={ this.increment }>{ this.props.store.counter }</span>;        
    },
    increment: function(){
        this.props.store.set({counter: this.props.store.counter + 1});
    }
});

React.render( document, <App store={ store } /> );

After adding the listener in the root component, you can deep your data/components as much as you can that no more listeners are needed.

staltz commented 9 years ago

@arqex

How do you nest your MVI components?

On app initialization, you register a custom element for <Foo> and you give it an implementation, which is a View internally containing a Model, a View, and Intent. Cycle.registerCustomElement('Foo', FooComponent); where FooComponent is that View I mentioned. The idea here is to imitate how web components are registered in the DOM. Once all the custom elements are registered, you can use them just like divs or whatever, so it doesn't matter if they are nested or not.

You are detaching the intends from the views events, but you are attaching them to the view markup in order to listen to user actions. If you change the markup you need to change the intend code too, don't you think it would be a maintainabilty problem in a future?

Is there some kind of mechanism for intercommunicate the MVI components? It is possible that a view can be used to request a change in a parent component.

I get these two questions quite often so I decided to write the answer in an FAQ: https://github.com/staltz/cycle/blob/master/docs/faq.md Normally I am verbose explaining these issues in the gitter chat about Cycle, tune in if you want to discuss.

Since your model has not a big model, how do you think you will face the router problem?

You can have a "big" top-level model.

ArnoBuschmann commented 9 years ago

@arqex Well, what I did and actually accomplished is this:

The plan was to catch events and send them as action message (flux alike with payload) into a stream (RxJS in this case) to move the event handler logic out of the React component.

Then I have a subscriber to that RxJS stream, listening for and acting on that action. Here sits the logic, state gets handled/transformed/whatever and the result goes back into the tree structure, Curxor for example.

Now as the tree structure changed, the view component should update accordingly.

This is how it went. In RxJS I created a Rx.Subject:

var actionSelectChange = new Rx.Subject();

The handleChange method within the React component simply drops the event into the stream:

  handleChange: function() {
    actionSelectChange.onNext(this.refs.input.getValue());
  }

Then outside the React component a subscription listens, appstate in this case is the tree structure:

var subscription = actionSelectChange.subscribe(
  function (payload) {
    appstate.set('myCursor', payload);
  },
  function (e) { console.log('onError: ' + e.message); }
  );

@staltz Just curious, is this a proper way to do in in RxJS or could I do better? Also thanks for the links :)

staltz commented 9 years ago

Just curious, is this a proper way to do in in RxJS or could I do better?

Yes for both. It is a rather proper way to do it in RxJS, and you could to better. The use of Subjects and onNext are valid, but often a code smell. If I keep on suggesting improvements here, I'll just end up describing Cycle, where on the app level you see no subject, no onNext, no subscribe, because they are hidden in the framework.

christianalfoni commented 9 years ago

@arqex Hi, just wanted to complement on fluxify. It is really exciting to see how developers are combining tools and creating new APIs for flux :-)

I suppose this lib is great for isomorphic apps. As I understand it you "by default" push the store down through the top component? What I am having problems understanding with this approach though is that all components become dependent on their parent components to pass the state they need. I like the concept where components are totally isolated from each other, as I can just move them wherever I want without affecting other components. But this approach becomes more complex on the server as you can not just push the state in from the top, you have to use some kind of "special module" to inject the state in the different components. I have no experience with trying to create an isomorphic app :-)

Is this what the "withContext" is supposed to solve?

arqex commented 9 years ago

@christianalfoni From my point of view, most of the apps have a main state, that can make use of Curxor to be its store and use the always-rendering- from-the-top solution. I think that main store can be the key to create isomorphic apps easier.

That doesn't mean that your app can't have independent components, completelly or partially decoupled from the main state of the app. With Curxor you can listen to changes at any node in a similar way you do with Baobab if your independent component need some data from the main store:

var store = new Curxor({a: {b: {c: [0, 1, 2] }}})
var data = store.getData();

// Get a listener for data.a.b
var listener = data.a.b.getListener();

listener.on('update', function( updatedNode ){
  console.log( updatedNode );
});

data.a.b.c.push( 3 ); // Logs {c: [0,1,2,3]} on nextTick

Or you can create a new store for your component and work in the same way that with he main app component but with its own domain (store).

Your components may not follow the same structure of the store either, and receive different branches of the tree as props. But the main store makes thinking in a isomorphic way much easier.

It is funny, because all this brainstorming started discussing about how to create a router to handle isomorphic apps with ease, then we got worried about handling stores changes and communication and we are again talking about the router :)

What do you mean with the "withContext" stuff?

christianalfoni commented 9 years ago

@arqex Hehe, its easy to drift talking about flux :-)

I see the flexibility provided with Curxor now, thanks for the explanation! What one have to be careful about though is when passing the store. If the store has changed and the store reference is passed on the props to all components, all of them will do a render. For optimization reasons you have to make sure only passing store props the component is actually interested in or you risk doing unnecessary renders. That could give scenarios where one component passes a property from its parent to its child, which causes a render it really did not need, because it was the child that needed to render.. heh. Hope I am making sense :-) What I am having a tough time reason about is that a store passed by props makes rendering incredibly difficult to control. That said, React JS and JavaScript is crazy fast, so it probably is not a big thing. I am just really deep into this rendering and optimizing it at the moment :-)

About withContext, https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html, it is a method that lets components grab state from from a main context without affecting parents. I think your main Curxor store would fit very nicely in a "context" like this.

arqex commented 9 years ago

In fact, it is not just when the parent passes its props to a child, the parent get "re-rendered", using Curxor, if a node is updated you are sure that all the parents are going to be "re-rendered" because their nodes are updated too.

But it is not a real rendering, it executes the render method and makes a reconciliation with the actual dom, and that process is really fast. And since the other nodes didn't change, using the pureRenderMixin you are sure that our node's siblings are not going to even execute the render method.

The last part of the immutable presentation at react conferece talks about this matter: https://www.youtube.com/watch?v=I7IdS-PbEgI#t=1271

This is not as performant as re-render only the changing node, but for most of the cases is performant enough, and it makes you forget about the 'what to render' problem (less listeners, less boilerplate)

Ey withContext is really interesting. I didn't know about it. Maybe using childContextTypes as it said in the article you can save some parent renderings too.

christianalfoni commented 9 years ago

I think to a lot of developers the use of PureRenderMixin is a bit confusing since you basically need to use something like "React.addons.update" for it to work as you expect. The good thing is libs like yours and Baobab, that just makes that use irrelevant :-)

I am going through the reactjs conf talks now, really looking forward to the one you mention here!

I agree that in regards of performance it does not matter that components do unnecessary renders, but for the principle of controlling the code execution and more so, debugging, I think it is really important. Adding a console.log to a render method causes it to trigger an unknown number of times in larger applications. Kinda annoying :-)

My mission with affecting Baobab is to get 100% control of component rendering, without having to understand immutability. Make the code run the way you expect really.

If that is even important could of course be discussed, but this uncontrollable rendering is not something you have to handle in other view libs.

arqex commented 9 years ago

I understand your point, I reckon that all those updates ( and console.logmessages ) are side effects of forgetting about the way it renders. But I don't agree on the terms 'uncontrolled code execution' or 'uncontrollable rendering' though, they are perfectly controlled and predictable passing down the props.

It is clear that, if you want to decide what is going to be rendered in every change, it is not the way to go, and Baobab or Cortex is much better (Curxor is going to update always from the top). I think that is less reactive way of programming too. But my point is that in most of apps, taking care of what to render doesn't worth the effort, it is time you better spend in other parts of the app and makes you more productive.

I added listeners to Curxor nodes in order to let those nodes be used as independent stores following the receiving props from the parent way, but not fine control rendering.

I think we are trying to achieve different goals, it is difficult we can agree completelly on the topic :)

christianalfoni commented 9 years ago

Heh, yeah, I think you are right :-) Flux is still a "new thing" and there is a speedy evolution going on now. Libs solving different challenges in different ways, and Facebook throwing new concepts into the mix from time to time :-) It will be exciting to see how it continues to evolve in the coming months!

arqex commented 9 years ago

Ey guys, I have renamed Curxor to Freezer. Freezer is faster, uses less memory and it is just 6KB minified.

https://github.com/arqex/freezer

I hope you like it!

christianalfoni commented 9 years ago

Hi @arqex ,

It looks really great! Have you cleaned up the API? I got a different impression this time, seems extremely clean :-)

Now, I would encourage you to create a React JS mixin. So that you can:

var store = new Freezer({
    a: {x: 1, y: 2, z: [0, 1, 2] }
});

var Component = React.createClass({
  mixins: [Freezer.Mixin, React.addons.PureRenderMixin],
  getStoreState: function () {
    return {
      y: store.a.y,
      list: store.a.z
    }
  },
  render: function () {
    return <div>{this.state.y}</div>
  }
});

So basically the mixin does this:

  1. Runs the getStoreState method
  2. Registers listeners on all pointers
  3. Moves the value of the pointer to the state object of the component, using the key provided
  4. When listeners update, update the state object with the new data

Now the component is able to verify if it should render or not using the PureRenderMixin. Awesome stuff :-) Would also be great to configure Freezer to use the PureRenderMixin together with its own Mixin. Something like Freezer.mixins = [React.addons.PureRenderMixin]...

It is such a pure API, with a mixin you keep that purity also inside the component. Just a suggestion :-) Great job on this! The best API for immutable data I have seen!

arqex commented 9 years ago

Hi @christianalfoni

Thanks for the compliments, I am glad you liked it. The API is almost the same as curxor. Why to create an API if we have objects and arrays?

As I told Arno, I prefer to keep mixins out of Freezer because it may be used in a non-react environment, but it would be nice to have a mixin like that.

I also like the way that Baobab handle local state, keeping it in the store: https://github.com/Yomguithereal/baobab#common-pitfalls

It would be nice to create a synchronous way of updating the data, so input states can also live in a freezer store.

arqex commented 9 years ago

Ey @ArnoBuschmann

I kept thinking about the idea of using events to create a more responsive Flux. Instead of using the RxJS stream inside the component ( the component would be dependant on RxJS ) I think it would be much better to emit DOM events from the component and having a listener outside the view. You still can use RxJS there to use event streams and keep your components independent from the tool.

Using DOM events turned to be a great idea, and I am got used to work this way really fast. I have written a small article about it

http://arqex.com/1028/better-flux-dom-events

I am willing to know your thoughts.

staltz commented 9 years ago

@arqex Are you aware that this approach is very similar to how Cycle works? https://github.com/staltz/cycle and https://github.com/staltz/cycle/blob/master/src/dom-user.js#L104-L127

By the way, you can drop the custom DOM events (in most cases), and just use plain old selectors and event listeners on the rendered DOM elements.

Instead of this

var Item  = React.createClass({  
    render: function(){
      var className = 'item';
      if( this.props.selected )
        className = ' selected';

      return (
        <div className={ className }>
        <span>Item { this.props.index } </span>
        <button onClick={ this.onSelect }>Select</button>
        </div>
      );
    },
    onSelect: function(){

        // Hit the button will just emit an event
        var e = document.createEvent('Event');
        e.initEvent('select', true, true);

        // Add item index to the event
        e.detail = this.props.index;

        this.getDOMNode().dispatchEvent( e );
    }
});

This

var Item  = React.createClass({  
    render: function(){
      var className = 'item';
      if( this.props.selected )
        className = ' selected';

      return (
        <div className={ className }>
        <span>Item { this.props.index } </span>
        <button className="item-button">Select</button>
        </div>
      );
    }
});

$(".item-button").on("click", function doStuff()); // jQuery just as an illustration
arqex commented 9 years ago

Hi @staltz

Thanks for drop your thougths! Yes, the idea of Intent in cycle inspired me so much, in the way that I saw that it is possible to make the view trigger actions without couple the view and the action. The article shows how it is possible to continue using Flux without attaching your components to a Flux implementation, that is why I used that verbose way of creating events using plain JS.

I will speak from the Flux point of view, that I think that differs a bit from the cycle.js one. Since I don't have intents I can make the views trigger directly the events needed that will provoke the reactions, and custom events have advantages doing so:

ArnoBuschmann commented 9 years ago

Hi @arqex, you were creative again! :)

I really like your idea of using events and even more the effect of keeping components independent of RxJS or baconjs - another thing decoupled, nice!

Also I love to see on how exchanging thoughts on this page leads to new ideas and developments. When I started to read and think about functional reactive programming (frp) some time ago, there was a lot confusion, such as what the heck is frp in general and what differentiates it from fp (functional programming) for example. It became clear very quickly, what functional programming is but lots of people have had hard times to explain the reactive part. This is, when @staltz dropped in with his writings to nail the beauty of reactive.

It is important to pay attention on how we name things. Being reactive is a passive thing, now just take the name React, to react, to do something... not passive but active. But I guess Reactivejs was already taken, so just Reactjs was left? Then with Flux new names were thrown in such as actions and action creators, confusing! We have events since long, a user actively interacts with the UI and that's causing events - nothing new to learn. Now we need reactions on these events, so let's call it reactions, easy! Long story short, I like your naming as well :)

arqex commented 9 years ago

Ey Arno,

Thanks for your kind words. I am glad to discuss all this stuff with you guys, I learn a lot!

This event approact would be perfect if we could use custom react.js events for the communication. It will work in every enviroment then, browser, server, mobile with react native... I will ask react guys about if they see a chance of implement it.

neverfox commented 9 years ago

@staltz What do you make of David Nolen's challenge that FRP will break down (compared to CSP) implementing something like a full autocompleter. Might be a good demo for Cycle.

staltz commented 9 years ago

@neverfox (1) what does "full autocompleter" mean? What are the requirements/specs? (2) I haven't encountered yet a problem which cannot be solved with RxJS, and I don't think there is. I mean, if it can be solved imperatively, it can be solved with RxJS. And I also believe if it can be solved with CSP, it can be solved with RxJS (and vice-versa). (3) I'm not sure from that Twitter discussion if he actually meant that FRP would break down. (4) I would enjoy implementing the "full autocompleter" in RxJS + Cycle.js but I don't have time at the moment (should be on holidays) and (1) as mentioned above.

neverfox commented 9 years ago

@staltz (1) I believe he's referring to his clojurescript example here. (2)(3) I only know that he's said things like this. I believe it's the same opinion shared by Rich Hickey that "Events complect communication and flow of control. While there are various mechanisms to make events/callbacks cleaner (FRP, Rx/Observables) they don't change their fundamental nature, which is that upon an event an arbitrary amount of other code is run, possibly on the same thread, leading to admonitions such as 'don't do too much work in your handler', and phrases like 'callback hell'." Bret Young added, "In FRP style approaches you are tying the event stream directly to the code that is executed when events happen on the stream. With the channel abstraction you have separated communication (something is put or taken from the channel) from flow of control (arbitrary code executed as a result of item taken off the channel. And, more importantly, the consumer and producer on either end of the channel change as desired (it not fixed, like a handler is, at stream definition time). Plus, channels are really first class in that a channel can be put on another channel." So perhaps you're right that they don't think it breaks down per se, but rather that it "complects" and eventually leads to brittle architectures in complex situations. (4) I'd love to see it. I only bring it up because I'm curious about the two approaches, not because I have much of dog in the fight.

staltz commented 9 years ago

it "complects" and eventually leads to brittle architectures in complex situations.

That's just a conjecture. I disagree of course. I've seen the opposite also: http://www.jayway.com/2014/09/16/comparing-core-async-and-rx-by-example/

Where the RxJS solution (this one is not in the blog post above) is just

bothButtons
  .timestamp()
  .bufferWithCount(6, 1)
  .filter(xs => xs[5].timestamp - xs[0].timestamp < 5000)
  .map(xs => xs.map(x => x.value))
  .filter(xs => xs.join('') === 'ABBABA')
  .subscribe(() => outputDiv.html("Congratulations!"));

(4) I'd love to see it. I only bring it up because I'm curious about the two approaches, not because I have much of dog in the fight.

I'll gladly do that after my holidays. ;)

staltz commented 9 years ago

@neverfox https://twitter.com/andrestaltz/status/620365243055603712

:) Code is looking really beautiful so far.

neverfox commented 9 years ago

Nice.

staltz commented 9 years ago

Here's the full autocompleter code @neverfox : https://gist.github.com/staltz/e6fc5b2b19207bb4f7da It's only the source code. I'll publish the index.html and a blog post later on, where I'll explain the whole approach step by step.

HighOnDrive commented 9 years ago

Interesting to catchup with this issue again after a break, where I got to know RxJS and then Cycle.js. I simply followed all my hutches and ended up loving what Cycle.js is doing. Comparatively, I've grown tired of reading about Flux and the endless attempts to repair it.

Looks like more React developers are discovering RxJS now and just what being partially vs. fully reactive actually entails. Here is one of many such discovery discussions going on:

https://news.ycombinator.com/item?id=9988522

I wish everyone the best in their endeavors and very much appreciate what Andre Medeiros has contributed toward making development more sane. May your aim be true :-)