christianalfoni / cerebral-react-baobab

A Cerebral package with React and Baobab
MIT License
13 stars 1 forks source link

Baobab On Update #3

Closed saulshanabrook closed 9 years ago

saulshanabrook commented 9 years ago

Is it possible to achieve something similar to:

tree.on('update', function (e) {
  var newData = e.data.currentData.synced
  // send HTTP POST with all data
})

I want to be able to monitor one part of the tree for an update.

saulshanabrook commented 9 years ago

Would i use the emitted events example?

controller.eventEmitter.on('change', onChange);

Would this call onChange for every change in the tree?

christianalfoni commented 9 years ago

I have not exposed the tree because then Cerebral will loose control of it. A change to your tree will always be done with a signal, so your signal knows about this update before the tree :-)

Do you have a bit more details on the use case? I can give you an example of how you would solve it with Cerebral.

Absolutely everything starts with a signal.

christianalfoni commented 9 years ago

Really great that you ask these questions! :-)

saulshanabrook commented 9 years ago

I have a large part of my tree that is sent as JSON via websockets on every change. It is a very poor man's synchronization between clients. Just send the whole state to the server on every change, and then sound out the updated state to the other clients connected.

So I want to attach a listener to a part of the tree, so that every time any action updates it, it can be sent to the server.

saulshanabrook commented 9 years ago

So I guess I could add sendData at the end of every signal. It just seems like a more verbose solution then how I am currently doing it with baobab.

christianalfoni commented 9 years ago

Aha, I see!

Both your suggestions will work, though the first onChange suggestion will always receive the whole tree. You could of course just store the branch of the tree in a variable and check if it has changed whenever there is an update.

But what you can do with Cerebral to really optimize this is share the signals, not the state. So when a signal triggers you send the argument passed to that signal and what signal was triggered over the wire. Then run that signal on the other client. A lot less data and more effective. Does that make sense?

christianalfoni commented 9 years ago

So instead a "sendData" at the end, you have a "shareSignal" at the start.

controller.signal('somethingHappened', shareSignal, doSomething);
controller.signal('somethingElseHappened', shareSignal, doSomethingElse);

So with a "per signal" control you can effectively share and rerun them on other clients.

saulshanabrook commented 9 years ago

On your last suggestion, that does make sense and would work if I was just synchronizing clients, but I also parse the JSON on the server (in Go). And on the server, it needs to be able to parse the whole JSON tree in order to compute some things about it.

christianalfoni commented 9 years ago

Aha, I see! Well, in your scenario I think this is the best solution:

var currentBranch = null;
controller.eventEmitter.on('change', function (tree) {
  if (currentBranch !== tree.some.branch) {
     currentBranch = tree.some.branch;
    // AJAX stuff
  }
});
saulshanabrook commented 9 years ago

I am making a lighting control dashboard, so multiple clients edit at the same time and changes are synced between and to the server.

Why would I want to share signals between clients? Can't I just share the state?

I am thinking about it like this. When a client gets an incoming websocket message, that contains an updated state. So it sends a signal 'incomingWebsocketMessage. Which has a listener that will update the state with the incoming state.

Ah I see. Is it possible to get the old state for in that .on('change', function (tree)... method? Also that tree is the controller thing right?

saulshanabrook commented 9 years ago

I just recorded my state each time, so I can compare if it has changed. The synced part of the tree is the on that is (duh) synced.

var lastSynced = null

controller.eventEmitter.on('change', function (tree) {
  const newSynced = tree.synced
  if (newSynced !== lastSynced) {
    Storage.store(tree.synced)
    lastSynced = newSynced
  }
})
christianalfoni commented 9 years ago

Since the signals will run the same way on all clients it is just less data to send over the wire {signal: 'foo', args: {}}. Then you run the signal when an update is received, using the little amount of information. This rather than sending a big object with lots of data where just a small thing actually changed.

But yeah, as you stated, you need the state change on the server too, so it will not work.

The controller is Cerebral, which internally uses the Baobab tree. Any mutations run inside actions will be not directly run on the tree, but proxied to the tree. Any 'change' events will have the tree passed to it.

So the tree is very much "hidden", because the idea is that you work with the "controller", not the "model". If that makes sense, just wrote an article about it :-)

http://www.christianalfoni.com/articles/2015_08_02_Why-we-are-doing-MVC-and-FLUX-wrong

christianalfoni commented 9 years ago

Yeah, great! there it is! :D

saulshanabrook commented 9 years ago

@christianalfoni Just reading your article now. I have actually decided to add a shareData across all signals that mutate data that needs to synced. The reason for that is previously I was using the commit function in baobab, with autocommit: false, to do a bunch of changes then sync them all at once. However, that isn't possible with cerebral, right?

So to get a similar effect, I can have a sync handler type thing that I add to be the last signal handler.

christianalfoni commented 9 years ago

Actually I have to check that up with Baobab author. V2 of Baboab does all state updates synchronously, but when I tried to just pass the tree when the signal was done running, it did not have the updates. So now Cerebral runs a commit() before the change event is triggered.

So I think you might currently have to use your initial approach, but it should not be necessary though. Have asked author on https://github.com/Yomguithereal/baobab/issues/322

saulshanabrook commented 9 years ago

@christianalfoni thanks!

saulshanabrook commented 9 years ago

@christianalfoni How is it possible to get access to the commit baobab function to create a commit action like this? https://github.com/Yomguithereal/baobab/issues/322#issuecomment-127060364

christianalfoni commented 9 years ago

I checked it up, there was an option that was not set. Now any mutation is instantly accessible and you can safely access the latest state on the latest action in a signal :-)

Version 0.2.1 released

This will be default when V2 is ready, as stated here:

" Syncwrite

The tree now write synchronously only but will continue firing its update events asynchronously for obvious performance reasons. "

saulshanabrook commented 9 years ago

@christianalfoni But is it possible to execute multiple actions then only commit after all of them have finished?

christianalfoni commented 9 years ago

Baobab author is back from vacation now, so V2 is closing on release. I have to discuss it a bit more with him. There is some confusion in that you can do mutations and get changes, then commit all later... but they do not seem to be available when getting state using parent paths.

So yeah, need some more checkup, but this will work optimally when Baobab V2 is officially released :-)