-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Investigate Cerebral #343
Comments
i was looking into it, wonder if it is able to hot-reload, but the thing that made the most sense to me was that the we are definitively doing the router wrong! this video highlights the issue. |
The UI of the Debug-Tools? j/k I haven't checked the complete code yet, so all my impressions are from the video. Same goes for Redux, as I just started with react two days ago. (So maybe I should not answer at all? :) ) The chaining of the signalhandlers (Cerebral: ?actions?, Redux: reducers) in response to a signal (Redux: action) is maybe - haven't decided yet - nice. It provides some documentation out of the box, but is nothing that can't be done by chaining normal functions in a reducer. Also, it will only call the handlers responsible for an action and not call every reducer (that's how I understood Redux for now). Don't know if that is performance relevant. Also signalhandlers can return values, that get merged with their args and will be passed to the next signalhandler in the chain. Something I actually don't like, as it creates a hidden contract between any two signalhandlers. One "thing" that we could look into is how easy it is to have async handlers/reducers (well actually more like ActionCreators) in Cerebral: Just put them into Arrays inside of the chain. All in all, I don't see anything that can be done in Cerebral, that can not be done in Redux. I still think - if possible at all, still wrapping my head around it - we should come up with a solution for async, that put's the function closer to the reducers (even if it is only by naming or examples) or even into them, as this seems to be - as far as I can tell from the issues here - one of the harder problems for new users of Redux? |
Hi guys, just putting in my two cents! I am sorry to say I have not looked too much into Redux yet... very interested, but spending as much time as I can on getting Cerebral to a 1.0 version :-) What I think users of Cerebral benefits from, as @ioss points out, is how you define "application flow". What I mean is that when you request a state change from the UI layer, or whatever, you often have many things you want to occur. An example would be:
All this is described very precise with a Cerebral signal, which also the debugger benefits from. It is not clear to me how you would define this kind of flow with Redux, but as I said, I am not familiar enough with it. @ioss I completely see your point with the "args". The reasoning behind it is that although the actions are reusable across signals, they should be able to operate on some input to the signal chain itself and some output from a previous signal. For example handling output from an async signal. That said, this merging strategy might not be the best way to go. Let me know if you have some ideas on how it could be solved differently :-) @Agamennon The hot-loader for React, by @gaearon, is really amazing! But one thing I think we often overlook is that the really great thing about the hot-loader is not avoiding a browser refresh, it is that the components keep their state after you change your code. So, even though you have to refresh the browser, using Cerebral, your application continues in its previous state. And it does not require React to do so. I understand Redux also has a local-storage middleware? So it works there too, but hot-loading requires React. Again, live code update is awesome for sure, but live-reload with reproduced state on load gives the same thing.. its just a browser blink further away ;-) Thanks for taking a look at Cerebral and giving the feedback. It is just as useful for me! So thanks for putting up the issue @gaearon! |
Question for "hot loading" for both frameworks: if you've navigated a few links deep into a wizard and pulled up a modal, what happens when code is changed and hot reload happens? Does the nav and modal stay where they are with state intact? |
@nateajohnson This is not really related either to Redux or Cerebral and depends on your hot reloading solution. React Hot Loader keeps the state of React components so yeah, as long as you're using React, UI stays during code change. |
@nateajohnson Just adding that you can keep state with normal Live-Reload too, using Cerebral (Also Redux I think?). This does not specifically require React, but you browser will blink, as it is a full refresh. |
Thanks Dan. I only replied to this thread because it was mentioned that hot reload in cerebral works with a reload. I'm just trying to understand how that can be replayed in both frameworks. I think from what you said it works ok in redux because there is no reload. |
I demo hot reloading with (and without) Redux in my talk: http://youtube.com/watch?v=xsSnOQynTHs |
Thanks @christianalfoni and @gaearon. I appreciate the prompt answers from both of you. I think I'm starting to get it. I'm going to step out and watch both projects for a bit. |
For me this "controller.signal('formSubmitted', setLoading, [saveForm], unsetLoading);" and "controller.recorder.record(controller.get());" looks really great. I think it is possible with Redux too, but you don't have it out of the box? |
@gaearon @christianalfoni Personally I have found that the conceptual model of signals (or channels, impluses...) as implemented in Cerebral is really nice to work with. Having all UI 'inputs' simply emit a signal/impulse when some thing has 'happened' separates the UI from taking actions directly, or, just decouples it in a way that I like. Then, It is up to the 'controller' (brain) to decide what actions to actually take. Having the ability to look at a signal and see that it describes the action flow is fantastic, especially with async flow. Controller.signal('formSubmitted',
setLoading,
[saveForm, {
resolve: [closeModal],
reject: [setFormError]
}],
unsetLoading
); I have never liked the ActionCreator concept from Flux - Why do we need to create an action? (Although I understand the technical reason). Oh, and the Chrome dev tools debugger is so great - being able to see each executed signal with it's corresponding actions and payloads. I urge you to have a play with it yourself. You may be interested in something like You might be interested in https://github.com/garth/ganglion-impulse as middleware, if that is possible. I have not had any experience with Redux yet, but I having been following closely. Thanks for all your amazing contributions :) |
I have not played with Cerebral yet (lol I'm also crazy busy with our 1.0 release) but it looks like Signals correspond to Actions going through Middleware very closely. The only difference so far is that Signals seem a first-class concept, and they are composable. Maybe we can add something composable like
An action creator in Redux isn't that different from a signal. It's just that we don't ship a helper to compose action creators into some kind of a flow. In Redux, there's an additional difference: instead of operating on the model directly, the action is given to the reducer, but in the context of signal composition this doesn't make any difference. |
I've been doing the set loading workflow like this in redux with thunk middleware: export function fetchCampaigns(mode) {
return (dispatch) => {
dispatch(setLoading(true, mode));
getCampaigns(mode)
.then(rows => {
dispatch(setLoading(false, mode));
dispatch(setRows(rows));
})
.catch(() => {
dispatch(setLoading(false, mode));
});
};
} |
Yeah, it seems to achieve a similar goal. Signals are more opinionated (e.g. async = always a promise), but this helps them be concise (array instead of a bunch of waterfall calls). I feel |
@gaearon : With |
@ioss Yes. |
Yes, I see this too in "redux-addons". |
How does Cerebral express a sequence vs a parallel? e.g. controller.signal('formSubmitted',
setLoading,
[saveForm, {
resolve: [closeModal],
reject: [setFormError]
}],
unsetLoading
); looks like a sequence. How do I run |
controller.signal('formSubmitted',
setLoading,
[saveForm, saveOtherForm, {
resolve: [closeModal],
reject: [setFormError]
}],
unsetLoading
); |
So first level is sequential, arrays inside it are parallel. Can you nest sequential inside parallel? Another question is: do the arrays inside How do you pass data between signals? Is there just initial arguments being passed in a sequence? Can signals modify the arguments they output? |
@gaearon , You can not nest sequential inside parallel, at least not now. Have not met a use case for that yet. The arrays after resolve/reject are run synchronously, but if you put an array in there those are run async again. controller.signal('formSubmitted',
setLoading,
[saveForm, {
resolve: [closeModal, [moreAsync]],
reject: [setFormError]
}],
unsetLoading
); Thinking of it, the following syntax might make things more clear: controller.signal('formSubmitted', [ // <- array
setLoading,
[saveForm, saveOtherForm, {
resolve: [closeModal, [moreAsync]],
reject: [setFormError]
}],
unsetLoading
]); So the resolve/reject is just concatenated on to the initial array. Hm... is this clearer? Might consider changing to that as it would allow for "options" on the signal more easily. All actions receives an "args" object where initial signal value, returned action values, resolved action values and rejected action values are merged in. If that makes sense :) |
@christianalfoni What benefits do you find to this kind of control flow DSL compared to imperative code like this? Being able to see the flow in debugger? Not messing with promises manually? |
There ought to be a library that transforms some DSL like |
Somebody please implement |
Although frontend isn't ready for generators IMO.. Maybe something like https://github.com/thunks/thunks instead. |
@gaearon It all actually started out with the debugger. I wanted the developer to understand the flow of the application just playing around with the UI. This led to the initial signals implementation. When Cerebral first got some attention I was surprised that the signals implementation got more feedback than the debugger, it was just a fun and easy way to express the flow of the application :-) So yeah, my conclusion is that I wanted an abstraction that lets you read a flow in your application from start to end without scrolling and jumping into different files. It was intended for the debugger, but the code itself became just as clear. I think the imperative code belongs in the actions, its "lower level" and can be a lot more complex. If that makes sense :) Initially an action had to return a promise to be async. The problem with that is that Cerebral did not understand that an action was async at runtime. The other problem is that you could not clearly see in the signal what actions actually were async. So changing that, using arrays, solved those things. I started with a custom async implementation, but chose to use promises instead... just less work ;-) It would indeed be nice with a standard implementation of this kind of sequence. It is really nice for expressing flow! For the record I just want to mention @marbemac, who helped a lot with discussions and ideas on the current signals implementation. |
The flow definition is a data structure. It allows to treat code as data and manipulate it programmatically. Metaprogramming can be applied to write programs which either generate new flows or take existing flow and modify it to create a new one. It reminds me that one day I should learn some LISP. I assume Cerebral execution layer is exploiting this property to automatically generate action log for replay and removes some of its boilerplate. I think that significant difference from Redux design is that pure actions (aka reducers) can return values directly to async actions which then can continue in flow without having to either move more pure logic code to async actions (action creators) or abuse app state for transient values. It allows Cerebral to potentially replay more code and write logic in better enclosed actions. While Redux sometimes requires to artificially slice it away from reducers or delegate reducers to a dumb state updaters. I tried to better described it in this comment in #291 DSL is double edged sword though. It is similar argument as using html templating language or write it directly in javascript (React). If DSL will be enough expressive to successfully declaratively define logic flow then fine. Otherwise in some cases it will be required to hide flow in actions. Can it express conditional branching? If such branching will be near a top level than I assume the rest of flow would be hidden in an action, no? Today javascript is not that bad and when complex logic is properly factored out to functions then expressing the flow with Promise.then and Promise.all and arrow functions can be terse as well with all the flexibility on hand. But DSL is exactly what gives Cerebral the power to generate action log for replay. If I understand the whole thing... |
@christianalfoni Given the example with "formSubmitted" signal - what would happen if this signal comes twice in a row? Say user unintentionally clicked "submit" button twice. And second signal came when form has already been in "submitting" state. I am curious, because in general case reaction to event is a function of both - current state and event. So it's interesting how cerebral approaches this. |
Hi @vladar, It is an interesting question indeed :-) In this scenario I would set an "isSubmitting" state as part of an action in the signal. That would be used to disable the submit button in the form, avoiding any more clicks as the form is submitting. So the general answer would be to set a state that prevents the possibility to trigger the signal. There is no implementation that allows you to prevent a signal from running "from inside" the signal. I suppose it is a question of what should be responsible for controlling when a signal can be triggered or not. Should it be the signal itself? Or should it be the event that triggers the signal? Currently it is the event that triggers the signal. Let me know if you have other scenarios or if I was unclear on anything here :-) |
@vladap You make a very good point on the signals of Cerebral being an implementation that can describe flow good enough. You always meet some conditionals and currently the Cerebral signal implementation can branch out the result of async actions. So if async action(s) resolve, you can go down the resolve path with actions, or down the reject path with different actions. cerebral.signal('appMounted', [getUsers, getProjects, getTasks, {
resolve: [setUsers, setProjects, setTasks],
reject: [setError]
}]); A Cerebral signal is actually just a list of functions really. So a replay is basically just passing in the same initial argument to the signal and run the functions again. But there is special handling of async functions, one part storing their resolved/rejected value so in a replay it skips running the function and just resolves/rejects immediately. |
For the debugger UI, therea's a slider monitor for Redux. I'm excited to see this cross-pollination between Redux and Cerebral. Very cool. |
The following blog post describes an alternative action > reducers flow with Redux like Reducers, which also gives you a flow overview, a bit like Cerebral? |
Closing as inactive. |
There's been some interesting development on explicit side effects and async control flow in the Redux Saga project. Check it out: #1139 I think it may even allow us to build Cerebral-like async control flow debugger with full information about async actions: redux-saga/redux-saga#5. |
Cerebral by @christianalfoni seems very interesting.
People say:
Is there something we can bring to Redux from Cerebral?
The text was updated successfully, but these errors were encountered: