reagent icon indicating copy to clipboard operation
reagent copied to clipboard

Behavior of children differs from React.Children

Open SevereOverfl0w opened this issue 5 years ago • 6 comments

Attempting to reproduce https://codepen.io/SevereOverfl0w/pen/oOEvpY like-for-like does not currently work when doing:

(defn my-component
  []
  (let [this (r/current-component)]
    (into
      [:div {:style {"backgroundColor" "red"}}]
      (map-indexed
        (fn [i child]
          (when (> i 0)
            child))
        (r/children this)))))

(def messages ["Howdy" "Hey there"])

(defn root
  []
  [my-component
   (for [message messages]
     [:h2 message])
   [:h1 "Hello, world"]])

In the reagent example, the whole list inside of for is dropped. What should instead happen is that the list is concatenated with the [:h1] into a single list.

SevereOverfl0w avatar Apr 17 '19 06:04 SevereOverfl0w

Reagent’s conversion from hiccup -> React elements is lazy; it will only process your children when passed into a “native” (keyword) element identifier in the first position e.g. [:div (for [message messages] ....

When a component (function) is in the first position, it passes the subsequent arguments in verbatim without processing them at all. This allows you to manipulate the children passed to your component as regular Clojure data until you need to render it within some HTML.

This means that your my-component will receive the following as it’s children:

( <LazySeq generated by for> [:h1 “Hello, world”] )

To fix your example and match the semantics you are looking for, you can use into and other seq operations inside of the root component body just like you are doing within the my-component body to consume the LazySeq and spread it into middle of the resulting hiccup vector like you want:

(defn root
  []
  (conj
    (into [my-component]
      (for [message messages]
        [:h2 message]))
    [:h1 "Hello, world"]))

lilactown avatar Jun 23 '19 17:06 lilactown

Just use normal Clojure destructuring with Reagent components, and leave r/children for interop.

(defn my-component [props & children] ...)

Deraen avatar Apr 16 '20 21:04 Deraen

That does mean the custom if map? dance to detect whether props have been passed or not.

SevereOverfl0w avatar Apr 16 '20 23:04 SevereOverfl0w

Yes, though if you can choose, I'd force passing in the props map always, similar to React.

Deraen avatar Apr 17 '20 05:04 Deraen

The other downside is that react will unwrap lists for you (based on my original issue) so that you have an easy way to do wrapping. I think children can give different results based on an if statement contained within.

SevereOverfl0w avatar Apr 17 '20 06:04 SevereOverfl0w

When a component (function) is in the first position, it passes the subsequent arguments in verbatim without processing them at all

Worth to be documented? Could somebody point to the place in implementation where decision is happening? 🙏 (cc @lilactown)

vlad-obrizum avatar Jul 25 '23 17:07 vlad-obrizum