Fork me on GitHub
#re-frame
<
2019-01-15
>
Twice09:01:17

Hey everybody! I frequently find myself in need of using same (db-manipulating) functions in both subscriptions and events. There is a solution with using an inject-sub interceptor in an event handler, but this approach kinda seems wrong to me. I've read https://purelyfunctional.tv/guide/database-structure-in-re-frame/ where Eric writes about keeping events and subscriptions together (in same namespace/file), because they both need intimate knowledge of the structure of the Database.

Twice09:01:27

That leads me to the next hypothetical approach: extract all db manipulation into separate layer, that both subscriptions and events refer to. Then, when db structure changes, we can isolate those changes in one place. The downside is — our subscriptions and event handlers are now kinda anemic and do nothing except passing values. What do you think of it? Have anyone tried this approach? Is it worth it?

WhoNeedszZz09:01:58

As per the docs (https://github.com/Day8/re-frame/blob/master/docs/SubscriptionsCleanup.md) the recommended method is to create subscriptions that manipulate that data and then subscribe to that "Level 3" subscription handler in the respective event handlers.

WhoNeedszZz09:01:50

There's been some discussion on the splitting of the namespaces and personally I don't recommend splitting them as if you're writing idiomatic re-frame that shouldn't be necessary.

WhoNeedszZz10:01:18

The events do not need intimate knowledge of the structure of the database as that is handled by the subscription if written this way.

Twice10:01:25

So how do you subscribe to Level 3 subscriptions in respective event handler? With inject-sub?

Twice10:01:36

>The events do not need intimate knowledge of the structure of the database That's arguable: whenever I assoc-in something into db I must know exactly the structure of the db.

WhoNeedszZz10:01:08

Subscribing to Level 3 subscriptions is exactly the same as regular subscriptions - re-frame/subscribe.

Twice10:01:25

>Subscribing to Level 3 subscriptions is exactly the same as regular subscriptions That is not recommended way, as described here: https://github.com/Day8/re-frame/blob/master/docs/FAQs/UseASubscriptionInAnEventHandler.md

WhoNeedszZz10:01:14

First, you're responding to what I said above, which is not the quoted text. The quoted text is correct. Second, you're talking about effects (modifying the db), which is re-frame.core/reg-event-fx. What you just referred to is talking about re-frame.core/reg-event-db, which is for pure events, being made impure by throwing a subscription in there when it should have been an effect if it is relying on external data.

WhoNeedszZz10:01:02

That being said the document you referred to has your answer.

WhoNeedszZz10:01:56

However, it appears you're over-complicating things. You should define an atomic effect that does the modification that you want. When that modification is desired you just dispatch that effect.

WhoNeedszZz10:01:52

Effects are supposed to be simple so that you just compose a series of effects for more complicated larger effects.

WhoNeedszZz10:01:07

Level 3 subscriptions are for modifying the data received by the subscription it calls to present it to the original caller. I had a typo in my previous response - subscriptions should not be performing an assoc-in and thus don't need to know that structure for that purpose.

WhoNeedszZz10:01:09

The point of separating subscriptions and events is to reinforce that subscriptions are for querying data for the view and events are for modifying data in response to user events. This follows Command Query Responsibility Segregation (CQRS).

Twice11:01:58

>However, it appears you're over-complicating things. You should define an atomic effect that does the modification that you want. When that modification is desired you just dispatch that effect. Do you mean "event" instead of "effect"? I never mentioned effects in my question, I'm only talking about reg-event-db pure handlers that modifies the Database value. It's just that sometimes before modifying I need to obtain some inner db value as in subscription.

Twice11:01:50

I guess you're suggesting the same thing as in referred document - using inject-sub.

Twice11:01:16

Thanks for your responses!

👍 1
WhoNeedszZz11:01:38

No, I meant it as stated. An event is a response to a user interaction. It may or may not have side effects. An effect is the representation of a side effect. Thus a pure event is an event without side effects and should use reg-event-db. When a side effect is required it should be declared with reg-event-fx and should be a single, atomic side effect. When a larger effect is desired you chain together multiple dispatches of the individual atomic effects to produce the overall effect.

WhoNeedszZz11:01:29

So don't use inject-sub, but rather structure your app into atomic pure events and effectful events. By making atomic effects you can dispatch that effect anywhere it is needed in the view.

WhoNeedszZz11:01:46

Glad to be of assistance. Re-frame provides a specific mental model that can take a little bit to understand, but once you understand it the dominoes start to fall in place.

WhoNeedszZz11:01:56

Ok, after re-reading what you said above I guess I wasn't exactly sure what you were asking. Can you give a more specific example?

Twice11:01:46

I think you misunderstood me - when I said "modify db" I meant the db that reg-event-db uses and returns, not some database somewhere on the server-side. So I was talking only about pure events as in "without side-effects, only modifying local re-frame db'. No effects at all 🙂

WhoNeedszZz11:01:02

So what specifically are you trying to do here?

Twice12:01:01

Example: I make some calculations in my subscription to retrieve a value from re-frame db. Now, in reg-event-db handler I need to get the same value from db before modifying it. I don't want to duplicate same logic twice - what are my options?

Twice12:01:03

1. Inject subscription into event. 2. Make some additional layer that deals with all the calculations and transformation of db, and make both subscriptions and events use that layer.

WhoNeedszZz12:01:26

Neither of those

WhoNeedszZz12:01:38

Can you give me a specific example?

WhoNeedszZz12:01:51

More specific as in what does that section of your db look like and what specific problem are you trying to solve?

Twice12:01:07

(reg-sub :can-save?
  (fn [db]
  	(let [editor (:editor-params db)]
  	  (and (not (empty? (:created-items editor)))
           (or (and (= :add (:mode editor))
                  	(some? (get-in editor [:item-wip :id])))
               (= :edit (mode editor)))))))

Twice12:01:16

Suppose I have some subscription with calculation or logic like that

Twice12:01:45

Now in my reg-event-db handler I need to obtain that :can-save? value before modifying the db.

Twice12:01:23

>Neither of those I guess there is third option then?

Twice12:01:09

(reg-event-db
  :approve-current-edit
  (fn [db _])
  	(let [can-save? ???))
  	  (if can-save?
  	  	(assoc db :result :success)
  	  	(assoc db :errors ...)
  ...

WhoNeedszZz12:01:16

First, you can insert code snippets that can specify Clojure syntax highlighting to make that more readable. For whatever reason the triple backtick code blocks don't support Clojure. Second, you are over-complicating subs. The sub should just return (:editor-params db) and be called something like ::editor-params. In your view component you subscribe to that and pass the dereferenced value as a parameter to your event. In the event you determine if the user can edit based on the contents. The subscription should not be making that determination; only subscribing to the data.

WhoNeedszZz12:01:52

A Level 3 sub simply modifies the data returned from another sub in a form that is more usable by the component referencing that sub (which is not applicable here - just explaining for reference).

Twice12:01:40

I actually use level 3 subs and signals a lot, perhaps my example was a little bit convoluted. Still, there are situations when event handler needs to calculate intermediate data before making a modification to db. I don't like at all passing that data as parameters to event handler. But thanks for the third option.

WhoNeedszZz12:01:56

It's the appropriate way to handle this so I'm not sure what to tell you. What's your issue with passing a value to the event handler?

Twice12:01:58

>The subscription should not be making that determination; only subscribing to the data. What if I need to make button disabled based on sub determination or display some label like "can't save now"?

WhoNeedszZz12:01:09

Herein lies the issue with you not stating your actual problem you're trying to solve. I'm suggesting things incorrectly based on how you are framing the question.

WhoNeedszZz12:01:44

What is the actual logic behind whether or not the user sees the edit button?

WhoNeedszZz12:01:08

What are you even doing here?

Twice12:01:58

The question is more general, and I've already asked it: I make some calculations in my subscription to retrieve a value from re-frame db. Now, in reg-event-db handler I need to get the same value from db before modifying it. I don't want to duplicate same logic twice - what are my options?

Twice12:01:57

I don't need precise advice for particular situation - I want to know general approach to this kind of problems.

Twice12:01:48

So I guess there are three options, with passing data as a parameter to event handler as the third option.

Twice12:01:08

I guess I'm not the only one that stumble upon such request, otherwise there wouldn't be this document at all . https://github.com/Day8/re-frame/blob/master/docs/FAQs/UseASubscriptionInAnEventHandler.md

Twice12:01:28

Are you trying to tell me that this kind of situation (using same logic in subs and events for getting data from db) shouldn't arise in the first place?

WhoNeedszZz13:01:25

No, I was asking what your code is doing. You left out a bunch of critical details that are required to properly answer your question.

WhoNeedszZz13:01:35

That document is answering a specific question - "How do I access the value of a subscription from within an event handler?" It states that you shouldn't use a subscription in an event handler and proposes an alternate solution for that specific question. It also states that it is something not implemented by re-frame and suggests a 3rd party that has that implementation. The fact that it is not implemented in re-frame.core should be an indication that it is something you can do, but is not idiomatic. You can instead re-structure your mental model of your code that would obviate the need for such a solution.

👍 1
Twice14:01:55

> You can instead re-structure your mental model of your code that would obviate the need for such a solution Ok, now I've got what you mean. Thanks!

👍 1