The purpose of this article is just to show some really basic differences between using React with Javascript and using Om with Clojurescript. There are, of course, better ways to do what I'm going to show here, so please comment and suggest corrections as you see fit (here or twitter).
At the end of the day, your team/organization should probably weigh the benefits and the costs of using one or the other.
Basic Component Definition
Keep in mind that in CLJS/Om, you can take advantage of immutable data structures to get pure rendering out of the box, so there won't be wasted rendering (if you scope props correctly). With JS/React, you'll have to add PureRenderMixin
or something to achieve the same and be disciplined in how you modify state. See my other boring post about this: http://qiita.com/kimagure/items/c38444713ea48a6f02e8 (or go straight to the demo: http://jsbin.com/cobiyi/1/edit?js,output)
Application state and root
var appState = {
columns: [{
id: 'name',
title: "Name"
}, {
id: 'episode',
title: "Episode"
}, {
id: 'lastViewed',
title: "Last Viewed"
}],
rows: [{
id: 1,
name: 'dfsds',
episode: 1,
lastViewed: "20-Jan-14"
}, {
id: 2,
name: "sdfsdfsd",
episode: 2,
lastViewed: "21-Jan-14"
}, {
id: 3,
name: "vcxvcsd",
episode: 5,
lastViewed: "14-Jan-14"
}]
};
function renderApp(state) {
React.render(<TrackerTable appState={state}/>, document.getElementById('app'));
}
(def app-state
(atom
{:columns [{:id :name :title "Name"}
{:id :episode :title "Episode"}
{:id :last-viewed :title "Last Viewed"}]
:rows [{:id 1 :name "dfsds" :episode 1 :last-viewed "20-Jan-14"}
{:id 2 :name "sdfsdfsd" :episode 2 :last-viewed "21-Jan-14"}
{:id 3 :name "vcxvcsd" :episode 5 :last-viewed "14-Jan-14"}]}))
(om/root
(fn [app owner]
(reify om/IRender
(render [_]
(om/build tracker-table app))))
app-state
{:target (. js/document (getElementById "app"))})
Main table
JS version calls React.createClass
and passes in an object, uses JSX that is transformed to plain JS targeting the specific methods per version of React.
var TrackerTable = React.createClass({
render: function () {
var columns = this.props.columns;
var rows = this.props.rows;
return (
<table>
<TrackerHeader columns={columns}/>
<TrackerBody columns={columns} rows={rows}/>
</table>
);
}
});
CLJS version defines a function that implements om/IRender.
(defn tracker-table [props]
"my tracker table"
(reify om/IRender
(render [_]
(dom/table
#js {}
(om/build tracker-header (:columns props))
(om/build tracker-body props)))))
Table row
These two do the same thing.
// var PureRenderMixin = require('react/lib/ReactComponentWithPureRenderMixin');
var TrackerRow = React.createClass({
// mixins: [PureRenderMixin],
render: function () {
var row = this.props.row;
var columns = this.props.columns.map(function (column) {
var id = column.id;
return (
<td key={id}>
{row[id]}
</td>
);
});
return (
<tr>
{columns}
</tr>
);
}
});
(defn tracker-row [props]
"my tracker row"
(let [{columns :columns row :row} props]
(reify om/IRender
(render [_]
(apply
dom/tr
#js {:key (:id row)}
(map
#(let [{id :id} %]
(dom/td #js {:key id} (id row)))
columns))))))
State update
// This version if you care about pure render
// You can also do this using react/lib/update: http://facebook.github.io/react/docs/update.html
var assign = require('react/lib/Object.assign');
var newRows = appState.rows.slice();
newRows.push({
id: 4,
name: "hello",
episode: 4,
lastViewed: "22-Jan-14"
});
var newAppState = assign({}, appState, {
rows: newRows
});
// otherwise, appState.rows.push({...}); renderApp(appState);
// explicitly call renderApp since there's no binding between app-state and our App
renderApp(appState);
// you can use 'flux' or Backbone Models or whatever instead from your root if you want to
;; updating the atom will update our application, yay
(swap!
app-state assoc :rows
(conj (:rows @app-state)
{:id 4 :name "hello" :episode 4 :last-viewed "22-Jan-14"}))
See the original CLJS source here: https://github.com/kimagure/tracker-om/blob/master/src/tracker_om/core.cljs