├── .gitignore ├── example-resources └── public │ └── devcards.html ├── src └── cljs_react_reload │ └── core.clj ├── README.md ├── project.clj └── example_src └── react_reload_devcards └── core.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /out/ 7 | /target/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | checkouts/ 12 | .nrepl-port 13 | .lein-classpath 14 | .rbenv-version 15 | config.ru 16 | example-resources/public/js/compiled 17 | .\#* 18 | \#* 19 | 20 | *-init.clj 21 | figwheel_server.log -------------------------------------------------------------------------------- /example-resources/public/devcards.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/cljs_react_reload/core.clj: -------------------------------------------------------------------------------- 1 | (ns cljs-react-reload.core 2 | (:require 3 | [cljs.env :as env])) 4 | 5 | (defmacro def-react-class [vname body] 6 | `(def ~vname (js/React.createClass ~body))) 7 | 8 | (defmacro defonce-react-class 9 | "Creates a stable base React class and then patches it on reload." 10 | [vname body] 11 | (if (and env/*compiler* 12 | (let [{:keys [optimizations]} (get env/*compiler* :options)] 13 | (or (nil? optimizations) (= optimizations :none)))) 14 | `(let [base# ~body] 15 | (defonce ~vname (js/React.createClass base#)) 16 | (doseq [property# (map 17 | name 18 | '(shouldComponentUpdate 19 | componentWillReceiveProps 20 | componentWillMount 21 | componentDidMount 22 | componentWillUpdate 23 | componentDidUpdate 24 | componentWillUnmount 25 | render))] 26 | (when (aget base# property#) 27 | (aset (.-prototype ~vname) property# (aget base# property#))))) 28 | `(defonce ~vname (js/React.createClass ~body)))) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cljs-react-reload 2 | 3 | > Write reloadable Reactjs classes in ClojureScript 4 | 5 | This library was created to answer the question how do I maintain 6 | local state for React components that I am reloading live. This 7 | library will be very helpful for folks who use Figwheel and write 8 | React.js classes. 9 | 10 | To understand what it does it is probably best to [read it](https://github.com/bhauman/cljs-react-reload/blob/master/src/cljs_react_reload/core.clj) 11 | 12 | @dan_abramov's React hotloader prompted me to write this. 13 | 14 | ## Usage 15 | 16 | [](https://clojars.org/lein-figwheel) 17 | 18 | Require the `cljs-react-reload.core` macros 19 | 20 | ```clojure 21 | (ns example.core 22 | (:require-macros 23 | [cljs-react-reload.core :refer [defonce-react-class def-react-class]))] 24 | ``` 25 | 26 | Then use the `defonce-react-class` to define a React class that gets 27 | patched on reload. 28 | 29 | ```clojure 30 | (defonce-react-class Counter 31 | #js {:getInitialState (fn [] #js {:count 0}) 32 | :render 33 | (fn [] 34 | (this-as this 35 | (sablono.core/html 36 | [:div [:h1 (str "Count")] 37 | [:button 38 | {:onClick 39 | #(.setState 40 | this 41 | #js{ :count (inc (.. this -state -count))})} "inc"]])))}) 42 | ``` 43 | 44 | Now you can work on the various React lifecycle hooks and see that 45 | their behavior changes while the local state of the component doesn't. 46 | 47 | The `def-react-class` is provided so that you can easiliy **redefine** the 48 | React class. 49 | 50 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject cljs-react-reload "0.1.1" 2 | :description "A library to define React classes that maintain local state on reload." 3 | :url "https://github.com/bhauman/cljs-react-reload" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :scm { :name "git" 8 | :url "https://github.com/bhauman/cljs-react-reload.git" } 9 | 10 | :dependencies [[org.clojure/clojure "1.7.0"]] 11 | 12 | :clean-targets ^{:protect false} ["example-resources/public/js/compiled" 13 | "target"] 14 | 15 | :source-paths ["src"] 16 | 17 | :cljsbuild { 18 | :builds [{:id "devcards" 19 | :source-paths ["src" "example_src"] 20 | 21 | :figwheel { :devcards true } 22 | :compiler { :main "react-reload-devcards.core" 23 | :asset-path "js/compiled/devcards_out" 24 | :output-to "example-resources/public/js/compiled/cljs_react_reload_devcards.js" 25 | :output-dir "example-resources/public/js/compiled/devcards_out" 26 | :source-map-timestamp true }}]} 27 | 28 | :figwheel { :css-dirs ["resources/public/css"] 29 | :open-file-command "emacsclient"} 30 | 31 | :profiles { 32 | :dev { 33 | :resource-paths ["example-resources"] 34 | :dependencies [[devcards "0.2.0-SNAPSHOT"] 35 | [sablono "0.3.6"] 36 | [org.clojure/clojurescript "1.7.122"]] 37 | :plugins [[lein-cljsbuild "1.1.0"] 38 | [lein-figwheel "0.4.0"]]}}) 39 | -------------------------------------------------------------------------------- /example_src/react_reload_devcards/core.cljs: -------------------------------------------------------------------------------- 1 | (ns react-reload-devcards.core 2 | (:require 3 | [sablono.core :as sab :include-macros true]) 4 | (:require-macros 5 | [devcards.core :as dc :refer [defcard deftest]] 6 | [cljs-react-reload.core :refer [def-react-class defonce-react-class]])) 7 | 8 | (enable-console-print!) 9 | 10 | (defcard 11 | "## We are going to try and create a reloadable react element.") 12 | 13 | (def-react-class my-class 14 | #js {:getInitialState 15 | (fn [] #js {:count 0}) 16 | :render 17 | (fn [] 18 | (this-as 19 | this 20 | (sab/html 21 | [:div 22 | [:h1 (.. this -props -name)] 23 | [:div (.. this -state -count)] 24 | [:button 25 | {:onClick (fn [] 26 | (.setState 27 | this 28 | #js {:count (inc (.. this -state -count))}))} 29 | "inc"]])))}) 30 | 31 | (defcard def-react-class-example 32 | "When you redefine a React class the React diffing algorithm blows 33 | away the local state of the component. If increment the counter and 34 | you edit the `my-class` component you will see this in action." 35 | (js/React.createElement my-class #js {:name "Mike"})) 36 | 37 | (defonce-react-class my-classyy 38 | #js {:getInitialState 39 | (fn [] #js {:count 0}) 40 | :shouldComponentUpdate 41 | (fn [a b] 42 | (prn "I'm here yep") 43 | true) 44 | :componentWillReceiveProps 45 | (fn [a b] 46 | (prn "Receiving props now")) 47 | :render 48 | (fn [] 49 | (this-as 50 | this 51 | (sab/html 52 | [:div 53 | [:h3 "And now the local state is maintained."] 54 | [:h1 (.. this -props -name)] 55 | [:div (.. this -state -count)] 56 | [:button 57 | {:onClick (fn [] 58 | (.setState 59 | this 60 | #js {:count (inc (.. this -state -count))}))} 61 | "inc"]])))}) 62 | 63 | (defcard defonce-react-class-example 64 | "The solution is to define the React class once and then patch it's protoype." 65 | (js/React.createElement my-classyy #js {:name "Margaret"})) 66 | --------------------------------------------------------------------------------