Advertisement

Reagent, a wrapper for React.js

Introduction:

React authors define it as a JavaScript library for creating UIs and addressing problem of building large applications with data that changes over time.  React can be described as a tool for gen­er­at­ing a UI (ba­si­cal­ly, your HTML DOM), from appli­ca­tion data. React is a superfast UI library but for other layers we have to rely on other libraries, unlike Ember.js which is a full stack and opinionated framework. Reagent is a library that provides minimalistic interface between React and ClojureScript.    Have a look at the Figure below and see how the tools became increasingly better as the web applications became more complex.

 

As the complexity of web applications has increased, better tools to tackle the same has also emerged.

  1. Initially only plain JavaScript was used.
  2. JQuery, library was used for easier user-interaction to manipulate DOM but still only the server had to re-render the DOM.
  3. MVC frameworks took over the job of manipulation of DOM. They also enabled re-rendering of the DOM in the client. But as the entire DOM was re-rendered, it was relatively slow.
  4. New component oriented architecture was adopted and UI was composed from components. As only the changed DOM was re-rendered, re-rendering was faster.
  5. ClojureScript wrappers for React.js re-rendered even faster. In React.js the components are objects. In these wrappers components are just functions. The persistent data structures used by the language also contributed to this improved performance.

There is a lot of activity in ClojureScript community around React.js .Om, Reagent,Quiescent and Rum are some of the wrappers in ClojureScript for React.js. Unlike “Om” , another  wrapper which is thinner wrapper over React and exposes Reac­t’s API and life­cy­cle events, Regent  allows you to define React UIs of arbitrary complexity using only plain ClojureScript functions & data and describe it using Hiccup-like syntax which is very concise and easy to read. Our application can be built together only using bunch of very basic concepts. Gen­er­at­ing or mod­i­fy­ing Reagent com­po­nents is just a mat­ter of manip­u­lat­ing this data struc­ture. React itself is very fast. Reagent is able to be even faster because of the functional paradigm of ClojureScript and the optimizations done in it. Above all a separate state management tool like Redux is not necessary,  as “atoms” – a feature of ClojureScript – can handle state management.

Sample:

We can use reagent template to generate the sample as under:
lein new reagent myproject
You can start the server by executing
lein figwheel

 When figwheel server starts,  you will be asked to navigate to http://0.0.0.0:3449.If you do so you will see the browser as in Figure-1.Have a look at core.clj and see the the  home-page component definition

(defn home-page []
  [:div [:h2 "Welcome to myproject"]
   [:div [:a {:href "/about"} "go to about page"]]])

The general format for creating an HTML element inside a component is to create a vector whose first element is a keyword giving the HTML element name, a map of its attributes (if any), and the element content, which may contain other elements.

Under the above component,  the function mount-root() is defined calling the render() provided by reagent. Reagent’s render function takes two arguments: 1)a call to the component and 2) the DOM node where you want the component rooted, in this case, the app element. Modify the function as under:

(defn hello [name]
[:div "hello " name])

Modify the mount-to as under:

(defn mount-root []
    (reagent/render [hello] (.getElementById js/document "app")))

Now we will refresh the browser and can see Figure-2.You can include other components within a component as shown below:

(defn comment-list []     ;;comment-list component
[:div.commentList
"Hello, world! I am a CommentList"])

(defn comment-form []                                          ;;comment-form component
[:div.commentForm
"Hello, world! I am a CommentForm"])

(defn comment-box []   ;; comment-box composed from other two components
[:div.commentBox
[:h1 "Comments"]
[comment-list]
[comment-form]])

If Modify mount-to accordingly and refresh the browser, you can see Figure-3.

Modify state

React is very efficient in managing state. You simply update component internal state and then its UI is re-rendered accordingly. You do not have to deal with programming state transitions by hand which results in reduction of code complexity. The lifecycle method getInitialState() and componentDidMount() methods like setState(), setInterval() are used. In ClojureScript, almost everything is immutable. To accomplish mutability ClojureScript uses concept of atoms. Atoms are references to objects which needs to be dereferenced in order to obtain object instance. Think about them as similar thing as pointers in C. Common and viable practice is to define all of your application state in one atom. Reagent own version of atom. It works exactly like the one in clojure.core, except that it keeps track of every time it is deref’ed. Any component that uses an atom is automagically re-rendered when its value changes;  the de-referenced atom is  modified with swap! According to reagent doc, you can return a new function while defining components, so as to avoid usage of getInitialState and componentDidMount. This allows for more complex binding scenarios than in typical use of React. The distinction in React between props and state is not there in Reagent.

(def click-count (atom 0))
(defn counting-compnt []
[:div
"The atom " [:code "click-count"] " has value: "
@click-count ". "
[:input {:type "button" :value "Click me!"
:on-click #(swap! click-count inc)}]])

 

(defn mount-root []
(reagent/render [counting-compnt] (.getElementById js/document "app")))

If you refresh the browser, you can see Figure-4.

(defn timer-component []
(let [seconds-elapsed (atom 0)] ;; initialized to 0 and stored in atom
(fn []
(js/setTimeout #(swap! seconds-elapsed inc) 1000) ;; value modified with swap
[:div
"Seconds Elapsed: " @seconds-elapsed]))) ;;de-referenced for re-rendering

.Reagent benefits from fast pointer comparisons between changed ClojureScript data structures and triggering of  re-rendering of components only when it is really needed.

You can modify mount-root and refresh the browser and see the result/ the counter incrementing as in Figure-5. In short , number of elapsed seconds is kept in its own atom - in this case unique state of component. Most common way to mutate atom value is to use built-in ClojureScript function swap!.Call to swap! accepts function as argument that is applied to value of atom and stored.

Conclusion:

Reagent provides a minimalistic interface between ClojureScript and React. It allows you to define efficient React components using nothing but plain ClojureScript functions and data, that describe your UI using a Hiccup-like syntax. The goal of Reagent is to make it possible to define arbitrarily complex UIs using just a couple of basic concepts, and to be fast enough by default that you rarely have to care about performance. React is pretty fast, and so is Reagent. It should even be faster than plain old javascript- React a lot of the time, since ClojureScript allows us to skip a lot of unnecessary rendering (through judicious use of React's shouldComponentUpdate).The ClojureScript overhead is kept down, thanks to lots of caching. Reagent, is built around its own ver­sion of atom which uses ‘computed observables’ to keep the UI up to date. Every change to one of these atoms results in an update of the com­po­nents that depend on it. Com­po­nents are defined using nor­mal Clo­jure­script func­tions. These func­tions must either return a Hic­cup-­like data struc­ture or return another func­tion, which in turn returns a Hic­cup-­like data struc­ture. Those func­tions are ulti­mately passed to Reagent which does the hard work of con­vert­ing them into com­po­nents. There is less empha­sis on local state in Reagent than in Om and the default mode for com­mu­ni­ca­tion between com­po­nents is to sim­ply have top level com­po­nents pass argu­ments to lower level com­po­nents. It feels much more ‘Clo­jur­ish’ and the pro­gram­ming model feels much more acces­si­ble, espe­cially to some­one already famil­iar with Clo­jure/­Clo­jure­Script.








Added on February 18, 2017 Comment

Comments

Post a comment