This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-07-31
Channels
- # beginners (9)
- # boot (38)
- # cider (7)
- # cljs-dev (181)
- # cljsrn (49)
- # clojure (136)
- # clojure-italy (44)
- # clojure-losangeles (1)
- # clojure-news (1)
- # clojure-russia (3)
- # clojure-sanfrancisco (1)
- # clojure-serbia (2)
- # clojure-spec (28)
- # clojure-uk (41)
- # clojure-ukraine (1)
- # clojurescript (103)
- # core-async (6)
- # core-logic (46)
- # cursive (5)
- # data-science (8)
- # datascript (6)
- # datomic (5)
- # emacs (35)
- # events (3)
- # jobs (2)
- # jobs-rus (2)
- # juxt (6)
- # lumo (7)
- # off-topic (101)
- # om (6)
- # onyx (6)
- # parinfer (38)
- # pedestal (5)
- # perun (1)
- # planck (4)
- # protorepl (4)
- # re-frame (62)
- # reagent (20)
- # remote-jobs (1)
- # ring-swagger (1)
- # spacemacs (16)
- # unrepl (43)
- # vim (13)
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?
@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?
What are you trying to achieve?
@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?
Yes, correct
I’m leaning towards dispatch-later
to continually issue events, to avoid going “outside” re-frame
The simple example has something of a clock in it
In repo /examples/simple
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))
@mikethompson What would you suggest?
Yeah, that method is very simple
The :dispatch-later
will involve a tiny amount more expertise
Because ...
Your handler for the dispatched later event will have to obtain the time from somewhere
That means coeffect
Which is fairly trical once you know how ... but it means a steeper curve initially
One more concept to embrace
Yeah, I’ve been poring over the docs tonight 😋
But I was planning on just adding a :time-remaining
in the :db
, and decrementing it every minute. When it’s <= 0, stop.
I don’t care about clock time per se, just elapsed time, relative to when the user pushed a “start” button
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))))
@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?
No, in that case (countdown), the :now
coeffect not needed
Your method will work fine
@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.
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...?
@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.
@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.
@danieleneal current state of the art is https://github.com/Day8/re-frame/issues/255#issuecomment-299478925
ooooh interesting - thanks @mikethompson 😄
Actually, I had forgotten that those ideas were put into https://github.com/vimsical/re-frame-utils
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)
it seems from first glance that it will solve the problem quite neatly
It is fine.
There's the thought that maybe it might not be efficient if the subscription does not already exists
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
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...
Both are good
great!
@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.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
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
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
@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.
it needs to talk to the outside world, basically i have a loading icon pop up while a rest request is being performed
the event handler dispatches the request so it has to be in charge of updating the loading
part of the state
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
yea i was just wondering if i explicitly had to do it that way (adding loading
to the app-db)
seems like my app-db is gonna be populated with a ton of what i used to consider local component state
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?
@mikethompson Thanks! Just now saw your follow-up.