Fork me on GitHub
#reagent
<
2016-03-06
>
smw06:03:24

Hey folks, I get a

Warning: Every element in a seq should have a unique :key
message with my reagent component tree, but doing, for instance
[:div {:key ‘unique-string’} …]
on each element in list doesn’t seem to (a) render or (b) make the warning go away. What am I missing?

mikethompson06:03:37

As an experiment, what happens if you do this: [:div {:key (gensym "key-")} …]

mikethompson06:03:18

That will actually give you unique keys. But don't keep this code. Its just an experiement.

mikethompson06:03:39

Apart from that experiment, it is very hard to answer your question without a gist.

juliobarros06:03:11

@smw have you tried ^{:key unique-string} [:div .... ]

smw06:03:06

I haven’t! Metadata? Awesome.

smw06:03:12

Thank you

smw06:03:34

@mikethompson: my actual code didn’t just specify ‘unique string’, btw… it was a two element list of maps, and I was using the static, but unique, name key

mikethompson06:03:22

Keys provided via metadata should be identical to keys provided via "first map arg".

mikethompson06:03:39

@smw yeah, I was suggesting a change to gensym so you absolutely knew for sure that you are using different keys .... because the warning you report says that you are providing duplicate keys. In effect, the warning is telling you that name is not unique (among sibling divs)

smw06:03:19

I tried a static one with no variables and observed the same behavior

smw06:03:06

I can change also change the dynamic one to [:div {:data-key (:name data)}] and get unique data-key attributes.

smw06:03:28

However, neither setting key in the argument map or via metadata make the warning go away.

mikethompson06:03:02

Could that be because :name is not unique? After all, that's what the warning is telling you.

smw06:03:05

setting a :key attribute just doesn’t appear to do anything.

smw06:03:43

Dude, there are two elements, if I change the same code and set the :data-key attribute instead of :key, I get two unique :data-key attributes.

smw06:03:55

They’re unique.

smw06:03:05

However, setting :key doesn’t render anything.

mikethompson06:03:55

If they are unique, why do you have a message saying they aren't?

smw06:03:17

Because in the rendered output neither element has a :key attribute.

smw06:03:32

that means they don’t have unique :key attributes.

smw06:03:44

You can keep pushing me on this, but you’re barking up the wrong tree.

cjmurphy06:03:15

A bit laborious but obviously you can see exactly what's there from the Chrome browser console. Often I'

cjmurphy06:03:53

ve found $null to be the duplicate...

smw07:03:17

Yeah, I’m just very confused on this. I’m looking in the chrome browser console. I can get two (component) elements where, for instance

<div data-key=“tw-r01”/><div data-key=“tw-r02”/>

smw07:03:10

but if I change

[:div {:data-key (:name data}]
to
[:div {:key (:name data)}]

smw07:03:23

<div/><div/>

mikethompson07:03:03

And what happens when you perform the experiment suggested above?

smw07:03:24

Metadata?

smw07:03:25

which one?

smw07:03:29

gen-sym?

mikethompson07:03:33

I only suggested one

smw07:03:09

defn tree-server [server]
  [:div {:key (gensym "key-")}
   [:p (:name server)]])

(defn tree-rack [rack]
  [:div {:key (gensym "key-")}
   [:p "Rack: " (:name rack)]
   [:p "Servers:"]
   (for [server (:servers rack)]
         [tree-server server])])

(defn tree-test []
  (let [racks [{:name "tw-r01" :servers [{:name "tw-r01-s01"}, {:name "tw-r01-s02"}]},
               {:name "tw-r02" :servers [{:name "tw-r02-s01"}]}]]
    [:div {:key (gensym "key-")}
     (for [rack racks]
       [tree-rack rack])]))

smw07:03:22

Warning: Every element in a seq should have a unique :key (in rgnt.graphics.tree_test > rgnt.graphics.tree_rack). Value: ([#object[rgnt$graphics$tree_server "function rgnt$graphics$tree_server(server){

mikethompson07:03:43

A gist makes it so much easier ....

mikethompson07:03:14

You are putting your keys in the wrong place

smw07:03:30

ahhh. ok. where should they be?

mikethompson07:03:36

An easy mistake to make

mikethompson07:03:55

So ... you'll need to do this . . .

mikethompson07:03:34

(for [rack racks]
       [tree-rack rack])])    <-------  you have to put the key on this NOT what is inside of it

mikethompson07:03:44

So more like this ....

smw07:03:02

Ahhh. So a component can’t generate it’s own key?

mikethompson07:03:23

(for [rack racks]
       ^{:key (gensym)}}[tree-rack rack])])

mikethompson07:03:31

Unfortunately not

mikethompson07:03:47

You are supplying a key for the LEVEL ONE DOWN

smw07:03:51

Ugh. That does seem unfortunate. Is there some reason I’m missing what that’s the case?

mikethompson07:03:19

Well [tree-rack rack] is a component

mikethompson07:03:30

So it needs the key and not a nested component (which is what you are doing now)

smw07:03:54

I see. That’s not an instantiation of the component, that’s the component itself?

smw07:03:18

Does it matter if I’m using … what was the terminology… class 1 2 or 3 components?

mikethompson07:03:41

The moment you do this [ ] you are creating a component

mikethompson07:03:21

(for [rack racks]
       (tree-rack rack))])       ;; <---- round brackets

smw07:03:49

right, but then I lose the, uh, react-nature, right?

smw07:03:53

redraws on every call

smw07:03:27

(Thanks for re-frame btw, absolutely brilliant)

mikethompson07:03:50

Well, not necessarily. if becomes the same as:

(for [rack racks]
  [:div {:key (gensym "key-")}
     [:p "Rack: " (:name rack)]
     [:p "Servers:"]
      (for [server (:servers rack)]
          [tree-server server])]

mikethompson07:03:37

That works because, sure enough, the direct descendent [:div ...] has a key

mikethompson07:03:56

And there's no intermediate [tree rack ...]

mikethompson07:03:42

But to answer your question, yes, [tree-rack rack] will likely be more efficient

smw07:03:52

And so the issue is basically that reagent would be required to render the child component in order to get the key for the child component?

smw07:03:06

But it only matters on re-render

smw07:03:13

so this actually seems like a fixable bug?

mikethompson07:03:26

I'd put it this way ...

mikethompson07:03:35

tree-test is going to produce some hiccup ...

smw07:03:39

if you want to assume that the correct level of abstraction is for the component to be better at generating its own id

mikethompson07:03:57

if you use [tree-rack rack] the amount of hiccup produced is a lot less

mikethompson07:03:21

So there'll be less work to be done figuring out is stuff really does need to be redrawn

mikethompson07:03:54

Yeah, i agree. Which is why I said above that it is unfortunate.

smw07:03:31

Just wondering if it’s the sort of thing that’s fixable with a pull request, or the sort of thing that’s broken by design.

smw07:03:38

It seems to me that it’s the former.

mikethompson07:03:06

Unfortunate is theory, but not, in my experience, not that harrowing in practice.

smw07:03:24

(for [rack racks]
       [tree-rack ^{:key (:name rack)} rack])

smw07:03:42

that’s pretty ugly simple_smile maybe I could write a macro

smw07:03:57

Or does that invoke jwz’s “and then you have two problems”?

mikethompson07:03:17

Yeah, don't be tempted to write a macro. simple_smile

mikethompson07:03:00

Put up with the uglyness for a little while and see how much true pain it brings. You might reach the same conclusions that I have: not really that much of an issue after all.

mikethompson07:03:24

But i do share the aesthetic pain

smw07:03:25

Perhaps, perhaps.

smw07:03:01

Obviously (gensym) is a lousy idea long-term, right? As the whole idea is for the keys to be stable?

mikethompson07:03:48

That was me just trying to figure out if there was something wierd about the keys themselves OR if we had a structural problem

mikethompson07:03:56

(it turned out to be the later)

smw07:03:23

Ok, one more question

smw07:03:31

as you said

smw07:03:34

this syntax works

smw07:03:36

^{:key (:name rack)} [tree-rack  rack]

smw07:03:53

but

[tree-rack ^{:key (:name rack)} rack]
doesn't

smw07:03:10

What’s going on with the first one?

smw07:03:34

I guess I don’t even understand how that’s valid clojure syntax simple_smile

mikethompson07:03:40

I'm surprised by that. To use the 2nd one you'll have to change tree-rack to take two params of course

smw07:03:00

Ooooh, because it’s not actually doing the metadata thing because it’s not a function call?

mikethompson07:03:10

Reagent's rules around this are:

mikethompson07:03:41

- look for metadata - if the first argument is a map, look for a key in there

mikethompson07:03:01

So you actually have to pass a map as the first arguement

mikethompson07:03:27

(defn tree-rack [_  rack]        ;; <<<<<<<<
  [:div {:key (gensym "key-")}
   [:p "Rack: " (:name rack)]
   [:p "Servers:"]
   (for [server (:servers rack)]
         [tree-server server])])

smw07:03:28

Yeah, but that totally messes up the api of my component for some crappy implementation detail simple_smile

smw07:03:37

Ugh. Ok.

smw07:03:51

This is the stuff I’m trying to use clojure to avoid simple_smile

mikethompson07:03:13

But it doesn't if you are dealing with [:div ] etc which often have a map as the first argument for styling purposes

smw07:03:50

Sure. So maybe every component should just have an attr map as the first arg.

mikethompson07:03:53

So yeah, use, ^{:key (:name rack)} [tree-rack rack]

smw07:03:11

And then write a macro for defcomp or something.

mikethompson07:03:47

Hmm. macro talk makes me nervous simple_smile

smw07:03:15

Or maybe fix reagent to find the key in the (necessarily single) element returned from render.

mikethompson07:03:42

Yeah, if you did want to fix Reagent .... my sketch would be as follows ....

mikethompson07:03:11

Put metadata on the component, saying how to obtain the key

smw07:03:11

(defn get-key [x]
  (when (map? x)
    ;; try catch to avoid clojurescript peculiarity with
    ;; sorted-maps with keys that are numbers
    (try (get x :key)
         (catch :default e))))

(defn key-from-vec [v]
  (if-some [k (some-> (meta v) get-key)]
    k
    (-> v (nth 1 nil) get-key)))

mikethompson07:03:25

Indicative pseduo code:

^{:key-fn  #(:name %) }      ;;  <<<<<  meta data on the component itself
(defn tree-rack [rack]
  [:div {:key (gensym "key-")}
   [:p "Rack: " (:name rack)]
   [:p "Servers:"]
   (for [server (:servers rack)]
         [tree-server server])])

mikethompson07:03:57

Except my pseduo code above won't work, but you get the idea

smw07:03:57

is it legit to put that metadata after the defn?

mikethompson07:03:18

Well, yes, but it doesn't do what you think it does

mikethompson07:03:30

It puts metadata on the VAR

mikethompson07:03:33

Not the function

mikethompson07:03:30

A subtle and (to me) midly annoying feature of Clojurescrtipt (wasted about 4 hours on it one day :-))

smw07:03:36

could it be written with (with-meta) instead?

smw07:03:52

or is there no way to apply that to the function either?

mikethompson07:03:39

You can do this uglyness ....

smw07:03:40

(yeah, see, that one screams for a macro)

smw07:03:52

and I don’t see the issue with using it in that place, either

mikethompson07:03:38

(def tree-rack   
    ^{:key-fn #(:name %)}
    (fn [rack] 
       .....))

mikethompson07:03:10

Notice that there is a def there ... the var is tree-rack ... and the function now gets the metadata

mikethompson07:03:31

But I fee dirty

smw07:03:36

No kidding.

smw07:03:52

(defn key-from-vec [v]
  (if-some [k (some-> (meta v) get-key)]
    k
    (-> v (nth 1 nil) get-key)))

mikethompson07:03:17

That's the code from reagent, right?

smw07:03:21

Yeah… so

smw07:03:20

threading macro gives up if (nth 1 nil) is nil?

mikethompson07:03:03

some-> gives up on a nil yeah

smw07:03:23

I was trying to figure out the ‘else’ part

mikethompson07:03:28

But -> keeps going

smw07:03:01

so it either gets :key from meta or :key from the first element of v

mikethompson07:03:02

(get-key nil) will return nil

smw07:03:17

ahh, right, because there’s a try/catch among other things

smw07:03:37

and it returns nil anyway, I think?

smw07:03:06

I don’t mind your metadata idea at all

smw07:03:40

but I want the default behavior to be to call (key-from-vec) on the first element of the expanded component

mikethompson07:03:06

I'm in a timezone which requires dinner

smw07:03:19

Thanks for the help. Looking to how easy this is.

escherize16:03:08

Okay, Reagent - I know the kind of code usually posted here is very tricky - but with the new version of cljsfiddle we can save + share urls: http://cljsfiddle.com/#gist=d0c3121fb7083f066273

gadfly36117:03:18

Wohoooooo!!!! 😆

sveri18:03:53

Looks great, still, the circling circle makes me nuts and want to close the window 😄

mikethompson22:03:24

Yeah, the moving circle is super annoying simple_smile I too just immediately want to close the page.

mikethompson22:03:26

Also, when I click on one of the "Samples" I feel as though it should immediately "Run" it for me.

mikethompson22:03:24

I'm not sure the "Simple Component" (last Sample) is working - editting the colour doesn't work for me on Chrome.

mikethompson22:03:05

loving it ... but more critical feedback ... The samples encourage "bad practice" ... via the (:require [reagent.core :refer [atom]]) leading to atom references. Over time, we have found that naked atom use causes all sorts of confusion with clojure.core/atom, which leads to some subtle bugs for beginners, so we encourage use of (:require [reagent.core :as [reagent]]) and then reagent/atom in samples (or similar).

mikethompson22:03:39

The business about putting [simple-component] (or something) at the end of each sample is a bit unnatural because that isn't the way you mount components in real code.

mikethompson22:03:21

Wondering about alternatives: 1. Under the editable Sample area, perhaps there should be a section called "mounting" (or something) which looks like this

(reagent/render-component [simple-component] (js/document.getElementById "output"))))    
except that the [simple-component] is editable (input field). 2. Perhaps just mandate that there is a main component defined.

mikethompson22:03:56

I'd love to see this be a powerful teaching tool. Demonstrating rookie mistakes. Showing variations? Perhaps that means there should be a Notes section for the Samples.

mikethompson22:03:51

Eg: explain why am I getting the message "Warning: Every element in a seq should have a unique :key " Show alternative solutions.

mikethompson22:03:20

BTW, although this is called cljsfiddle, all my comments are really coming from reinterpreting this as reagent-fiddle.

mikethompson22:03:57

So much potential in this. Thanks for doing it!!!