Fork me on GitHub
#rum
<
2017-07-07
>
Matt Butler11:07:58

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 Butler11:07:30

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

Matt Butler11:07:13

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"]))]

martinklepsch12:07:26

@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

martinklepsch12:07:22

@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 Butler12:07:19

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

martinklepsch12:07:57

it’s not sablono, it’s react

martinklepsch12:07:11

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

Matt Butler12:07:52

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

martinklepsch12:07:44

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

martinklepsch12:07:07

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

Matt Butler12:07:11

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 Butler12:07:04

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

martinklepsch12:07:05

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

Matt Butler12:07:50

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

martinklepsch12:07:15

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 Butler12:07:16

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 Butler12:07:24

React doesnt want keys when i do something like this

Matt Butler12:07:03

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

Matt Butler12:07:50

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

martinklepsch14:07:12

@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

rauh14:07:34

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

martinklepsch14:07:52

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

rauh15:07:15

@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.

rauh15:07:40

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

rauh15:07:21

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

martinklepsch15:07:26

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?

rauh15:07:17

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

martinklepsch15:07:03

@tonsky you have any feelings about this? 🙂

rauh15:07:02

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

rauh15:07:30

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

rauh15:07:42

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

rauh15:07:24

(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? {})

rauh15:07:01

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.

rauh15:07:49

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

martinklepsch16:07:33

@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)))

rauh16:07:13

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

martinklepsch16:07:22

yes, DCE working

martinklepsch16:07:34

but idk, feels complex

rauh16:07:06

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

rauh16:07:39

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

martinklepsch16:07:38

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

martinklepsch16:07:45

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

rauh16:07:30

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