Skip to content

Instantly share code, notes, and snippets.

@swannodette
Last active September 1, 2016 15:36
  • Star 29 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save swannodette/7763911 to your computer and use it in GitHub Desktop.
(ns react-cljs.core
(:require React))
(declare render)
(defn handle-change [e]
(render {:text (.. e -target -value)}))
(defn render [{:keys [text]}]
(React/renderComponent
(React/DOM.div nil
(array
(React/DOM.h1 nil "Type some stuff in the text box")
(React/DOM.input
(js-obj
"type" "text"
"value" text
"onChange" handle-change))
text))
js/document.body))
(render {:text ""})
;; -- Oh the beautiful DSLs that await us! --
;;
;; (render-component
;; (div nil
;; [(h1 nil "type some stuff here")
;; (input :type "text" :value text :change handle-change)
;; text])
;; js/document.body)
(defproject react-cljs "0.1.0-SNAPSHOT"
:description "FIXME: write this!"
:url "http://example.com/FIXME"
:dependencies [[org.clojure/clojure "1.5.1"]
[org.clojure/clojurescript "0.0-2080"]
[reactjs "0.5.1"]]
:plugins [[lein-cljsbuild "1.0.0"]]
:source-paths ["src"]
:cljsbuild {
:builds [{:id "dev"
:source-paths ["src"]
:compiler {
:output-to "main.js"
:output-dir "out"
:optimizations :none
:source-map true
:foreign-libs [{:file "reactjs/react.js"
:provides ["React"]}]
:externs ["reactjs/externs/react.js"]}}]})
@noprompt
Copy link

noprompt commented Dec 3, 2013

Here's the beginnings of dom macro that offers a hiccup flavor:

(declare parse)

(defn emit-react-dom-fn-name [element-name]
  (symbol (str "React/DOM." (name element-name))))

(defn emit-attributes [m]
  (cons 'js-obj (interleave (map name (keys m)) (vals m))))

(defn emit-react-dom-call [v]
  (let [[element-name & content] v
        attrs (when (map? (first content))
                (emit-attributes (first content)))
        content (if attrs
                  (rest content)
                  content)]
    (concat
     (list (emit-react-dom-fn-name element-name) attrs)
     (list (cons 'array (map parse content))))))

(defn parse [x]
  (if (vector? x)
    (emit-react-dom-call x)
    x))

(defmacro dom [html]
  (emit-react-dom-call html))

Which shortens the example to:

(declare render)

(defn handle-change [e]
  (render {:text (.. e -target -value)}))

(defn render [{:keys [text]}]
  (React/renderComponent
   (dom [:div
         [:h1 "Type some stuff in the text box"]
         [:input {:type "text" :value text :onChange handle-change}]
         text])
   js/document.body))

(render {:text ""})

Fun stuff!

@piranha
Copy link

piranha commented Dec 3, 2013

That is a much thinner approach than what we did in pump, and our reasoning was that when React sees that top-level component is different it just throws out whole DOM and re-renders it (that's a part of their logic). Trying to remember why didn't we go with a macro to render hiccup's syntax into React functions - IIRC we've tried it and it didn't work for us in some case (maybe dynamic lists of elements? like (for [item items] [:li item])).

@haywoood
Copy link

haywoood commented Dec 3, 2013

For those that need further hand holding, place an HTML file in the root folder with this markup to get it going.

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <script src="out/goog/base.js" type="text/javascript"></script>
        <script src="main.js" type="text/javascript"></script>
        <script type="text/javascript">goog.require("react_cljs.core");</script>
    </body>
</html>

@noprompt
Copy link

noprompt commented Dec 3, 2013

@piranha This was just something I banged out in about 15 minutes and I'm fairly sure it's not a robust solution. It was just something to minimize friction as I worked through the tutorial. Honestly, I'd prefer the approach you guys took with pump, which I wasn't aware of until now, over using a macro like this one. I was also messing around with something that generates React.createClass code but it's buggy.

(defclass CommentBox
  (render []
    (dom
     [:div {:className "commentBox"}
      [:h1 "Comments"]
      (CommentList {:data (js* "this.props.data")})
      (CommentForm)])))

Later today I plan to browse through the pump library code. 👍

@piranha
Copy link

piranha commented Dec 3, 2013

To be honest, I don't have much free time now (that's why pump is a bit stalled in development), but I did have plans to improve it a bit, and one of things I wanted to get rid of was to drop hiccup-like syntax in favor of just functions a-la (div (h1 "comments")).

Another thing I wanted to do is to drop object-oriented style of defr and invent something more functional. But so far all my ideas were insufficient to have enough coverage of lifecycle (at least componentDidMount/componentWillUnmount need to be covered). For example, one of my main ideas was to have a function accepting element (i.e. componentDidMount) and returning a render function, but this will not work since render is called by React before componentDidMount. Damn. This needs some experimenting for sure.

@rads
Copy link

rads commented Dec 3, 2013

What is the reasoning behind changing the Hiccup-like syntax to functions? I personally like representing HTML as plain old data.

@kovasb
Copy link

kovasb commented Dec 3, 2013

Is it possible to protocolize the component api somehow?

It might be nice to define types/records and have them end up with the render function, without having to manually modify their prototype.

@noprompt
Copy link

noprompt commented Dec 4, 2013

@rads I won't speak for @piranha but one advantage I can think of is that there is no intermediate parsing step. With the macro this isn't as much of an issue (since the expanded macro code will be emitted at compile time). However, there are the typical disadvantages of macros, such as composability, and, in the case of this particular one, there are a variety of things you must do to get this to work properly (see here).

@swannodette
Copy link
Author

So it looks like React is quite useful even w/o the magic props and state machinery. https://gist.github.com/swannodette/7782780, looks a lot like WebFUI to me. Seems like we could do a more idiomatic version of the props and state functionality w/o much hassle?

@piranha
Copy link

piranha commented Dec 4, 2013

@noprompt @rads that's right, main reason for functions instead of hiccup-like data structures is performance. IIRC transformation step takes around 20-30% of runtime of a whole application, which is just an overhead and could be fixed quite easily.

@swannodette well state is really not needed with CLJS, local atoms could be used, but I still think that creating React components makes sense since React has some optimisations related to components update. For example, if your props didn't change, there is no need to call render, which you will call anyway if you're going with a simple function like render-counter.

Though it would be interesting to see how would you scale code you wrote into something bigger. :) TBH most components could be represented as just a function (render), but sometimes you need a bit more: sometimes it's local state - that's possible with closures, I guess, but then when you need to integrate with external libraries you will need componentDidMount and componentWillUnmount at least.

@piranha
Copy link

piranha commented Dec 4, 2013

I wonder if Github should notify me somehow when I got mentioned here, since it does not right now and that's frustrating.

@swannodette
Copy link
Author

@piranha are there many external React libraries? Or do you mean that components created in ClojureScript will need componentDidMount, componentWillUnmount in order to be used by others? To be honest this seems unlikely given ClojureScript's whole program compilation model.

Interesting point about props, I didn't realize this was compared - do you have a link to the documentation and/or source about this optimization?

@piranha
Copy link

piranha commented Dec 4, 2013

@swannodette by "external libraries" I mean absolutely separate external libraries, i.e. jQuery, various date pickers and calendars, d3 and so on.

Regarding optimizations, something like that.

@ericnormand
Copy link

Is there a way to suppress the warnings of React.js? I'm getting 268 warnings about JSDoc tags. I can't find the real warnings about my code (like undefined variables).

@swannodette
Copy link
Author

@ericnormand, this is fixed in master, your release build should look something like this to suppress warnings:

{:id "release"
 :source-paths ["src"]
 :compiler
 {:output-to "main.js"
  :optimizations :advanced
  :pretty-print false
  :foreign-libs [{:file "reactjs/react.js"
                  :provides ["React"]}]
  :externs ["reactjs/externs/react.js"]
  :closure-warnings {:non-standard-jsdoc :off}}}

@swannodette
Copy link
Author

After chatting with the React devs some more, there are definitely some improvements to my approach in the counter Gist, will keep exploring and post updates when I discover something new.

@hutch
Copy link

hutch commented Dec 8, 2013

@swannodette, in case you weren't aware, Conrad Barski (webfui) has made a few comments on the React mailing list: https://groups.google.com/forum/#!topic/reactjs/e3bYersyd64

@przeor
Copy link

przeor commented Sep 1, 2016

Hi I see that you use React, so I am sure that you will find interesting the https://reactjs.co - this is the free online convention and tutorial book for React.JS Developers. React is not only the View (in MVC) anymore. ReactJS For Dummies: Why & How to Learn React Redux, the Right Way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment