This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-11-09
Channels
- # bangalore-clj (1)
- # beginners (158)
- # boot (8)
- # cider (9)
- # cljsjs (9)
- # clojure (169)
- # clojure-austin (1)
- # clojure-denmark (1)
- # clojure-dusseldorf (5)
- # clojure-italy (9)
- # clojure-losangeles (2)
- # clojure-russia (31)
- # clojure-spec (53)
- # clojure-turkiye (1)
- # clojure-uk (56)
- # clojurescript (145)
- # cursive (72)
- # datascript (4)
- # datomic (3)
- # duct (121)
- # events (9)
- # figwheel (1)
- # fulcro (46)
- # graphql (4)
- # hoplon (16)
- # jobs (1)
- # jobs-discuss (4)
- # leiningen (16)
- # lumo (5)
- # off-topic (38)
- # om (1)
- # om-next (5)
- # onyx (104)
- # parinfer (5)
- # re-frame (106)
- # reagent (1)
- # ring-swagger (3)
- # rum (1)
- # shadow-cljs (235)
- # slack-help (4)
- # unrepl (25)
- # yada (9)
hi guys
in my re-frame SPA I wanted to add filters in the urls so that urls can be shared around
and filters stay in
it's not hard to do I think but I think it should be a very common scenario to solve, is there anything semi-ready to use?
it's easy to make re-frame overwrite a value in the db from what I get from the URL query arguments
however I would like to have it synced back if someone changes the filter with the UI
@andrea.crotti I think this is what you need: https://github.com/gf3/secretary
so in that case it gets a bit more complicated
that looks good but not sure I can add that dependency since it's not my project
and how does that help with the bidirectional sync between Re-Frame and the url params anyway?
not sure it would help so much
I think with these 3 little functions
(defn get-url
[]
(-> js/window
.-location
.-href
url))
(defn get-date-from-url
[]
(-> (get-url)
:query
(get "date")))
(defn update-url
[date]
(let [url (get-url)
query (:query url)]
(str
(assoc url :query (assoc query "date" date)))))
I have everything I need
the only thing missing however is to actually overwrite the url as part of a reframe event
What I realized the hard way is that having the event from changing filter settings only update the URL makes for a much less complex solution.
Then hook popstate to dispatch a re-frame event that sets the parameters of the url in the db
That way the data flows in one direction. Trying to keep the db and url in sync caused us a lot of issues before we did it this way.
@kenny I always use nested data structures. For one thing, it makes it easier to modularize initial setup of the app db: I can put all my auth-related data into a db.auth
namespace, and then my top-level init fn can do something like
(reset! app-db {
:auth (db.auth/init-db)
;; ...
}
where db.auth/init-db
is just (def init-db {
… }
I don’t think flat vs nested makes a big difference either way, performance-wise, but I like it much better from a design perspective.
@kenny I am not aware of significant performance differences between flat and nested db. I nest the db primarily for organizational reasons. I’d be curious how others organize the app-db. Below is an overview of how I typically structure it:
:entitities
contains normalized data records from my server DB. I use [subgraph](https://github.com/vimsical/subgraph) to manage records. Under the :server
key I store data related to server interaction. E.g., the key [:server :data :state]
is used to render a spinner.
Under the :ui
key I store data related to the user interface. Each entry can have a [:components]
key to store data for further sub-components, and a [:data]
key to store data for the current ui component.
The last key is [:user]
. Here I store data related to the current user. In this case only the :user/id
in a format that is compatible with subgraph
so that I can load details via subgraph from under the :entities
key.
@raymcdermott I think it is “trodden”ed to some extent at least
https://clojurescript.org/guides/javascript-modules is something I haven’t tried out yet to directly work with npm
Another way I’ve achieved bringing in something like this before was by finding if there is a way to get it as a single js file that you can just copy into your project and point to via :foreign-libs
and then deal with any externs issues
if react isn't a must, can try interop https://github.com/nayuki/QR-Code-generator
@lovuikeng thanks for the link
cool, free of charge too, good to know, thanks @raymcdermott
I believe I already know this, but is it acceptable to return nil from a event handler registered via reg-event-db
I believe the return value is just put under the :db
effect and nil just means do not update the db?
actually, It may replace it with nil now that I look at it. I’m mostly just asking about if it conceptually wrong etc too.
Yes, it is valid according to Effects With No Data https://github.com/Day8/re-frame/blob/master/docs/Effects.md
but thanks for the link. I was just checking on that doc again. I’ve read it all before
yes, should work, further down the doc, the magic comes from "an interceptor, cunningly named do-fx"...
@manutter51 @jhund The performance benefit comes into play when re-rendering. All top level subscriptions rerun whenever the app DB changes. If you have nested subscriptions then you can avoid rerunning additional subscriptions when the app DB changes.
@kenny I’m not sure I follow. As far as I understand, top level subscriptions will always run when anything in app-db changes. No matter how you structure your data.
the performance benefit comes from cascading subscriptions (levels higher than 1) where the inputs to a subscription are other subscriptions, not the entire app-db.
For some reason I thought you were suggesting that nesting the data in app-db would give you a performance benefit. However, you never said that. Sorry.
Ah, ok, I understand now. I use both, but I don’t always use cascading subscriptions where I have nested values, or vice versa
Not necessarily. If you have composite data that changes as a unit, there’s no benefit to cascading subscriptions, and also you can have cascading subscriptions of purely derived data that don’t correspond to what’s stored in the db
I agree, the nesting of data in app-db and the cascading of subscriptions seem to be largely independent.
@kenny you could create a subscription :login-state
which does a select-keys
on app-db
if you wanted. That would then only rerun :login-state
when app-db changes which would be pretty cheap. And then nest everything off that
Really the question was more of how people typically structure their app state in a way that scales.
But that comes at the expense of rerunning every subscription each time the DB changes.
@kenny I think there is still a disconnect. Whether every subscription is re-run each time the DB changes has nothing to do with how app-db is structured, but everything with how you cascade subscriptions.
When you say “But that comes at the expense of rerunning every subscription each time the DB changes”, what do you mean with “that”?
Are you saying that a flat DB means you have to rerun every subscription each time the DB changes?
If you had a subscription that takes one of the get
subscriptions as an input signal, then that may or may not be rerun. But that is not relevant to my original question.
“all subscriptions are simple get
s off the DB” is the only factor determining whether all subscriptions run every time anything in app-db changes.
However the statement “that a flat DB means you have to rerun every subscription each time the DB changes” implies that it has to do with structure of app-db
I keep getting the sense that you suggest the structure of app-db (flat or nested) determines which subscriptions are run when anything in app-db changes.
Based on statements like “a flat DB means you have to rerun every subscription each time the DB changes”
That is not true. But the way you write your subscriptions is often dependent on the structure of the DB.
I think a concrete example would help here. Take this DB:
{:a ""
:b ""
:c/c1 ""
:c/c2 ""}
If you had a subscription for each value in your DB then every time :a
, :b
, :c/c1
, or :c/c2
changes, every subscription is ran. If instead you structured your DB like this:
{:a ""
:b ""
:c {:c1 ""
:c2 ""}}
where the :c
subscription is an input signal for :c1
and :c2
, when the app DB update only subscriptions :a
, :b
, and :c
are ran. Before subscriptions :a
, :b
, :c/c1
, or :c/c2
were all ran.Ok, with a nested DB it’s easier to write a Level 2 (extraction) subscription (https://github.com/Day8/re-frame/blob/master/docs/SubscriptionInfographic.md) if my input signal is a single sub tree of app-db vs. a list of attrs (in a flat db).
While the performance of this small problem is negligible, when you scale it up to a production app of 100s of those the perf starts to become apparent.
That's what I was trying to get across in my original code sample with input signals:
(rf/reg-sub
:login-state
(fn [db _]
(:login db)))
(rf/reg-sub
:email
:<- [:login-state]
(fn [state]
(:email state)))
(rf/reg-sub
:password
:<- [:login-state]
(fn [state]
(:password state)))
One way to split the difference is:
{:email ""
:password "sekrit"}
(rf/reg-sub
:login-state
(fn [db _]
(select-keys db [:email :password])))
(rf/reg-sub
:email
:<- [:login-state]
(fn [state]
(:email state)))
(rf/reg-sub
:password
:<- [:login-state]
(fn [state]
(:password state)))
Then you have your keys at the top of app-db, but only a few subscriptions will fire on change
I'd probably still recommend some light nesting, but is another way to do it
@kenny @jhund I'd summarize the efficiency considerations like this ...
1. avoid a large amount of computation in any Level 2 subscription. Make them trivial (just getters). In which case, it doesn't much matter how many of them there are (within reason), because running them takes no time.
2. "propagation pruning" (of values flowing through the subscription signal graph) happens when the value of a subscription this time
tests =
to the value last time
3. So, to efficiently prune propagation, make the =
checking efficient. (otherwise =
tests could take some time). Ideally, you want the =
test to succeed quickly or fail quickly.
4. Because =
does a shortcutting identical?
check, the best way to get fast =
succees is to make last time
test identical?
to this time
.
Thanks @mikethompson
I edited the above