reagent-project / reagent

A minimalistic ClojureScript interface to React.js
http://reagent-project.github.io/
MIT License
4.74k stars 412 forks source link

Behavior of children differs from React.Children #429

Open SevereOverfl0w opened 5 years ago

SevereOverfl0w commented 5 years ago

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.

lilactown commented 5 years ago

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"]))
Deraen commented 4 years ago

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

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

SevereOverfl0w commented 4 years ago

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

Deraen commented 4 years ago

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

SevereOverfl0w commented 4 years ago

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.

vlad-obrizum commented 12 months ago

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)