cjohansen / replicant

A native ClojureScript virtual DOM renderer - render hiccup directly
223 stars 10 forks source link

Why `:replicant/key` and not a fully qualified namespaced keyword? #44

Closed simongray closed 2 months ago

simongray commented 2 months ago

It seems like a lot of typing/extra characters to have to write replicant every time.

If it were a namespace, the natural thing (to me, anyway) would be to do this:

(require '[replicant.dom :as d])

(def el (js/document.getElementById "app"))

;; Render to the DOM - creates all elements
(d/render el
  [:ul.cards
    [:li {::d/key 1} "Item #1"]
    [:li {::d/key 2} "Item #2"]
    [:li {::d/key 3} "Item #3"]
    [:li {::d/key 4} "Item #4"]])

;; This render call will only result in one DOM node being moved.
(d/render el
  [:ul.cards
    [:li {::d/key 1} "Item #1"]
    [:li {::d/key 3} "Item #3"]
    [:li {::d/key 2} "Item #2"]
    [:li {::d/key 4} "Item #4"]])
cjohansen commented 2 months ago

The namespace was picked to be short 😅 Replicant is fully data-oriented, so it is not necessary, nor intended, to have a hard dependency on replicant in every namespace that creates hiccup. Thus, "replicant" is both short and specific. "replicant.core" would be longer and would incentivise you to require the library for the shortcut.

In a well structured codebase using replicant you will likely only have one or two explicit requires for it.

Let me also add that you rarely need to explicitly key elements.

simongray commented 2 months ago

Ok, maybe I'm damaged from too much React. So keys aren't used in the same way as in React?

gaverhae commented 3 weeks ago

As a side note, you can actually do what you want to do here:

repl=> (require '[replicant :as-alias r])
nil
repl=> ::r/key
:replicant/key
repl=>
simongray commented 3 weeks ago

@gaverhae good point! Thanks.

gaverhae commented 3 weeks ago

Ok, maybe I'm damaged from too much React. So keys aren't used in the same way as in React?

They are, and they aren't. My understanding is that, in React, you are forced to give a key to any list of elements. In replicant, you only need to if you care about the specific mapping; I tend to think of it as replicant doing the expected "right" thing by default (i.e. when using reagent most of my lists looked like (map-indexed (fn [idx e] [:div {:key idx} ...])), where the key really serves no other purpose than making the React warning go away). So you only need to provide the key if you want to control the mapping from your supplied hiccup to the mounted DOM elements. I can't think of any use-case that does not involve CSS animations.

As a concrete example, I was working yesterday on displaying a bunch of tokens on top of a grid. So my code for the tokens basically looks like:

[:div
 (->> tokens
      (map (fn [t] [:div {:style {:translate (compute-coords t)}}])]

This worked fine without keys as long as I did not have any animation. If I both:

then things go weird as all the tokens visually shuffle around. In that specific case, adding a key to each element lets replicant know that the mapping from my hiccup to the DOM needs to be based on keys rather than order, avoiding the shuffling animation when the order of token changes.

So far this has been my only use-case for :replicant/key, so writing out the keyword does not seem too onerous.

Note that I needed both animations and non-constant order for :replicant/key to be needed; either one alone would work without it.