Fork me on GitHub
#re-frame
<
2017-07-31
>
Matthew Davidson (kingmob)01:07:15

Hi, I’m trying to build a simple timer app, and struggling to understand where to place the timer. Should the timer (the ID returned by setTimeout) be in :db, or should it be a coeffect stored elsewhere? Also, how should I update it? Should I use :dispatch-later repeatedly to update the timer, or is there a better way?

mikethompson01:07:28

@kingmob the ID is data so its fine to put into app-db. I assume you'd only do that if you wanted to later use clearTimeout? Yes, you can use :dispatch-later in which case I assume you'd not be using setTimeout at all?

mikethompson01:07:35

What are you trying to achieve?

Matthew Davidson (kingmob)02:07:35

@mikethompson Thanks, Mike! Still trying to understand the landscape. Really just trying my hand at a simple egg timer-style app while I learn Re-frame/Re-natal. Are coeffects more for data sources that can’t be easily placed in the in-memory DB? Like randomness, network calls, localStorage, etc?

Matthew Davidson (kingmob)02:07:52

I’m leaning towards dispatch-later to continually issue events, to avoid going “outside” re-frame

mikethompson02:07:19

The simple example has something of a clock in it

mikethompson02:07:34

In repo /examples/simple

Matthew Davidson (kingmob)02:07:14

That uses an external timer to continually issue events though, it doesn’t use :dispatch-later

(defonce do-timer (js/setInterval dispatch-timer-event 1000))

mikethompson02:07:10

Yeah, that method is very simple

mikethompson02:07:36

The :dispatch-later will involve a tiny amount more expertise

mikethompson02:07:18

Your handler for the dispatched later event will have to obtain the time from somewhere

mikethompson02:07:28

That means coeffect

mikethompson02:07:48

Which is fairly trical once you know how ... but it means a steeper curve initially

mikethompson02:07:01

One more concept to embrace

Matthew Davidson (kingmob)02:07:35

Yeah, I’ve been poring over the docs tonight 😋

Matthew Davidson (kingmob)02:07:47

But I was planning on just adding a :time-remaining in the :db, and decrementing it every minute. When it’s <= 0, stop.

Matthew Davidson (kingmob)02:07:30

I don’t care about clock time per se, just elapsed time, relative to when the user pushed a “start” button

mikethompson02:07:10

Very approximately ... (completely untested ... parens probably don't match)

(re-frame.core/reg-event-fx 
    :timer
    (inject-cofx :now)           ;; injects current time via a coeffect
    (fn [coeffect event]
       (let  [now  (:now  coeffect)
              db   (:db  coeffect)      ;; this would all normally be done with destructuring
              span (- now (:started db)]                ;; assuming :started has the initial time
         {:dispatch-later  [{:ms 1000 :dispatch [:timer]}]      ;; make sure we get another timer event in a second
          :db  (assoc db :span span)}))

;; register the :now coeffect
(reg-cofx               ;; registration function
   :now                     ;; what cofx-id are we registering
   (fn [coeffects _]    ;; second parameter not used in this case
      (assoc coeffects :now (js/Date.now))))  

Matthew Davidson (kingmob)02:07:13

@mikethompson Oh wow, thanks! I just want to make sure I understand, though. Imagine the user sets :duration for 8 minutes. They then click on the “start” button. I issue a :start event, copy :duration to a :time-remaining field, and use :dispatch-later once a minute to decrement :time-remaining. Once :time-remaining is <= 0, we stop. Say I don’t care about precision, drift, slew, etc, as long as it’s roughly accurate. In that particular scenario, is the :now coeffect even needed?

mikethompson04:07:24

No, in that case (countdown), the :now coeffect not needed

mikethompson04:07:53

Your method will work fine

mbertheau08:07:22

@nidu I think the pattern is to have :user/fetch signal completion by firing some other event (that will be part of the :user/fetch API). You can then handle that event in your app.

danielneal08:07:59

Sorry if this has been asked a million times already (it seems evidenced by the FAQ #1) https://github.com/Day8/re-frame/wiki/FAQ#1-why-cant-i-access-subscriptions-from-event-handlers but how is it best to manage the case when it looks like using subscriptions in handlers will solve your problem??? To explain: I have deliveries #{{:date "2017-03-03", :product-id 3, :quantity 1} {:date "2017-04-04", :product-id 5, :quantity 2} ...} Sometimes I want to access them by date, so I can make a [:deliveries/by-date] subscription. This will index the subscriptions by date {"2017-03-03", {...}, "2017-04-04", {...} Sometimes I want to access them by product, so I can make a [:deliveries/by-product] subscription. I can access them in my views fine - all is happy However, if I want to use these indexes in my handlers, I should in theory just use functions, but that means recomputing the whole index every time I want to use them, or doing scans, and I could quickly end up with O(n^2) performance. It seems unnecessary because the index is right there in the subscription already. What do other re-framers do for this kinda situation...?

nidu09:07:12

@akiroz thanks for the link, definitely will check out the option. Simplest thing i want is showing loading indicator. Probably i could make per-user loading flag but not sure how good idea is it. Another task is to make some default values in form based on user data.

nidu09:07:10

@mbertheau could you please explain it a bit? You mean :user/fetch emits some other event after completion (like :user/fetch.done)? This way i still can't find out form :form/init that user is fetched without nailing :user/fetch to :form/init. Another option i can think of is passing optional event that user should emit after completion, which looks weird as well.

danielneal11:07:21

ooooh interesting - thanks @mikethompson 😄

mikethompson11:07:56

Actually, I had forgotten that those ideas were put into https://github.com/vimsical/re-frame-utils

danielneal11:07:23

you said back then Repeat, this is untested. And perhaps not even a good idea. - what do you think now (perhaps no differently but thought I'd ask)

danielneal11:07:00

it seems from first glance that it will solve the problem quite neatly

mikethompson11:07:15

There's the thought that maybe it might not be efficient if the subscription does not already exists

mikethompson11:07:01

But (1) the subscription is likely to be cached (2) even if it wasn't, it probably still isn;t going to be any problem

danielneal11:07:31

ooh that other thing track in the library looks useful too - it addresses one of the key other things I've wanted to do from time to time. What do you think of that one? It seems to me that with both of these two things most of my reframe thorny questions/cases are handled...

mbertheau11:07:06

@nidu well, how would you like your :form/init to look ideally?

nidu12:07:08

@mbertheau probably something like this

(rf/reg-event-fx
    :form/init
    [{:keys [db]} [_ user-id]]
    {:db (assoc db [:user-form form-init-state])
     :dispatch [:users/fetch user-id]})
Then i update :user-form state after user is received.

akiroz14:07:46

You don't need a second event after :user/fetch.done for that. Just maintain a boolean for :fetching-user? in your app-db that's managed by the :user/* events and have the views subscribe that value to determine if you show a loading indicator

yedi18:07:05

is there a way to have an event handler update local state? or do handlers only update global state that components can access via subscriptions

yedi18:07:40

i assume i can dispatch an event with a local state ratom as an argument, but i'm guessing that's bad code design in reframe

rnagpal18:07:21

@yedi when you say local state, are you referring to the state of the component ? If the state of the component needs to be changes, and does’t have effect on the outside world, you can just do it in react.

yedi18:07:59

yea the state of the component

rnagpal18:07:05

or just within the component rather tan emitting an event

yedi18:07:42

it needs to talk to the outside world, basically i have a loading icon pop up while a rest request is being performed

yedi18:07:07

the event handler dispatches the request so it has to be in charge of updating the loading part of the state

yedi18:07:48

im wondering if theres a way i can have the event handler directly update the components local ratom or if i just have to use subsriptinos and place the loading state in app-db

rnagpal18:07:55

you can have the following map

{:data [some data]
 :loading false
 :has-errors false }

rnagpal18:07:00

This can be part of the db

rnagpal18:07:16

and you update loading when you make request

rnagpal18:07:23

and subscribe to it in the component

rnagpal18:07:55

which can be then used for showing the loading icon

rnagpal18:07:09

and then you update loading, when you get the response back

yedi18:07:59

yea i was just wondering if i explicitly had to do it that way (adding loading to the app-db)

yedi18:07:49

seems like my app-db is gonna be populated with a ton of what i used to consider local component state

lwhorton20:07:19

can someone help me figure out the correct time to call (or if I should call) clear-subscription-cache with reloading? is it something I want to do in a (reload :js-onload 'my-clearing-ns/clear!) handler, or can I get it injected with the :preloads [my-clearing-ns] config?

Matthew Davidson (kingmob)20:07:28

@mikethompson Thanks! Just now saw your follow-up.