Fork me on GitHub
#re-frame
<
2020-06-30
>
ozfraier08:06:52

Hey, I'm wondering if my code is an anti-pattern I have a table in my app database that get populated with up to hundreds of keys representing some data tags. I need to be able to subscribe to these tags but manually writing an individual function for every tag didn't make sense as all of them would've been identical besides the tag name, so I wrote a function to register the subs for all the tags.

(defn register-subscription-for-tag
  "make a sub for a tag"
  [tag]
  (re-frame/reg-sub
   (keyword tag)
   (fn [db _]
     ((keyword tag) (:tag-table db)))))
But this still creates hundreds of subs, that are only for a simple get and it feels like an overkill, I'm suspecting this is part of the reason why tools like re-frame10x and re-frisk get into some performance issues*. I was thinking of using dynamic subscriptions, but I'm not sure if its not deprecated and going to be removed soon. Do you think the current function is legit or is there a better solution? *well another suspect is too many events dispatched to send and recieve websocket data, but maybe it is the hundreds of subs.

p-himik08:06:29

I'd say it's not an antipattern if you create all the subs during your app initialization and not during its regular operation. AFAIK the dynamic subs have been deprecated because there's no need for them - you can chain subs directly:

(let [x @(subscribe [:x]), y @(subscribe [:y x])] ...)

ozfraier09:06:18

Thank you! Well, the code works fine, but I needed a sanity check πŸ™‚ It looks like the debug tools are bothered by the large amount of events, so I'll try configure them to ignore some of them.

me198703:07:20

I think I'm a bit confused... subscriptions can take an argument. I feel like you should be able to do

(rf/reg-sub
 ::get-tag
 (fn [db tag]
   (get db tag)))

(defn my-conponent []
  (let [data @(rf/subscribe [::get-tag :my-tag])]))

me198703:07:22

That way you only have one sub, and even if tags are added dynamically, you can easily subscribe to new ones.

setzer2209:06:16

I'm interfacing with a react component (Reactour https://github.com/elrumordelaluz/reactour), that expects some props with arbitrary html content. And I don't know how to pass them in clojurescript. In React, they'd create the component as:

<Tour
  ...
  steps=[{
    selector: '[data-tut="reactour__iso"]',
    content: `Ok, let's start with the name of the Tour that is about to begin.`
  }]/>

setzer2209:06:21

In clojurescript, I do this, and it works fine:

[:> Tour 
  {...
   :steps [{:selector "...", :content "..."}]}]

setzer2209:06:11

however, the library also allows passing in DOM elements as part of the content, like so:

<Tour
  ...
  steps=[{
    selector: '[data-tut="reactour__iso"]',
    content: <button>Click me!</button>,
  }]/>

p-himik09:06:02

Use reagent.core/as-element.

setzer2209:06:50

thanks! that worked πŸŽ‰

setzer2209:06:52

but if I pass the equivalent hiccup map [:button "Click me!"] in clojurescript, that gets coerced to a string and rendered as text. How can I make it so whatever I pass in as content gets converted to DOM elements?

phil63410:06:27

Has anything changed in the last year or so that would cause the following code to not return a function that returns the expected values? (re-frame/reg-sub :cell (fn [db [_ x y]] (reaction (get-in @db [:board [x y]])))) Where :board in db is demonstrably something like :board {[2 0] :x, [2 1] :o, [2 2] :x, [1 2] :o, [1 1] :x}

mikethompson10:06:22

That would never have worked.

mikethompson10:06:39

Perhaps if you substitute reg-sub for reg-sub-raw ?

phil63410:06:13

OK, it's register-sub in the code I'm trying to re-engineer

phil63410:06:06

Fabulous πŸ™‚

phil63410:06:13

Thanks so much Mike

phil63410:06:22

So register-sub is deprecated for reg-sub and reg-sub-raw?

mikethompson10:06:38

It will still work, but you'll get a depricated warning

mikethompson10:06:01

You should just rename to reg-sub-raw

phil63410:06:55

didn't work in my context, but -raw does. I suspect the stuff I'm following isn't quite idomatic Re-frame though based on the 'you should probably be using reg-sub' note in the docs.

mikethompson10:06:56

or rewrite slightly and use reg-sub

mikethompson10:06:53

(re-frame/reg-sub                   ;; change name 
 :cell
 (fn [db [_ x y]]                   ;; get rid of the `reaction` code
   (get-in db [:board [x y]]))))    ;; remove @ in front of db

ozfraier12:06:14

Hey, that's what I was looking for πŸ™‚

ozfraier07:07:04

What I was looking for, but not what I needed

phil63410:06:47

Ooh OK. That's much more understandable

phil63410:06:15

thanks. I was getting hung up on trying to understand why it needed an explicit call to reaction

phil63410:06:14

Thanks for the help !

pavel.klavik13:06:51

Hi, I am rewritting subscriptions within our startup (http://orgpad.com) and I have some general questions. I started with very simple subscriptions which were too complicated and recomputed frequently. The main model is a graph where I have two maps, one from UUIDs to nodes and the other from UUIDs to links, and each value is a further map containing multiple properties. I used to have a single subscription for each node and each link which was recomputed whenever any property changed (label, color, position, even when it was dragged by a mouse). Currently, I switched into multiple subscriptions, for individual properties. For instance, when I press a mouse button on anything, there is one subscription for that, used by another subscription saying that the press occured on a node, and then a subscription for each node saying that the press occured on this particular node. And only the last subscription influences the node position, but nothing unrelated in the map. Less should be recomputed right now, but the exact tradeoff is not clear to me. 1. What is the extra cost of running a single small subscription? 2. How much slower is to use @(subscribe ...) within a subscription? I am using this sometimes when parameters of a subscription depend on some value extracted by other subscriptions. For instance, a link contains a pair node ids which it connects, so if I want to now its start and end position, I will first obtain these ids and then dynamically subscribe to their positions. 3. Is there a good way to analyze performance of subsciptions?

pavel.klavik13:06:51

Hi, I am rewritting subscriptions within our startup (http://orgpad.com) and I have some general questions. I started with very simple subscriptions which were too complicated and recomputed frequently. The main model is a graph where I have two maps, one from UUIDs to nodes and the other from UUIDs to links, and each value is a further map containing multiple properties. I used to have a single subscription for each node and each link which was recomputed whenever any property changed (label, color, position, even when it was dragged by a mouse). Currently, I switched into multiple subscriptions, for individual properties. For instance, when I press a mouse button on anything, there is one subscription for that, used by another subscription saying that the press occured on a node, and then a subscription for each node saying that the press occured on this particular node. And only the last subscription influences the node position, but nothing unrelated in the map. Less should be recomputed right now, but the exact tradeoff is not clear to me. 1. What is the extra cost of running a single small subscription? 2. How much slower is to use @(subscribe ...) within a subscription? I am using this sometimes when parameters of a subscription depend on some value extracted by other subscriptions. For instance, a link contains a pair node ids which it connects, so if I want to now its start and end position, I will first obtain these ids and then dynamically subscribe to their positions. 3. Is there a good way to analyze performance of subsciptions?

jahson18:06:44

Looks like for p 2. you probably should read about Layer 3 subscriptions https://github.com/day8/re-frame/blob/master/docs/subscriptions.md#reg-sub

pavel.klavik19:06:05

I am familiar with this level of description, but I would like to get a more advanced insight.

jahson07:07:30

I thought that that you was using subscribe in the main body of reg-sub, which is not the way it was intended to be. As for Layer 3 subsription performance I think it depends. According to @

And, to answer you question, layer 3 are not about less rendering (in general).  The propagation pruning layer is Layer 2.  Layer 3 is about performing the the expensive computation.

pavel.klavik10:07:48

So if you can't use subscribe in the main body of reg-sub, how can you achieve what I described in 2?

jahson10:07:31

It's like

;; A Layer 3 sub with one signal
(rf/reg-sub :a-layer-3-sub
  ;; signal function
  (fn [[subscription-id arg1]]
    (rf/subscribe [:other-sub arg1]))
  ;; main body
  (fn [other-sub-result [subscription-id arg1]]
    (computation-based-on other-sub-result)))

;; A Layer 3 subscription syntactic sugar version
;; NOTE: syntactic sugar version cannot make use of subscription arguments
(rf/reg-sub :another-layer-3-sub
  ;; signals
  :<- [:another-sub]
  ;; main body
  (fn [another-sub-result [subscription-id arg1]]
    (computation-based-on another-sub-result)))

;; A Layer 3 sub with many signals
;; NOTE: signal function can also return a map, or list.
(rf/reg-sub :a-layer-3-sub
  ;; signal function
  (fn [[subscription-id arg1]]
    [(rf/subscribe [:other-sub-1 arg1])
     (rf/subscribe [:other-sub-2 arg1])])
  ;; main body
  (fn [[other-sub-1-result other-sub-2-result] [subscription-id arg1]]
    (computation-based-on other-sub-1-result other-sub-2-result)))

pavel.klavik10:07:30

But what if I need to subscribe to a subscription, whose parameter depends on some other value I obtain from another subscription.

pavel.klavik10:07:00

Say, in my case, a link has two endpoints and only need to depend on their positions and not on position of all units.

pavel.klavik10:07:41

I could in principle obtain the ids of endpoints in my view and send them to another subscription, but that seems incorrect to me since subscription logic will be leaking into my views.

pavel.klavik10:07:50

Using @(rf/subscribe [:node/pos endpoint-id]) works within the main body of my subscription. So my question is what is the downside of using it like this?

p-himik12:07:50

No downsides. As to your question about feeding sub value into another sub, you can just use reg-sub-raw for that.

p-himik12:07:23

Ah, sorry, I missed the fact that you deref inside the body of a subscription and not a view. Here, I'm not sure.

pavel.klavik13:07:33

So basically the correct way would be to use reg-sub-raw. I will take a look at it how it works precisely.

jahson19:07:42

> Using @(rf/subscribe [:node/pos endpoint-id]) works within the main body of my subscription. So my question is what is the downside of using it like this? There's a possibility of memory leak because derefs are tracked and it is possible that subscription won't be disposed.

pavel.klavik20:07:58

Ok, so I read this http://day8.github.io/re-frame/flow-mechanics/ and using reg-sub-raw instead makes sense for my case. Btw. I am missing a real example which would need reg-sub-raw in the text. The example here is better: https://github.com/day8/re-frame/wiki/Dynamic-Subscriptions

jahson07:07:54

I think reg-sub-raw is more advanced, so it is not in the main docs. But better to ask @

steedman8718:06:17

quick question: when looking at traces in re-frame10x, what is the :raf operation?

steedman8723:06:58

I think it’s request animation frame

steedman8723:06:03

what exactly does that mean though?

r.hamers19:06:28

Hey guys I'm writing a reagent/re-frame frontend for a reitit (example) backend: https://github.com/metosin/reitit/tree/master/examples/ring-spec-swagger I'm currently writing the frontend to upload a file to the backend but I cannot get the upload Ajax request setup properly. I have created a form with `enc-type multipart/form-data' and send the formdata object via a re-frame event handler. I think the js/formdata is not set correctly. The response has error-code 400 with data:

{"spec":"(spec-tools.core/spec {:spec (clojure.spec.alpha/keys :req-un [:spec$10934/file]), :type :map, :leaf? false})","problems":[{"path":[],"pred":"(clojure.core/fn [%] (clojure.core/contains? % :file))","val":{},"via":[],"in":[]}],"type":"reitit.coercion/request-coercion","coercion":"spec","value":{},"in":["request","multipart-params"]}
Upload form:
(defn upload-component []
  [:div
   [:form {:id       "upload-form"
           :enc-type "multipart/form-data"
           :method   "POST"}
    [:label "Upload Filename: "]
    [:input {:type "file"
             :name "upload-file"
             :id   "upload-file"}]]])
Function Invoked when a button is pressed:
(defn prepare-form-data [element-id]
  (let [el (.getElementById js/document element-id)
        name (.-name el)
        file (aget (.-files el) 0)
        form-data (doto
                    (js/FormData.)
                    (.append name file))]
    (println el)
    (println name)
    (println file)
    (println form-data)

    (rf/dispatch [:request-files-upload {:file form-data}])
    ))
Event handler:
(rf/reg-event-fx
  :request-files-upload
  (fn-traced [{:keys [db event]} [_ a]]
             (let [params (second event)
                   url (str (:backend-url environment) "/files/upload")]
               {:http-xhrio {:method          :post
                             :uri             url
                             :timeout         8000
                             :format          (ajax/json-request-format)
                             :response-format (ajax/json-response-format {:keywords? true})
                             :params          params
                             :on-success      [::good-files-upload-result]
                             :on-failure      [::bad-files-upload-result]}})))

p-himik19:06:06

None of this matters because we don't know what you server expects. HTTP 400 means "Bad Request" which, in turn, "indicates that the server cannot or will not process the request due to something that is perceived to be a client error".

p-himik19:06:25

So it mostly depends on your server and its configuration.

p-himik19:06:13

Ah, I misunderstood the purpose of the topmost code block. Seems like you're sending your server upload-file but it expects file.

p-himik19:06:33

Or maybe I'm misunderstanding it again (sorry, a bit late here). It wants a multipart request from you, but you're sending it a JSON request - maybe that's the issue.

r.hamers03:07:58

Yeah, It is set as Json in the json. When I set the :content-type to multipart/form-data it wont work.

r.hamers03:07:17

when I try the same with Insomnia. Set the content type to multipart-form-data it wont work. ( when I test through the browser just running the swagger UI it does work)

r.hamers03:07:16

ok, got it working in insomnia now

r.hamers04:07:56

-----------------------------175945971223698693323893870064
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

ajsdnasd 
-----------------------------175945971223698693323893870064--

r.hamers04:07:24

getting a 500 error file upload exception

r.hamers04:07:45

getting closer:P

r.hamers04:07:06

So this is how the request looks like in insomnia butI cant get it working in cljs through cljs-ajax :

(client/post "" {:multipart [{:name "file"
                                                                :content "ajsdnasd
                                                                    "}]})

p-himik06:07:53

Try not specifying :format at all and instead of :params pass an instance of https://developer.mozilla.org/en-US/docs/Web/API/FormData as :body.

p-himik06:07:07

That's how I do it.

ruben_hamers06:07:43

ok, ill give it a try πŸ™‚

r.hamers03:07:42

alright, damn; finally fixed it. my formdata and request setup was correct, except for my headers.. I set

{:content-type "multipart/form-data"}
explicitly. So, for other readers, now my re-frame ajax handler looks like:
(rf/reg-event-fx
  :request-files-upload
  (fn-traced [{:keys [db event]} [_ a]]
             (let [params (second event)
                   url (str (:backend-url environment) "/files/upload")]
               (println params)
               {:http-xhrio {:method           :post
                             :uri              url
                             :timeout          8000
                             :response-format  (ajax/json-response-format {:keywords? true})
                             :body params
                             :on-success       [::good-files-upload-result]
                             :on-failure       [::bad-files-upload-result]}})))
Thx for the help @