rum

Simple, decomplected, isomorphic HTML UI library for Clojure and ClojureScript | 0.12.8 https://github.com/tonsky/rum/blob/gh-pages/CHANGELOG.md#0128
Matt Butler 2017-07-07T11:44:58.138877Z

Hi, not sure if this is the best place to ask about sablono behaviours so I apologise beforehand. I want to ask about the behaviour of list in sabolono and how to return multiple elements from a form (for example a conditional)

[:div
   [:span "a"]
   [:span "b"]]

valid, no errors

----
[:div
   [:span "a"]
  (list [:span "b"]])

react, unique key error 'Each child in an array or iterator should have a unique "key" prop'

fixed by doing 

[:div
   [:span "a"]
  (list [:span {:key "b"} "b"]])
` Why is this? both produce identical html? Is list seen as an iterator?

Matt Butler 2017-07-07T11:54:30.306061Z

Happens if i swap the span within the list to a diff element (such as p or div).

Matt Butler 2017-07-07T11:58:13.372176Z

Is there some rule where all elements returned from list must have a unique key, or be lists themselves? That doesn't seem right because the following doesn't error.

[:div
   [:span "a"]
   (list [:span {:key "b"} "b"] (list [:span "x"]))]

or even

[:div
   [:span "a"]
   (list [:span {:key "b"} "b"] (list [:span "x"] [:span "y"]))]

martinklepsch 2017-07-07T12:18:26.753829Z

@mbutler sequences are seen as similar items of the same kind and so they do need a key for effective reconcilaition inside the DOM tree

martinklepsch 2017-07-07T12:19:22.772037Z

@mbutler you can trick react into not seeing this if you use (into [:div] (list [:span "a"] [:span "b"])) but generally it’s not a bad idea to provide react-keys when you render many things of the same kind

Matt Butler 2017-07-07T12:24:19.871368Z

Okay, that makes sense @martinklepsch, so its sabolono eagerly assuming that what i return from my list are similar elements even if they are not?

[:div
   [:span "1"]
   (list [:span "a"] [:div "b"] [:p "c"]) ]

Error 

----

[:div
   [:span "1"]
   (list [:span {:key "a"} "a"] [:div {:key "b"} "b"] [:p {:key "c"} "c"]) ]

No error

martinklepsch 2017-07-07T12:24:57.883982Z

it’s not sablono, it’s react

martinklepsch 2017-07-07T12:25:11.888775Z

if react sees a list it assumes they are similar items, and thus need a key

Matt Butler 2017-07-07T12:25:52.902710Z

But in reality doesnt actually need it? Or as far as reacts concerned they ARE similar because they are in a list?

martinklepsch 2017-07-07T12:26:44.920752Z

if you only have 3-10 items in the list the need might be debatable depending on the items own complexity

martinklepsch 2017-07-07T12:27:07.928299Z

but yes, as far as react is concerned items in a list are similar and thus should have a key

Matt Butler 2017-07-07T12:27:11.929635Z

I think i might be missusing list then. All i wished to do was return multiple items from a conditional rather than repeating the conditional, they are in no way actually a list of things.

Matt Butler 2017-07-07T12:28:04.947399Z

[:div
   [:span "1"]
   (when test (list [:span "a"] [:div "b"] [:p "c"]))] 

martinklepsch 2017-07-07T12:28:05.947539Z

(into [:div]
      (if false
        [[:span “foo”]]
        [[:span “bar”]
         [:span “xxx”]]))

Matt Butler 2017-07-07T12:28:50.962930Z

So you think its ok to trick react if i know what im doing is not returning a "list" ofthings

martinklepsch 2017-07-07T12:31:21.016339Z

sure

martinklepsch 2017-07-07T12:32:15.035168Z

I think the “this needs a key” warnings are intended as guidance not hard rule, it’s a machine interpreting your code after all, can’t know your intent

Matt Butler 2017-07-07T12:34:16.077510Z

I think thats a very "fair" statement. I have a followup question thats slightly related, now I think i understand what list is doing

(map #(list [:br] [:span %]) ["a" "b" "c"])

Matt Butler 2017-07-07T12:34:24.080522Z

React doesnt want keys when i do something like this

Matt Butler 2017-07-07T12:35:03.093497Z

it seems like that should fall under the same rule as the previous one.

Matt Butler 2017-07-07T12:35:18.098775Z

@martinklepsch

Matt Butler 2017-07-07T12:35:50.109915Z

is it just an oversight? when you return interleaved stuff like this does react want keys it just doesnt know it?

martinklepsch 2017-07-07T14:58:12.472600Z

@rauh if you don’t mind, could you check if I did everything as you suggested here: https://github.com/tonsky/rum/pull/145/files ? I get DCE to work but only if I remove the specify! call

rauh 2017-07-07T14:58:34.485976Z

@martinklepsch Yeah I'm looking at it as we speak

martinklepsch 2017-07-07T14:58:52.496661Z

@rauh nice, thank you very much and sorry for bugging 🙂

rauh 2017-07-07T15:22:15.335108Z

@martinklepsch Well shoot, I must've tested it incorrectly, GCC probably inlined the string so I didn't find it and thought it was DCE'd.

rauh 2017-07-07T15:22:40.350547Z

It looks like the only way to do this is to create a custom Datatype just like MetaFn and realize the meta data lazily

rauh 2017-07-07T15:23:21.374737Z

So (LazyMetaFn. f c) which then returns the meta of (c) in IMeta

martinklepsch 2017-07-07T15:24:26.414457Z

Right. So given that this whole rum/class metadata thing isn’t documented and seems generally troubling wrt DCE — maybe it should be considered for removal?

rauh 2017-07-07T15:25:17.444614Z

Yeah just put in an API, thats why we have API's so we can change the underlying implementation 🙂

martinklepsch 2017-07-07T15:26:03.473244Z

@tonsky you have any feelings about this? 🙂

rauh 2017-07-07T15:41:02.002411Z

@martinklepsch I think I got it. GCC really doesn't like IIFE's

rauh 2017-07-07T15:41:30.018861Z

Try exporting the f (fn ...) let into a separte function with only one binding in the let (thus avoiding IIFE).

rauh 2017-07-07T15:41:42.025700Z

Then specify! in there and call that function as the last statement.

rauh 2017-07-07T15:42:24.048822Z

(defn hmmmmmm [c]
  (let [f (fn []
            (let [ctr (js* "~{}()" c)]
              (.apply ctr ctr (js-arguments))))]
    (js* "~{}.fooooooo = 1;" f)
    f))
(defn is-this-side-effecting? [spec]
  (let [bf #(-> spec) ;; Avoid IIFE
        c (gf/cacheReturnValue bf)]
    (hmmmmmm c)))
(is-this-side-effecting? {})

rauh 2017-07-07T15:43:01.069854Z

So by not using hmmm (great name, right?) and putting it within the let of the lower function you'll get an IIFE and GCC doesn't know it's "just" a function and won't DCE.

rauh 2017-07-07T15:43:49.097771Z

The first binding in a let is always good and CLJS won't IIFE it. Hence why it won't work further down.

martinklepsch 2017-07-07T16:03:33.781978Z

@rauh ok, managed to reproduce that

(defn- set-meta [c]
  (let [f (fn []
            (let [ctr (js* “~{}()” c)]
              (.apply ctr ctr (js-arguments))))]
    (specify! f IMeta (-meta [_] (meta (c))))
    f))

(defn lazy-build [ctor render mixins display-name]
  (let [bf #(ctor render mixins display-name) ;; Avoid IIFE
        c  (goog.functions/cacheReturnValue bf)]
    (set-meta c)))

rauh 2017-07-07T16:04:13.804067Z

@martinklepsch Ie, you got it to DCE? Yay!

martinklepsch 2017-07-07T16:04:22.809085Z

yes, DCE working

martinklepsch 2017-07-07T16:04:34.816050Z

but idk, feels complex

rauh 2017-07-07T16:05:06.833748Z

Well, IMO it's rather nice. GCC correctly detects that all the fn's are not side effecting

rauh 2017-07-07T16:05:39.851940Z

You don't need to write the js*, it's just an optimization I have running locally

martinklepsch 2017-07-07T16:08:38.950465Z

Whats the difference between (js* "~{}()" c) and (c)?

martinklepsch 2017-07-07T16:13:45.116893Z

re complexity: yes maybe the solution is nice but the sole reason for it is a feature that isn’t part of the public API

rauh 2017-07-07T16:17:30.234153Z

@martinklepsch (c) will check for the IFn to be there when you enable :static-fns.

martinklepsch 2017-07-07T16:20:11.317753Z

I see