Fork me on GitHub
#re-frame
<
2016-02-29
>
amacdougall02:02:16

I'm just getting started with re-frame, and after looking at the example applications, I'm curious how to read the app-db atom directly. It looks like it's not necessary—both sample apps initialize it by merging some initial-data map into the global db, which is always the first argument to every handler. This is fine! And in fact, I can get to the app-db atom as re-frame.db/app-db. I'm just not sure if that's what I should actually be doing.

danielcompton03:02:05

@amacdougall: you really want to be getting app-db data through subscriptions, and changing it through handlers

amacdougall04:02:18

Yep, that's definitely the approach I'm taking. If the debugging advice is to "take a snapshot of the app db so you can replay actions", I guess I'd write it to a known atom as a side effect of a handler at some point? And more generally, if I want to view the current app-db purely for debugging purposes, re-frame.db/app-db is as good (or bad) a way as any.

mikethompson04:02:29

@amacdougall: There's an FAQ entry on the debugging side of this: https://github.com/Day8/re-frame/wiki/FAQ#5-how-can-i-inspect-app-db I mention it for completeness, because it seems you have already mostly answered that question yourself.

mikethompson04:02:54

There is also the not-documented-very-well undo/ redo side of things, which causes snapshots to be taken. It may act as inspiration.

mikethompson05:02:31

@nidu gotcha. I can see what you mean.

mikethompson05:02:21

Sorry, I didn't grok your question correctly. In my mind the general pattern here is:

(rf/register-sub
  :item
  (fn [db _ [id]]
    (let  [token  (cause-database-query-to-happen  :on-success #(dispatch [:write-to  [:some :path])])]
      (make-reaction
        (fn [] (get-in @db [:some :path]))
        :on-dispose #(do (cause-database-query-termination token)
                       (rf/dispatch [:cleanup [:some :path]))))

mikethompson05:02:41

In this pseudo code, I'm following this pattern: 1. We issue a query and organize that the async results are placed into app-db at some location. 2. The subscription returns a reaction to that location 3. Via the on-dispose you cleanup: 3.1 anything about the query itself (which might be a long lasting subscription in rethinkdb, or a one off. 3.2 any data now accumulated in app-db

nidu05:02:48

I think i got it. In my snippet database operations are inside :items/sub and :items/unsub handlers so you could be implicit if there's would be a need to.

mikethompson05:02:57

There are variation on all of this, of course, but you have nailed the salient points in your code.

mikethompson05:02:37

I really should write this up in a Wiki page

mikethompson05:02:11

Thanks for being so persistent, and I'm sorry I wasn;t more help oiginally

mikethompson05:02:40

Remember that you can mix this pattern with dynamic susbscriptions <-------

nidu05:02:20

No worries, that's totally understood. I just found this approach quite elegant and less verbose to track my server sub/unsub needs. Moreover to justify calling dispatchers from subscriptions i can say that data fetching is not actually an action but a workaround for a fact that your data is not in your client app-db yet (just some words for me to sleep better).

nidu05:02:45

Sure, btw in initial snippet (and yours as well) dynamic subscription is used.

mikethompson05:02:25

Oh, yeah. I copied and pasted your code, when creating the pseudo code generalisation, and didn't notice the dynamic subscription

mikethompson05:02:21

And, yes, I regard the arrival of query results to be "an external event".

mikethompson05:02:32

And hence I like it to be "handled" in an event handler

mikethompson05:02:54

BUT, I don't necessarily regard the issuing of the query as an event

mikethompson05:02:04

But this is a very subtle distinction

mikethompson05:02:13

And I'm not at all confident I'm right simple_smile

nidu05:02:18

Moreover i thought that this approach can be applied for traditional data fetching. For example fetching can be called with every new subscription (with some debouncing) though that sounds much more doubtful.

nidu05:02:56

Yeah, that's why in initial snippet i just call :items/sub which itself handles data fetching and calling receiving event.

mikethompson05:02:23

Yep, understand. My approach doesn't do that. Which is why I called out the subtle difference

mikethompson05:02:08

BTW, this pattern has some very nice other subtle properties when dealing with databases like rethinkdb which provide the notion of change feeds.

mikethompson05:02:51

You issue a query ... and then get many updates over time. So you are slowly accumulating state over time.

mikethompson05:02:06

But equally, you need a way to "stop" the query. When the subscription is no longer needed

nidu05:02:25

Sure. Unfortunately we don't have RethinkDB and we handle subscription mechanism in application logic, but that's not of client side interest

mikethompson06:02:28

The other aspect is that you can "tag" queries/susbcriptions. And, then,when you issue a mutating query back to the database, you can tell all queries tagged "x" that they should be rerun, to produce new values.

nidu06:02:38

Here with :on-dispose stopping comes natural which i like. No need to make :component-did-mount and :component-will-receive-props for each smart component.

mikethompson06:02:58

The subscription looks after itself

mikethompson06:02:25

The view is oblivious as to the source of the data

nidu06:02:25

For my task (though it looks quite general to me) there's also generic handler behaviour. There's a counter for each resource identifier (e.g. :items/fetch or :items/fetch-by-foo) and argument list (thanks to clojure equality here it's very simple), which is stored in app-db or elsewhere. Counter is incremented on every sub call and decremented on every unsub call. On first increment (from 0 to 1) server subscription occurs and on last decrement (from 1 to 0) server unsubscription occurs. Shortly it's a simple reference counter.

mikethompson06:02:09

I think that approach will serve you well simple_smile

mikethompson06:02:08

One danger ...

mikethompson06:02:41

If you are using the undo framework, it "undoes" and "redoes" the ENTIRE app-db

mikethompson06:02:01

Which is not quite right if it contains query results

mikethompson06:02:39

Solution: 1. Don't use undo / redo 2. Wait for me to add a patch to re-frame to allow partial redo / undo (excluding certain paths, or only including certain paths)

nidu06:02:04

There's one issue i found so far. Initially i stated that this approach allows easy resource tracking: you can unload resource once it has no corresponding subscriptions anymore. But in my case (don't know about the other once) resource usually have at least two overlapping subscriptions, e.g. :items/fetch and :some-item-parent/fetch-items which totally discards my idea.

mikethompson06:02:10

You could just pass the items from the parent to the children as props

mikethompson06:02:21

And not resubscribe to each item in the children

mikethompson06:02:45

(I think I'm understanding the issue)

nidu06:02:17

Didn't think about undo. Maybe another solution is to store subscriptions in a separate atom? Ideally this atom can be defined with defonce and doesn't need any special treatment as subscriptions will discard themselves but probably it brings some complexities as well.

nidu06:02:54

No, there's no subscription for each children. Imagine some simple master detail. We wanna see a list of items (common info) and one specific item (detailed info). Thus we have to subscriptions, e.g. :items/fetch-by-parent and :items/fetch. Subscribing each item individually would be painful anyway because you also have to track new/deleted items.

nidu14:02:29

Does anybody know if i can use re-frame with reagent 0.6?

tord14:02:43

I think re-frame 0.7.0-alpha is supposed to work with reagent 0.6-alpha, but I haven't tried.

nidu14:02:42

@tord: Thanks, i'll check it out

hjrnunes16:02:17

hi all, I’m trying to use react-bootstrap with adapt-react-class, and I’ve got a component that expects a function to the onClick prop. I’m passing #(dispatch …) as per the re-frame docs, but chrome complains that it’s getting a string rather than a function. The debugger shows that what it gets is, indeed, a string with a single hash: “#” Any ideas of what might be going on?

amacdougall20:02:52

@mikethompson: Thanks for the advice! And more generally, thanks for being so active and helpful on this channel.

mikethompson21:02:30

@tord I'd be interested to hear how you go with 0.6 of Reagent. Although I made the changes necessary to get re-frame working with that version, I haven't ever done much testing.

mikethompson22:02:07

@hjrnunes: we can't help without a code fragment to look at.

mikethompson22:02:01

Although you'd probably be better posting to the reagent channel. I've got a feeling this isn't re-frame specific