News in 0.5.0

2014-12-10

Reagent 0.5.0 has automatic importing of React.js, two kinds of cursors, better integration of native React components, better performance, easier integration with e.g Figwheel, and more.

A new way of importing React

Reagent now takes advantage of ClojureScript’s new way of packaging JavaScript dependencies. That means that you no longer have to include React in your HTML, nor should you use :preamble. Instead, Reagent depends on the cljsjs/react library.

If you want to use another version of React, you can do that in two ways. In both cases you’ll have to exclude cljsjs/react by using e.g [reagent "0.5.0-alpha3" :exclusions [cljsjs/react]] in the :dependencies section of your project.clj.

You can then add e.g [cljsjs/react-with-addons "0.12.2-4"] as a dependency. Or you can add a file named cljsjs/react.cljs, containing just (ns cljsjs.react), to your project – and then import React in some other way.

Reagent now requires ClojureScript 0.0-2816 or later.

Splitting atoms

Reagent now has a simple way to make reusable components that edit part of a parents state:

The new wrap function combines two parts – a simple value, and a callback for when that value should change – into one value, that happens to look as an atom.

The first argument to wrap should be the value that will be returned by @the-result.

The second argument should be a function, that will be passed the new value whenever the result is changed (with optional extra arguments to wrap prepended, as with partial).

Usage can look something like this:

hide

Example

Current state: {:name {:first-name "John", :last-name "Smith"}}

I'm editing John Smith.

First name:
Last name:

Source

(ns example
  (:require [reagent.core :as r]))
(defonce person (r/atom {:name
                         {:first-name "John" :last-name "Smith"}}))

(defn input [prompt val]
  [:div
   prompt
   [:input {:value @val
            :on-change #(reset! val (.-target.value %))}]])

(defn name-edit [n]
  (let [{:keys [first-name last-name]} @n]
    [:div
     [:p "I'm editing " first-name " " last-name "."]

     [input "First name: " (r/wrap first-name
                                   swap! n assoc :first-name)]
     [input "Last name:  " (r/wrap last-name
                                   swap! n assoc :last-name)]]))

(defn parent []
  [:div
   [:p "Current state: " (pr-str @person)]
   [name-edit (r/wrap (:name @person)
                      swap! person assoc :name)]])

Here, the parent component controls the global state, and delegates editing the name to name-edit. name-edit in turn delegates the actual input of first and last names to input.

Note: The result from wrap is just a simple and light-weight value, that happens to look like an atom – it doesn’t by itself trigger any re-renderings like reagent.core/atom does. That means that it is probably only useful to pass from one component to another, and that the callback function in the end must cause a ”real” atom to change.

Cursors

Reagent has another way of isolating part of a data structure in an atom: reagent.core/cursor. Using the same state as in the previous example, usage now looks like this:

hide

Example

Current state: {:name {:first-name "John", :last-name "Smith"}}

I'm editing John Smith.

First name:
Last name:

Source

(defn cursor-name-edit [n]
  (let [{:keys [first-name last-name]} @n]
    [:div
     [:p "I'm editing " first-name " " last-name "."]

     [input "First name: " (r/cursor n [:first-name])]
     [input "Last name:  " (r/cursor n [:last-name])]]))

(defn cursor-parent []
  [:div
   [:p "Current state: " (pr-str @person)]
   [cursor-name-edit (r/cursor person [:name])]])

Cursors can now also be generalized to use any transformation of data from and to a source atom (or many atoms, for that matter). To use that, you pass a function to cursor instead of an atom, as in this example:

hide

Example

Current state: {:name {:first-name "John", :last-name "Smith"}}

I'm editing John Smith.

First name:
Last name:

Source

(defn person-get-set
  ([k] (get-in @person k))
  ([k v] (swap! person assoc-in k v)))

(defn get-set-parent []
  [:div
   [:p "Current state: " (pr-str @person)]
   [cursor-name-edit (r/cursor person-get-set [:name])]])

The function passed to cursor will be called with one argument to get data (it is passed the key, i.e the second argument to cursor), and two arguments when the cursor is changed (then it is passed the key and the new value).

The getter function can reference one or many Reagent atoms (or other cursors). If the cursor is used in a component the getter function will re-run to change the value of the cursor just like a Reagent component does.

Values and references

So what’s the difference between wraps and cursors? Why have both?

A wrap is just a value that happens to look like an atom. It doesn’t change unless you tell it to. It is a very lightweight combination of value and a callback to back-propagate changes to the value. It relies only on Reagent’s equality test in :should-component-update to avoid unnecessary re-rendering.

A cursor, on the other hand, will always be up-to-date with the value of the source atom. In other words, it acts a reference to part of the value of the source. Components that deref cursors are re-rendered automatically, in exactly the same way as if they deref a normal Reagent atom (unnecessary re-rendering is avoided by checking if the cursor's value has changed using identical?).

Faster rendering

Reagent used to wrap all ”native” React components in an extra Reagent component, in order to keep track of how deep in the component tree each component was (to make sure that un-necessary re-renderings were avoided).

Now, this extra wrapper-component isn’t needed anymore, which means quite a bit faster generation of native React elements. This will be noticeable if you generate html strings, or if you animate a large number of components.

Simple React integration

Since Reagent doesn't need those wrappers anymore it is also now easier to mix native React components with Reagent ones. There’s a new convenience function, reagent.core/create-element, that simply calls React.createElement. This, unsurprisingly, creates React elements, either from the result of React.createClass or html tag names.

reagent.core/as-element turns Reagent’s hiccup forms into React elements, that can be passed to ordinary React components. The combination of create-element and as-element allows mixing and matching of Reagent and React components.

For an example, here are four different ways to achieve the same thing:

hide

Example

Hello world
Hello world
Hello world
Hello world

Source

(defn integration []
  [:div
   [:div.foo "Hello " [:strong "world"]]

   (r/create-element "div"
                     #js{:className "foo"}
                     "Hello "
                     (r/create-element "strong"
                                        #js{}
                                        "world"))

   (r/create-element "div"
                     #js{:className "foo"}
                     "Hello "
                     (r/as-element [:strong "world"]))

   [:div.foo "Hello " (r/create-element "strong"
                                        #js{}
                                        "world")]])

If you don't need/want this kind of low-level control over interaction with javascript React, you can also use the new function adapt-react-class, that will take any React class, and turn it into something that can be called from Reagent directly. The example from above would then become:

hide

Example

Hello world

Source

(def div-adapter (r/adapt-react-class "div"))

(defn adapted []
  [div-adapter {:class "foo"}
   "Hello " [:strong "world"]])

You can also do the opposite: call Reagent components from JavaScript React (for example from JSX). For this purpose, you'd use another adapter – reactify-component – like this:

hide

Example

Hi, world

Source

(defn exported [props]
  [:div "Hi, " (:name props)])

(def react-comp (r/reactify-component exported))

(defn could-be-jsx []
  (r/create-element react-comp #js{:name "world"}))

The exported component will be called with a single argument: the React props, converted to a ClojureScript map.

More equality

Reagent used to have a rather complicated way of determining when a component should be re-rendered in response to changing arguments. Now the rule is much simpler: a component will be re-rendered if the old and new arguments are not equal (i.e. they are compared with a simple =).

Note: This is a breaking change! It means that you can no longer pass infinite seqs to a component.

React 0.12

Reagent now comes with, and requires, React 0.12.2. To mirror the changes in API in React, some Reagent functions have gotten new names:

  • render-component is now render
  • render-component-to-string is now render-to-string
  • as-component is now as-element

The old names still work, though.

There is also a new function, render-to-static-markup, that works just like render-to-string, except that it doesn’t add React-specific attributes.

Easier live-programming

It is now easier than before to integrate Reagent with e.g. the rather excellent figwheel, since render now will cause the entire component tree to update (by-passing the equality checks).

All the examples in the Reagent repo now uses figwheel.

Fork me on GitHub