I understand why re-frame has subscriptions and global state. (To make it easy for components to share state and only re-render components' whose state has changed) But what's the benefit of dispatching events to change the db state (I know events can do other things like make API calls) What was the pain point/problem that was addressed by having components dispatch events, instead of directly changing global state? If you an give me an example, that would be great! An alternative question is, what problems would I run into if I ONLY took the app-db, and subscription idea from re-frame and manipulated the app-db directly (without firing events)?
@abhinav.omprakash10 My notes: 1. events model "user intent". Eg. The user wants to delete item with id 456. The user wants to switch to the Customers panel. 2. you don't want your UI knowing how to interpret that intent. You want something else (an event handler) doing that work. The UI only renders the current state. Nothing more. 3. Why? Because interpreting "user intent" is highly mutative and you want that isolated. Global state might get updated, yes, but so too an HTTP request might get initiated or data might get put in localstore or the browser might need to be told to be maximised. Or logs might have to get sent to your open telemetry server. 4. You absolutely don't want this code in your UI components. You want them completely oblivious about what happen. You want them only sending an abstract representation of the users intent. 5. Also, this logic is the most error prone in your application. Because it is mutative. You want it isolated and testable. You want it bite sized and named.
"An alternative question is, what problems would I run into if I ONLY took the app-db, and subscription idea from re-frame and manipulated the app-db directly (without firing events)?"
That ^^ is a nice reframing of the question. Having examined more than a few reactive systems, there does seem to be a well-defined set of problems to solve when altering state directly. We must:
• arrange for the automatic recalculation of computed state, eg layer 3 re-freame subscriptions, or materialised views;
• tend to any necessary side effects. First, we need an API for specifiying those, then we have to see to their automagic execution whenever the associated state changes;
• re-run view functions subscribed directly or indirectly to altered state;
• de-dup all that; and
• carefully order the computations so they never see "stale" state.
If we do all that via some DB API function mutate-app-db function that targets a node of the DAG, ie, with no pre-defined event as a vehicle, we will be fine*. A couple of reactive systems work that way: Hoplon/Javelin, the original MobX (not MST), binding.Scala, and https://kennytilton.github.io/web-mx-quickstart/#/.
* Wild, wild west objection noted! 🤠 There is some increased discipline with a limited set of pre-defined events, and readability from their names alone. Different developers will value those advantages differently compared with the cowboy agility of direct DB mutation.
Btw, I would point people to this exploration of why events are central to reframe...
See also this explanation which also talks about events being the instructions of your app's DSL ...
Thanks for the quick reply. So the event dispatch/event handling is primarily to separate the concerns of views and behaviour? The pain of sharing and synchronising state is evident even in a teeny tiny application and the solution of storing state in a global atom makes sense. But I had a hard time figuring out where events fit in. I understand that there are many other benefits to having an event-driven architecture like replayability and easy debugging.
I think of this similarly to managed effects a.o.t. side effecting from any given function. Much prefer side effecting in a specific way than it be more akin to the Wild West.
When you split your app into folder-per-page structure, where each folder/page has separate subs, events etc. does it make sense to deregister subs of other pages, so that only relevant extractors are executed on each db change? Or is re-frame smart enough to detect which extractors are not needed based on view of the current page?
Neither. Re-frame's subs are just Reagent's reactions that are also cached. The cache is cleared of a reaction if the corresponding subscription is no longer used in a view. When a subscription is not in use by anything, it will not be re-run, just like a Reagent's reaction since "not in use" would also mean "it does not exist in memory".
reg-sub just establishes a mapping between sub ID and sub handler.
subscribe does all the actual work.