presumably for side-effects
a blog about clojure &c.

Reagent Mysteries

Part 4: Children and Other Props

published Jan 20, 2017

ReactDOM.render — essentially React's single public API function — renders a root component based on a set of arguments, or props, that determine the component's look and behavior. Transforming its inputs and expanding sub-nodes recursively, render constructs a tree of elements ready to be mounted to the DOM. In React-land data flows, in the form of props, from parent to children.

Reagent follows the same principles but thereby hangs a tale. In React, every component receives as its single argument a JavaScript object called props. Each prop is a key associated with a (hopefully) immutable value. Props can be anything you wish: strings, numbers, objects or, just as commonly, callback functions. In plain React, props are typically passed by way of JSX syntax, which looks like XHTML:

<MyUI name="Smith" age={72}}>
  <Child/>
  <Child/>
</MyUI>

Calling a component in Reagent works similarly:

(defn root []
  [my-ui {:name "Smith" :age 72}])

Here's one way to define the component:

(defn my-ui [{:keys [name age]}]
  [:div "Mr. " name " is " age " years old"])

In fact, if you are working with a plain React component, perhaps from a third-party library, this is the only way to instantiate a component:

[component {:prop1 :val1, :prop2 :val2 ...} child1 child2 ...]

React components always receive named props, but Reagent components are more flexible. A Reagent render function could be any ClojureScript function and, as such, can take any number of positional arguments.

(defn my-ui* [name age]
  [:div "Mr. " name " is " age " years old"])

(defn root []
  [my-ui* "Smith" 72])

If we peek under the covers, we can see that Reagent implements this feature by storing the entire list of props passed to the Reagent component in a single prop called argv. Note that argv contains the entire Hiccup vector. Its first element is the function representing the component (in our case, my-ui*), followed by each argument ("Smith" and 72).

It is often a good idea to follow the React convention of passing a map of attributes (or props) as the first argument, followed by children (if any). Here is such an example:

(defn title-ul-ui [{:keys [title]} & children]
  [:section
   [:h3 title]
   (into [:ul children])])

(defn root []
  (title-ul-ui
   {:title "people"}
   [:li {:key 0} "Smith"]
   [:li {:key 1} "Schmidt"]))

Another point in favor of this format is that Reagent comes with convenience functions that allow you to access the props map as (r/props (r/current-component)) and the children as (r/children (r/current-component)) from inside the render function. r/children, in particular, will only work if the props-first convention is followed.

Conversely r/children also works when you're implementing a plain React component in ClojureScript using reactify-component. This is sometimes useful when interoperating with Reagent components. To see how this works, we can create a plain React component and instantiate it using the :> shorcut introduced in Reagent 0.6.0:

(defn title-ul-ui [{:keys [title]}]
  [:div
   [:section
    [:h3 title]
    (into [:ul] (r/children (r/current-component)))]])

(def title-ul-ui* (r/reactify-component title-ul-ui))

(defn root []
  [:div
  [:> title-ul-ui* {:title "people"}
    [:li "Smith"]
    [:li "Taylor"]]])

To summarize, React distinguishes between regular props (passed like DOM attributes in JSX) and children (passed as sub-components in the tag body). The latter are available in JavaScript as this.props.children, a pseudo-array that contains the sub-components.

By default, Reagent handles props differently. All props, including the component's children, are held in a single React prop and accessible as function arguments to the render function. As a result, Reagent components aren't drop-in replacements for plain React components. However, interop with plain React is possible by using the functions r/reactify-component, r/as-element and r/adapt-react-class ([:> ...]). In interop cases, as well as during non-render lifecycle methods, props and children will be accessible through the r/props and r/children helpers.

A final difference between React and Reagent is that in recent versions React has introduced the notion of PropTypes. If you specify a component's PropTypes, you can make props obligatory or optional, set default values or define the prop's value type. These runtime checks cannot be used when building Reagent components because of the differences in prop format noted above. But it's possible to encounter PropTypes when using third-party JavaScript, so it's useful to be aware of their existence.

Further Reading


This is presumably for side-effects, a blog by Paulus Esterhazy. Don't forget to say hello on twitter or by email