├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── deps.edn ├── doc ├── cljdoc.edn ├── optimizations.md ├── react-hooks.md ├── react-interop.md ├── react-suspense-and-code-splitting.md └── useful-mixins.md ├── examples └── rum │ ├── examples.cljs │ ├── examples │ ├── binary_clock.cljc │ ├── bmi_calculator.cljc │ ├── board_reactive.cljc │ ├── context.cljs │ ├── controls.cljc │ ├── core.cljc │ ├── custom_props.cljs │ ├── errors.cljc │ ├── form_validation.cljs │ ├── inputs.cljc │ ├── js_components.cljc │ ├── keys.cljc │ ├── local_state.cljc │ ├── multiple_return.cljc │ ├── portals.cljc │ ├── refs.cljc │ ├── self_reference.cljc │ ├── timer_reactive.cljc │ └── timer_static.cljc │ └── examples_page.clj ├── index.html ├── perf ├── pages │ ├── page1.html │ ├── page2.html │ └── page3.html └── rum │ └── perf.clj ├── project.clj ├── scripts └── test ├── src ├── daiquiri │ ├── compiler.clj │ ├── core.clj │ ├── core.cljs │ ├── interpreter.cljs │ ├── normalize.cljc │ └── util.cljc └── rum │ ├── core.clj │ ├── core.cljs │ ├── cursor.clj │ ├── cursor.cljs │ ├── derived_atom.cljc │ ├── lazy_loader.cljc │ ├── server_render.clj │ ├── specs.cljc │ └── util.cljc ├── target └── main.js └── test ├── daiquiri ├── compiler_test.clj ├── interpreter_test.cljs ├── normalize_test.cljc └── util_test.cljc ├── rum └── test │ ├── cursor.clj │ ├── defc.clj │ ├── derived_atom.clj │ ├── react_render_html.js │ ├── server.clj │ └── server_render.cljc └── test_runner.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/** 2 | !/target/main.js 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .DS_Store 12 | .nrepl-history 13 | .idea 14 | rum.iml 15 | .cljs_node_repl/ 16 | .cpcache/ 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.12.11 2 | 3 | - Support `multiple` flag for `select` #259 thx @tomasd 4 | 5 | ## 0.12.10 6 | 7 | - add ^js typehint to React Context #251 #256 thx @alexdao3 8 | 9 | ## 0.12.9 10 | 11 | - restored :will-update for backward compatibility #249 thx @tiensonqin 12 | 13 | ## 0.12.8 14 | 15 | ### Fixes 16 | 17 | - Bringing back async rendering queue to address performance issues in existing clients 18 | 19 | ## 0.12.7 20 | 21 | ### Fixes 22 | 23 | - Actually fixed broken interpretation of a collection of elements, due to incorrect `fragment-tag?` check #235 24 | 25 | ## 0.12.6 26 | 27 | ### Fixes 28 | 29 | - Fixed broken interpretation of a collection of elements, due to incorrect `fragment?` check #235 30 | 31 | ## 0.12.5 32 | 33 | ### Fixes 34 | 35 | - Fixed warnings not being disabled in shadow-cljs @Azzurite 36 | 37 | ## 0.12.4 38 | 39 | ### New 40 | 41 | - `:did-remount` is now `:will-remount`, the name matches semantics closely 42 | - Added support for soft-deprecated lifecycle methods prefixed with `UNSAFE`: `:unsafe/will-mount` and `:unsafe/will-update` 43 | - Added a wrapper for React's `useLayoutEffect` hook: `use-layout-effect!` 44 | - Added Reagent-like `:>` syntax for interop with React components 45 | 46 | ### Fixes 47 | 48 | - Fixed `fragment` macro not supporting optional attributes 49 | - Fixed crashing in projects using Rum w/o ClojureScript dependency 50 | 51 | ## 0.12.3 52 | 53 | ### Fixes 54 | 55 | - Fixed `rum/local` & `rum/reactive` components not being updated when `rum/static` is enabled [#221](https://github.com/tonsky/rum/issues/221) 56 | 57 | ## 0.12.2 58 | 59 | ### Fixes 60 | 61 | - Added missing `:did-update` and `:key-fn` to specs 62 | 63 | ## 0.12.1 64 | 65 | ### New 66 | 67 | - Changed the order of arguments in `use-reducer`'s `reducer-fn` to match React [#213](https://github.com/tonsky/rum/issues/213) 68 | - String attribute keys won't be camel cased in ClojureScript, allowing custom attributes [#129](https://github.com/tonsky/rum/issues/129) 69 | - Added assertions to check incorrect mixin keys [#96](https://github.com/tonsky/rum/issues/96) 70 | - Added `rum.core/set-warn-on-interpretation!` that enables warnings when compiler emits intepretation calls [66d352](https://github.com/tonsky/rum/commit/66d352acdedb5acc5bb860a7fc30411eac67c30c) 71 | - Added README section about Hiccup pre-compilation and interpretation [#103](https://github.com/tonsky/rum/pull/103) 72 | 73 | ### Fixes 74 | 75 | - Fixed `defcontext` macro [#214](https://github.com/tonsky/rum/issues/214) 76 | 77 | ## 0.12.0 78 | 79 | ### Dependencies 80 | 81 | - Upgraded to ClojureScript 1.10.773 82 | 83 | ### Breaking 84 | 85 | - Removed custom update scheduling mechanism (hopefully doesn't break anything) 86 | - Replaced Sablono with Daiquiri, reworked Sablono fork (in case if you are depending on Sablono) 87 | 88 | ### New 89 | 90 | - Ported Sablono's test suite 91 | - Added unit tests runner on Node 92 | - Added alternative Hiccup syntax for React.Fragment `:<>` 93 | 94 | ## 0.11.5 95 | 96 | ### Dependencies 97 | 98 | - Rum now requires Clojure 1.9.0 99 | - Upgraded to ClojureScript 1.10.597 100 | - Upgraded to React 16.8.6 101 | 102 | ### Deprecations 103 | 104 | - Deprecated usage of string refs 105 | - Deprecated legacy React Context API of string refs 106 | 107 | ### New 108 | 109 | - Added `deps.edn` 110 | - Added `use-state`, `use-reducer`, `use-effect!`, `use-callback`, `use-memo` and `use-ref` hooks 111 | - Added `rum.lazy-loader` ns and `suspense` component 112 | - Added `fragment` component 113 | - Added JS SSR API ([#105](https://github.com/tonsky/rum/issues/105)) 114 | - Added React Context API 115 | - Changed component's `displayName` to a fully qualified var name e.g. `app.core/button` 116 | - Added `React.createRef` API 117 | - Added adapter for JavaScript React components to be used in Rum with a fallback hook to render on JVM 118 | 119 | ### Fixes 120 | 121 | - Fixed `:type` attribute value serialization on JVM SSR ([#120](https://github.com/tonsky/rum/issues/120)) 122 | - Fixed an error when calling `rum/with-key` on multiple return components on JVM SSR ([#185](https://github.com/tonsky/rum/issues/185)) 123 | - Fixed string escaping for `:class` and `:type` attribute values ([#93](https://github.com/tonsky/rum/issues/93)) 124 | - Fixed unsused components not removed in production builds 125 | 126 | ## 0.11.4 127 | 128 | - Fix render-all to forbid forceUpdate on falsy comp ([#193](https://github.com/tonsky/rum/pull/193), thx @FieryCod) 129 | 130 | ## 0.11.3 131 | 132 | - Docstrings for https://cljdoc.org/d/rum/rum 133 | 134 | ## 0.11.2 135 | 136 | - Server-render on-\* event handlers with string values 137 | 138 | ## 0.11.1 139 | 140 | - Sablono or CLJS are excluded completely when using SSR ([#83](https://github.com/tonsky/rum/issues/83), [#157](https://github.com/tonsky/rum/pull/157)) 141 | 142 | ## 0.11.0 (thx [Roman Liutikov](https://github.com/roman01la) & [Alexander Solovyov](https://github.com/piranha), [#151](https://github.com/tonsky/rum/pull/151)) 143 | 144 | - [ BREAKING ] `contextTypes` and `childContextTypes` should be specified through `:static-properties` instead of `:class-properties` 145 | - React 16.2.0, Sablono 0.8.1 146 | - Added `rum/portal` method 147 | - Added `:did-catch` lifecycle callback 148 | - Added `rum/hydrate` and updated SSR output to match React’s 149 | 150 | ## 0.10.8 151 | 152 | - React 15.4.2-0, Sablono 0.7.7 153 | - Render boolean `aria-*` values as strings (thx [r0man](https://github.com/r0man), [#114](https://github.com/tonsky/rum/pull/114)) 154 | - Escape attributes during server-side rendering (thx [Alexander Solovyov](https://github.com/piranha), [#115](https://github.com/tonsky/rum/pull/115)) 155 | 156 | ## 0.10.7 157 | 158 | - Fixed server-side rendering discrepancy ([#99](https://github.com/tonsky/rum/issues/99)) 159 | - Sablono 0.7.5, React 15.3.1-0 160 | 161 | ## 0.10.6 162 | 163 | - Sablono 0.7.4 [fixes the issue](https://github.com/r0man/sablono/pull/129) with controlling components refusing to change value if non-string value was used 164 | - React 15.3.0-0 165 | - Throw error when `<` is misplaced in `defc` (thx [Martin Klepsch](https://github.com/martinklepsch), [#88](https://github.com/tonsky/rum/issues/88), [#90](https://github.com/tonsky/rum/pull/90)) 166 | 167 | ## 0.10.5 168 | 169 | - Sablono 0.7.3 fixes the issue when IE lost keystrokes in controlled inputs/textarea ([#86](https://github.com/tonsky/rum/issues/86)) 170 | - React 15.2.1-1 171 | - Warn when `rum.core/react` is used without `rum.core/reactive` (thx [Martin Klepsch](https://github.com/martinklepsch), [#82](https://github.com/tonsky/rum/issues/82), [#87](https://github.com/tonsky/rum/pull/87)) 172 | 173 | ## 0.10.4 174 | 175 | - Ability to use `:pre` and `:post` checks in `rum.core/defc` (thx [Martin Klepsch](https://github.com/martinklepsch), [#81](https://github.com/tonsky/rum/pull/81)) 176 | 177 | ## 0.10.3 178 | 179 | - Fixed regression of `displayName` in 0.10.0 180 | - Bumped React to 15.2.0 181 | 182 | ## 0.10.2 183 | 184 | - Fixed a bug when `:before-render` and `:will-update` weren’t called on subsequent renders 185 | 186 | ## 0.10.1 187 | 188 | - Made `rum.core/state` public again 189 | - `:before-render` should be called on server-side rendering too (thx [Alexander Solovyov](https://github.com/piranha), [#79](https://github.com/tonsky/rum/pull/79)) 190 | 191 | ## 0.10.0 192 | 193 | A big cleanup/optmization/goodies release with a lot breaking changes. Read carefully! 194 | 195 | - [ BREAKING ] `cursor` got renamed to `cursor-in`. New `cursor` method added that takes single key (as everywhere in Clojure) 196 | - [ BREAKING ] `rum/mount` returns `nil` (because you [shouldn’t rely on return value of ReactDOM.render](https://github.com/facebook/react/issues/4936)) 197 | - [ BREAKING ] `:transfer-state` is gone. All of component’s state is now transferred by default. If you still need to do something fancy on `componentWillReceiveProps`, new callback is called `:did-remount` callback 198 | - [ BREAKING ] removed `cursored` and `cursored-watch` mixins. They felt too unnatural to use 199 | - [ BREAKING ] removed `rum/with-props` (deprecated since 0.3.0). Use `rum/with-key` and `rum/with-ref` instead 200 | - [ BREAKING ] server-side rendering no longer calls `:did-mount` (obviously, that was a mistake) 201 | - [ BREAKING ] `:rum/id` is gone. If you need an unique id per component, allocate one in `:init` as store it in state under namespaced key 202 | 203 | When upgrading to 0.10.0, check this migration checklist: 204 | 205 | - Change all `rum/cursor` calls to `rum/cursor-in` 206 | - Find all `:transfer-state` mixins. 207 | - If the only thing they were doing is something like `(fn [old new] (assoc new ::key (::key old)))`, just delete them. 208 | - If not, rename to `:did-remount` 209 | - Check if you were using `rum/mount` return value. If yes, find another way to obtain component (e.g. via `ref`, `defcc` etc) 210 | - Replace `rum/with-props` with `rum/with-key`, `rum/with-ref` or `:key-fn` 211 | - Check that you weren’t relying on `:did-mount` in server-side rendering 212 | 213 | Now for the good stuff: 214 | 215 | - Cursors now support metadata, `alter-meta!` etc 216 | - Cursors can be used from Clojure 217 | - Added `:key-fn` to mixins. That function will be called before element creation, with same arguments as render fn, and its return value will be used as a key on that element 218 | - Mixins can specify `:before-render` (triggered at `componentWillMount` and `componentWillUpdate`) and `:after-render` (`componentDidMount` and `componentDidUpdate`) callback 219 | - Added `rum/ref` and `rum/ref-node` helpers, returning backing component and DOM node 220 | - Some client-side API functions added to server version (`dom-node`, `unmount`, `request-render` etc). Their implementation just throws an exception. This is to help you write less conditional directives in e.g. `:did-mount` or `:will-unmount` mixins. They will never be called, but won’t stop code from compiling either. 221 | 222 | And couple of optimizations: 223 | 224 | - Rum now makes use of staless components (nothing for you to do, if your component is defined via `defc` with no mixins, it’ll be automatically compiled to stateless component) 225 | - Rum will use React’s batched updates to perform rendering on `requestAnimationFrame` in a single chunk 226 | - Streamlined internals of component construction, removed `render->mixin`, `args->state`, `element` and `ctor->class` 227 | 228 | ## 0.9.1 229 | 230 | - Added `rum.core/derived-atom`, a function that let you build reactive chains and directed acyclic graphs of dependent atoms. E.g. you want `*c` to always contain a value of `*a` plus a value of `*b` and update whenever any of them changes 231 | - Added `rum.core/dom-node` helper that takes state and finds corresponding top DOM node of a component. Can be called in mixins after initial render only 232 | - Fixed compatibility of `with-key` on nil-returning component in server rendering (thx [Alexander Solovyov](https://github.com/piranha), [#73](https://github.com/tonsky/rum/pull/73)) 233 | 234 | ## 0.9.0 235 | 236 | - Better support for server-side rendering of SVG 237 | - [ BREAKING ] Rum used to support multiple ways to specify attributes. You would expect that both `:allow-full-screen`, `:allowFullScreen` and `"allowFullScreen"` would be normalized to `allowfullscreen`. As a result, you have to face three problems: 238 | - how do I decide which variant to use? 239 | - how do I ensure consistency accross my team and our codebase? 240 | - find & replace become harder 241 | 242 | Starting with 0.9.0, Rum will adopt “There’s Only One Way To Do It” policy. All attributes MUST be specified as kebab-cased keywords: 243 | 244 | | Attribute | What to use | What not to use | 245 | | -------------------- | ----------------------------------------------- | ----------------------------------------------------- | 246 | | class | `:class` | ~~`:class-name`~~ ~~`:className`~~ | 247 | | for | `:for` | ~~`:html-for`~~ ~~`:htmlFor`~~ | 248 | | unescaped innerHTML | `:dangerouslySetInnerHTML { :__html { "..." }}` | | 249 | | uncontrolled value | `:default-value` | ~~`:defaultValue`~~ | 250 | | uncontrolled checked | `:default-checked` | ~~`:defaultChecked`~~ | 251 | | itemid, classid | `:item-id`, `:class-id` | ~~`:itemID`~~ ~~`:itemId`~~ ~~`:itemid`~~ | 252 | | xml:lang etc | `:xml-lang` | ~~`:xml/lang`~~ ~~`:xmlLang`~~ ~~`"xml:lang"`~~ | 253 | | xlink:href etc | `:xlink-href` | ~~`:xlink/href`~~ ~~`:xlinkHref`~~ ~~`"xlink:href"`~~ | 254 | | xmlns | not supported | | 255 | 256 | To migrate to 0.9.0 from earlier versions, just do search-and-replace for non-standard variants and replace them with recommended ones. 257 | 258 | ## 0.8.4 259 | 260 | - Improved server-side rendering for inputs ([#67](https://github.com/tonsky/rum/issues/67) & beyond) 261 | - Compatible server-side rendering of components that return nil ([#64](https://github.com/tonsky/rum/issues/64)) 262 | - Upgraded React to 15.1.0 263 | 264 | ## 0.8.3 265 | 266 | - `rum/render-static-markup` call for pure HTML templating. Use it if you’re not planning to connect your page with React later 267 | - `rum/def*` macros now correctly retain metadata that already exists on a symbol (thx [aJchemist](https://github.com/aJchemist), [#62](https://github.com/tonsky/rum/pull/62)) 268 | 269 | ## 0.8.2 270 | 271 | - Add `rum.core/unmount` function (thx [emnh](https://github.com/emnh), [#61](https://github.com/tonsky/rum/issues/61)) 272 | 273 | ## 0.8.1 274 | 275 | - Retain `:arglists` metadata on vars defined by `rum/def*` macros (thx [aJchemist](https://github.com/aJchemist), [#60](https://github.com/tonsky/rum/pull/60)) 276 | 277 | ## 0.8.0 278 | 279 | - Migrated to React 15.0.1 280 | - Optimized server-side rendering (~4× faster than Rum 0.7.0, ~2-3× faster than Hiccup 1.0.5) 281 | 282 | ## 0.7.0 283 | 284 | - Server-side rendering via `rum/render-html` (thx [Alexander Solovyov](https://github.com/piranha)) 285 | 286 | ## 0.6.0 287 | 288 | - [ BREAKING ] Updated to [React 0.14.3](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html) (thx [Andrey Antukh](https://github.com/niwinz), [#53](https://github.com/tonsky/rum/pull/53)) 289 | 290 | ## 0.5.0 291 | 292 | - Added `:class-properties` to define arbitrary properties on a React class (thx [Karanbir Toor](https://github.com/currentoor), [#44](https://github.com/tonsky/rum/pull/44)) 293 | - [ BREAKING ] Removed support for `:child-context-types` and `:context-types`. Use `{ :class-properties { :childContextTypes ..., :contextTypes ... } }` instead. 294 | 295 | ## 0.4.2 296 | 297 | - Check for `setTimeout` in global scope instead of in window (thx [Alexander Solovyov](https://github.com/piranha), [#43](https://github.com/tonsky/rum/pull/43)) 298 | 299 | ## 0.4.1 300 | 301 | - Fixed bug with rum macros emitting wrong namespace. You can now require `rum.core` under any alias you want (thx [Stuart Hinson](https://github.com/stuarth), [#42](https://github.com/tonsky/rum/pull/42)) 302 | 303 | ## 0.4.0 304 | 305 | - [ BREAKING ] Core namespace was renamed from `rum` to `rum.core` to supress CLJS warnings 306 | 307 | ## 0.3.0 308 | 309 | - Upgraded to React 0.13.3, Sablono 0.3.6, ClojueScript 1.7.48 310 | - New API to access context: `child-context`, `child-context-types`, `context-types` (thx [Karanbir Toor](https://github.com/currentoor), [#37](https://github.com/tonsky/rum/pull/37)) 311 | - New `defcc` macro for when you only need React component, not the whole Rum state 312 | - [ BREAKING ] Component inner state (`:rum/state`) was moved from `props` to `state`. It doesn’t change a thing if you were using Rum API only, but might break something if you were relaying on internal details 313 | - Deprecated `rum/with-props` macro, use `rum/with-key` or `rum/with-ref` fns instead 314 | 315 | ## 0.2.7 316 | 317 | - Allow components to refer to themselves (thx [Kevin Lynagh](https://github.com/lynaghk), [#30](https://github.com/tonsky/rum/pull/30)) 318 | - Support for multi-arity render fns ([#23](https://github.com/tonsky/rum/issues/23)) 319 | 320 | ## 0.2.6 321 | 322 | - Added `local` mixin 323 | 324 | ## 0.2.5 325 | 326 | - Fixed argument destructuring in defc macro ([#22](https://github.com/tonsky/rum/issues/22)) 327 | 328 | ## 0.2.4 329 | 330 | - `will-update` and `did-update` lifecycle methods added (thx [Andrey Vasenin](https://github.com/avasenin), [#18](https://github.com/tonsky/rum/pull/18)) 331 | 332 | ## 0.2.3 333 | 334 | - Components defined via `defc/defcs` will have `displayName` defined (thx [Ivan Dubrov](https://github.com/idubrov), [#16](https://github.com/tonsky/rum/pull/16)) 335 | - Not referencing `requestAnimationFrame` when used in headless environment (thx @[whodidthis](https://github.com/whodidthis), [#14](https://github.com/tonsky/rum/pull/14)) 336 | 337 | ## 0.2.2 338 | 339 | - Compatibility with clojurescript 0.0-2758, macros included automatically when `(:require rum)` 340 | 341 | ## 0.2.1 342 | 343 | - Updated deps to clojurescript 0.0-2727, react 0.12.2-5 and sablono 0.3.1 344 | 345 | ## 0.2.0 346 | 347 | - [ BREAKING ] New syntax for mixins: `(defc name < mixin1 mixin2 [args] body...)` 348 | - New `defcs` macro that adds additional first argument to render function: `state` 349 | - Ability to specify `key` and `ref` to rum components via `with-props` 350 | 351 | ## 0.1.1 352 | 353 | - Fixed a bug when render-loop tried to `.forceUpdate` unmounted elements 354 | - Fixed a cursor leak bug in `reactive` mixin 355 | - Removed `:should-update` from `reactive`, it now will be re-rendered if re-created by top-level element 356 | - Combine `reactive` with `static` to avoid re-rendering if component is being recreated with the same args 357 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' 19 | from a Contributor if it was added to the Program by such Contributor 20 | itself or anyone acting on such Contributor's behalf. Contributions do not 21 | include additions to the Program which: (i) are separate modules of 22 | software distributed in conjunction with the Program under their own 23 | license agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this 32 | Agreement. 33 | 34 | "Recipient" means anyone who receives the Program under this Agreement, 35 | including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | a) Subject to the terms of this Agreement, each Contributor hereby grants 39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 40 | reproduce, prepare derivative works of, publicly display, publicly 41 | perform, distribute and sublicense the Contribution of such Contributor, 42 | if any, and such derivative works, in source code and object code form. 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | c) Recipient understands that although each Contributor grants the licenses 54 | to its Contributions set forth herein, no assurances are provided by any 55 | Contributor that the Program does not infringe the patent or other 56 | intellectual property rights of any other entity. Each Contributor 57 | disclaims any liability to Recipient for claims brought by any other 58 | entity based on infringement of intellectual property rights or 59 | otherwise. As a condition to exercising the rights and licenses granted 60 | hereunder, each Recipient hereby assumes sole responsibility to secure 61 | any other intellectual property rights needed, if any. For example, if a 62 | third party patent license is required to allow Recipient to distribute 63 | the Program, it is Recipient's responsibility to acquire that license 64 | before distributing the Program. 65 | d) Each Contributor represents that to its knowledge it has sufficient 66 | copyright rights in its Contribution, if any, to grant the copyright 67 | license set forth in this Agreement. 68 | 69 | 3. REQUIREMENTS 70 | 71 | A Contributor may choose to distribute the Program in object code form under 72 | its own license agreement, provided that: 73 | 74 | a) it complies with the terms and conditions of this Agreement; and 75 | b) its license agreement: 76 | i) effectively disclaims on behalf of all Contributors all warranties 77 | and conditions, express and implied, including warranties or 78 | conditions of title and non-infringement, and implied warranties or 79 | conditions of merchantability and fitness for a particular purpose; 80 | ii) effectively excludes on behalf of all Contributors all liability for 81 | damages, including direct, indirect, special, incidental and 82 | consequential damages, such as lost profits; 83 | iii) states that any provisions which differ from this Agreement are 84 | offered by that Contributor alone and not by any other party; and 85 | iv) states that source code for the Program is available from such 86 | Contributor, and informs licensees how to obtain it in a reasonable 87 | manner on or through a medium customarily used for software exchange. 88 | 89 | When the Program is made available in source code form: 90 | 91 | a) it must be made available under this Agreement; and 92 | b) a copy of this Agreement must be included with each copy of the Program. 93 | Contributors may not remove or alter any copyright notices contained 94 | within the Program. 95 | 96 | Each Contributor must identify itself as the originator of its Contribution, 97 | if 98 | any, in a manner that reasonably allows subsequent Recipients to identify the 99 | originator of the Contribution. 100 | 101 | 4. COMMERCIAL DISTRIBUTION 102 | 103 | Commercial distributors of software may accept certain responsibilities with 104 | respect to end users, business partners and the like. While this license is 105 | intended to facilitate the commercial use of the Program, the Contributor who 106 | includes the Program in a commercial product offering should do so in a manner 107 | which does not create potential liability for other Contributors. Therefore, 108 | if a Contributor includes the Program in a commercial product offering, such 109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 110 | every other Contributor ("Indemnified Contributor") against any losses, 111 | damages and costs (collectively "Losses") arising from claims, lawsuits and 112 | other legal actions brought by a third party against the Indemnified 113 | Contributor to the extent caused by the acts or omissions of such Commercial 114 | Contributor in connection with its distribution of the Program in a commercial 115 | product offering. The obligations in this section do not apply to any claims 116 | or Losses relating to any actual or alleged intellectual property 117 | infringement. In order to qualify, an Indemnified Contributor must: 118 | a) promptly notify the Commercial Contributor in writing of such claim, and 119 | b) allow the Commercial Contributor to control, and cooperate with the 120 | Commercial Contributor in, the defense and any related settlement 121 | negotiations. The Indemnified Contributor may participate in any such claim at 122 | its own expense. 123 | 124 | For example, a Contributor might include the Program in a commercial product 125 | offering, Product X. That Contributor is then a Commercial Contributor. If 126 | that Commercial Contributor then makes performance claims, or offers 127 | warranties related to Product X, those performance claims and warranties are 128 | such Commercial Contributor's responsibility alone. Under this section, the 129 | Commercial Contributor would have to defend claims against the other 130 | Contributors related to those performance claims and warranties, and if a 131 | court requires any other Contributor to pay any damages as a result, the 132 | Commercial Contributor must pay those damages. 133 | 134 | 5. NO WARRANTY 135 | 136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 140 | Recipient is solely responsible for determining the appropriateness of using 141 | and distributing the Program and assumes all risks associated with its 142 | exercise of rights under this Agreement , including but not limited to the 143 | risks and costs of program errors, compliance with applicable laws, damage to 144 | or loss of data, programs or equipment, and unavailability or interruption of 145 | operations. 146 | 147 | 6. DISCLAIMER OF LIABILITY 148 | 149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 156 | OF SUCH DAMAGES. 157 | 158 | 7. GENERAL 159 | 160 | If any provision of this Agreement is invalid or unenforceable under 161 | applicable law, it shall not affect the validity or enforceability of the 162 | remainder of the terms of this Agreement, and without further action by the 163 | parties hereto, such provision shall be reformed to the minimum extent 164 | necessary to make such provision valid and enforceable. 165 | 166 | If Recipient institutes patent litigation against any entity (including a 167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 168 | (excluding combinations of the Program with other software or hardware) 169 | infringes such Recipient's patent(s), then such Recipient's rights granted 170 | under Section 2(b) shall terminate as of the date such litigation is filed. 171 | 172 | All Recipient's rights under this Agreement shall terminate if it fails to 173 | comply with any of the material terms or conditions of this Agreement and does 174 | not cure such failure in a reasonable period of time after becoming aware of 175 | such noncompliance. If all Recipient's rights under this Agreement terminate, 176 | Recipient agrees to cease use and distribution of the Program as soon as 177 | reasonably practicable. However, Recipient's obligations under this Agreement 178 | and any licenses granted by Recipient relating to the Program shall continue 179 | and survive. 180 | 181 | Everyone is permitted to copy and distribute copies of this Agreement, but in 182 | order to avoid inconsistency the Agreement is copyrighted and may only be 183 | modified in the following manner. The Agreement Steward reserves the right to 184 | publish new versions (including revisions) of this Agreement from time to 185 | time. No one other than the Agreement Steward has the right to modify this 186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 187 | Eclipse Foundation may assign the responsibility to serve as the Agreement 188 | Steward to a suitable separate entity. Each new version of the Agreement will 189 | be given a distinguishing version number. The Program (including 190 | Contributions) may always be distributed subject to the version of the 191 | Agreement under which it was received. In addition, after a new version of the 192 | Agreement is published, Contributor may elect to distribute the Program 193 | (including its Contributions) under the new version. Except as expressly 194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 195 | licenses to the intellectual property of any Contributor under this Agreement, 196 | whether expressly, by implication, estoppel or otherwise. All rights in the 197 | Program not expressly granted under this Agreement are reserved. 198 | 199 | This Agreement is governed by the laws of the State of New York and the 200 | intellectual property laws of the United States of America. No party to this 201 | Agreement will bring a legal action under this Agreement more than one year 202 | after the cause of action arose. Each party waives its rights to a jury trial in 203 | any resulting litigation. 204 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.9.0"} 2 | org.clojure/clojurescript {:mvn/version "1.10.773"} 3 | cljsjs/react {:mvn/version "16.8.6-0"} 4 | cljsjs/react-dom {:mvn/version "16.8.6-0"}} 5 | :aliases {:test {:extra-paths ["test"]}}} 6 | -------------------------------------------------------------------------------- /doc/cljdoc.edn: -------------------------------------------------------------------------------- 1 | {:cljdoc.doc/tree [ 2 | ["Readme" {:file "README.md"}] 3 | ["Changelog" {:file "CHANGELOG.md"}] 4 | ["Useful mixins" {:file "doc/useful-mixins.md"}] 5 | ["React interop" {:file "doc/react-interop.md"}] 6 | ["React Suspense and code-splitting" {:file "doc/react-suspense-and-code-splitting.md"}] 7 | ["React Hooks" {:file "doc/react-hooks.md"}] 8 | ]} 9 | -------------------------------------------------------------------------------- /doc/optimizations.md: -------------------------------------------------------------------------------- 1 | # Optimizations 2 | 3 | ## When to use `rum/static` mixin? 4 | 5 | `rum/static` applies `shouldComponentUpdate` or `React.memo`, in case of hooks-based components, optimization to underlying React component. The role of this optimization is to check if component's arguments are different from ones passed-in in the previous render of the component. If they didn't change, then React will skip running the component and will reuse returned value from previous render. In other words it's a memoization of React components based on the arguments from the most recent call to a component. 6 | 7 | It's tempting to apply this optimization to every single component when using React in ClojureScript, because we know that equality check on immutable data is free. 8 | 9 | Unfortunately this is not entirely true. Understanding this is important when making a decision to optimize particular component. 10 | 11 | Equality check on immutable data is fast when it falls into identity check, for example `=` operation in this case will short-circuit on `identical?` check (which is performed inside of `=`). 12 | ```clojure 13 | (def x {:key :value}) 14 | 15 | (= x x) 16 | ``` 17 | 18 | On the other hand comparing two values created from scratch will perform value equality operation (deep equals), which walks both entire data structures to figure out if they are equal. 19 | ```clojure 20 | (= {:key :value} {:key :value}) 21 | ``` 22 | 23 | ### When `=` is fast? 24 | 25 | Structural sharing is what makes equality check fast on immutable data structures. This obviously doesn't apply to values created from scratch, as in the example above, because they don't have shared structure. 26 | 27 | But updating a value and comparing the result to the original value will be faster. 28 | ```clojure 29 | (def x {:x {:a 1 :b 2} 30 | :y {:c 3 :d 4}}) 31 | 32 | (def y (update-in x [:x :a] inc)) 33 | 34 | (= x y) 35 | ``` 36 | In the above case `x` and `y` share `:y {:c 3 :d 4}` part, which will be checked only with `identitcal?` and then `:x {:a 1 :b 2}` will be traversed fully, since this path is updated in `y` and thus not `identitcal?` anymore. 37 | 38 | In the context of React, patterns with central data store, such as re-frame, benefit from structural sharing, because re-frame is updating subtrees of the original value. Taking those subtrees from central data store and passing them into memoized React components will result in an efficient memoization, where most of the time equality check will be short-circuited with `identical?` call. 39 | 40 | But creating data locally in a component, such as a hash map of attributes, means that on every run of a component those values has to be compared by value (deep equals), because they are created from scratch (no shared structure). 41 | 42 | How to use this information now? The thing is that sometimes running a component again would be cheaper than comparing its arguments, especially for components that are frequently updated with a different set of values. That also depends on React wrapper library that you are using. For example in Reagent, where Hiccup is interpreted at runtime it's very likely might be the case that memoizing a component would be cheaper than running it, since Hiccup has to be transformed into `React.createElement` calls. In Rum for example, where most of the Hiccup is pre-compiled into React calls via `defc` macro the perf hit is lower than in Reagent, thus the possibility of usefulness of memoization is lower. 43 | -------------------------------------------------------------------------------- /doc/react-hooks.md: -------------------------------------------------------------------------------- 1 | # React Hooks 2 | 3 | Starting from `0.11.5` Rum provides wrappers for a set of React Hooks that can be used from Rum components. 4 | 5 | ## Limitations 6 | 7 | First things first, hooks can be used only inside of `defc` components with optional `rum/static` mixin that enables component's memoization based on its arguments. The reason for that is that React Hooks work only in function-based components and Rum generates those for `defc` components that are not using mixins, because mixins are meant to be executed in lifecycle methods of class-based components. With that make sure that you are not using both hooks and mixins in a single component, otherwise Rum fallsback to generating class-based components and React will throw an expection about incorrect usage of hooks. 8 | 9 | ## About rum/static 10 | 11 | `rum/static` is a mixin that enables component's memoization based on arguments. In class-based components it declares `shoudComponentUpdate` method that compares previous and new arguments. When used as the only mixin in `defc` we generate function-based component and use `rum/static` as a compile-time marker to generate `React.memo` wrapper for a component. `React.memo` is the same as `shoudComponentUpdate`, but meant to be used with function-based components. 12 | 13 | ## Local state hook 14 | 15 | `rum/use-state` takes initial state value and returns a tuple, where the first entry is current state and the second one is a function that takes a new state and schedules an update of the component. It's important to understand that the update is scheduled, which means that the component will be re-rendered eventually, not syncronously. Additionally instead of initla value the hook can take a function that computes initial state, so that the initial value won't be recomputed on every update, but only once, when component is instantiated. 16 | 17 | ```clojure 18 | (rum/defc input-field [] 19 | (let [[value set-value!] (rum/use-state "")] 20 | [:input {:value value 21 | :on-change #(set-value! (.. % -target -value))}])) 22 | ``` 23 | 24 | ## Effect hook 25 | 26 | `rum/use-effect!` can be thought of as a replacement of `:did-mount`, `:will-unmount` and `:did-update` mixins. The hook takes a setup function which optionally can return a cleanup function. The former is meant to be used for side-effects execution and the latter to cleanup the result of that operation, if needed. 27 | 28 | In this example the component will setup a global `keydown` handler after every update and remove the handler right before every update. This makes sense in cases when the handler can schedule another update for example by updating local state, so we don't want this to happen when the component is already in update phase. 29 | 30 | ```clojure 31 | (rum/defc input-field [] 32 | (rum/use-effect! 33 | (fn [] 34 | (let [handler #(println :key (.-key %))] 35 | (.addEventListener js/document "keydown" handler) 36 | #(.removeEventListener js/document "keydown" handler)))) 37 | ...) 38 | ``` 39 | 40 | But when you only want to setup event listener once, when component is instantiated and remove it right before component gets removed from UI tree you should use the second argument to the hook, which is a collection of dependencies. Dependencies should be used for conditional execution of hooks. If previous deps are different from new ones, after an update, then the hook will re-execute. In case when we want it to execute only once, on mount and before unmount, deps collection should be an empty collection, just `[]`. 41 | 42 | ## Limitation of dependencies collection 43 | 44 | While the collection itself can be either JS Array or Clojure's Vector etc. the entries will be always compared by identity `identical?`, not by value as you would usually expect this in Clojure. The reason for that is that the eqaulity check is performed on React's side and the API doesn't have a way to provide a custom comparator for us. 45 | 46 | ## Callback caching hook 47 | 48 | `rum/use-callback` caches a callback function based on provided dependencies. In cases when you have a parent component with local state that gives control of updating it to child components via passed callback function child components will be updated every time the parent component updates, even though child components are memoized with `rum/static` mixin. That happens because a callback function is re-created on every update, whicn invalidates memoize child components. The hook is able to cache the callback so that it's not re-created unless the dependencies change. 49 | 50 | ```clojure 51 | (rum/defc list-item* < rum/static [on-click] 52 | ...) 53 | 54 | (rum/defc list-item [idx on-click] 55 | (let [handle-click (rum/use-callback #(on-click idx %) [idx]) 56 | [list-item* handle-click]])) 57 | ``` 58 | 59 | ## Memoization hook 60 | 61 | `rum/use-memo` is meant to be used for caching values and expensive computations, again re-evalution is controlled by deps collection. In the example below the component caches an instance of a class from some JavaScript library so that it's not re-instantiated on every update even though `config` doesn't change. 62 | 63 | ```clojure 64 | (rum/defc component [config] 65 | (let [js-lib (rum/use-memo #(js/LibClass. config) [config])] 66 | ...)) 67 | ``` 68 | -------------------------------------------------------------------------------- /doc/react-interop.md: -------------------------------------------------------------------------------- 1 | # Use different React version 2 | 3 | Add to `project.clj`: 4 | 5 | ``` 6 | :dependencies { 7 | [rum "0.11.3" :exclusions [[cljsjs/react] [cljsjs/react-dom]]] 8 | [cljsjs/react "16.6.0-0"] 9 | [cljsjs/react-dom "16.6.0-0"] 10 | } 11 | ``` 12 | 13 | # Including React.js manually 14 | 15 | If you want to include `react.js` yourself, then add this to `project.clj`: 16 | 17 | ``` 18 | :dependencies { 19 | [rum "0.11.3" :exclusions [[cljsjs/react] [cljsjs/react-dom]]] 20 | } 21 | ``` 22 | 23 | Create two files 24 | 25 | 1. `src/cljsjs/react.cljs`: 26 | 27 | ``` 28 | (ns cljsjs.react) 29 | ``` 30 | 31 | 2. `src/cljsjs/react/dom.cljs`: 32 | 33 | ``` 34 | (ns cljsjs.react.dom) 35 | ``` 36 | 37 | Add to your HTML the version you want: 38 | 39 | ``` 40 | 41 | 42 | ``` 43 | 44 | # Using React with addons 45 | 46 | ```clj 47 | [rum "0.11.3" :exclusions [cljsjs/react cljsjs/react-dom]] 48 | [cljsjs/react-dom "16.2.0-3" :exclusions [cljsjs/react]] 49 | [cljsjs/react-dom-server "16.2.0-3" :exclusions [cljsjs/react]] 50 | [cljsjs/react-with-addons "16.2.0-3"] 51 | ``` 52 | 53 | # Profiling with [React perf](https://facebook.github.io/react/docs/perf.html) 54 | 55 | Specify the `react-with-addons` dependency in your `project.clj` (see above ↑) 56 | 57 | Then from within your program run: 58 | 59 | ```clj 60 | (js/React.addons.Perf.start) 61 | ;;run your app 62 | (js/React.addons.Perf.stop) 63 | (js/React.addons.Perf.printWasted) 64 | ``` 65 | 66 | and results will be printed to the developer console. 67 | 68 | # Using 3rd-party React components 69 | 70 | ## via Adapter API 71 | 72 | Since `0.11.5` Rum provides an API to adapt React component for usage in Rum components. But you can still create components manually as described in the next section. 73 | 74 | ```clojure 75 | (rum/defc component [] 76 | [:div 77 | (rum/adapt-class js/Slider 78 | {:min min 79 | :max max 80 | :range true 81 | :defaultValue [40 60]})]) 82 | ``` 83 | 84 | When rendering on JVM Rum browsides a hook to fallback rendering of JS components, so you can delegate this work to JS environment such as GraalJS or renderer a placeholder instead. 85 | 86 | ```clojure 87 | (defn render-js-component [type-sym attrs children] 88 | (case type-sym 89 | 'js/Slider (rum/render-static-markup (slider-placeholder)) 90 | nil)) 91 | 92 | (binding [rum/*render-js-component* render-js-component] 93 | (component)) 94 | ``` 95 | 96 | ## via React.js directly 97 | 98 | Given e.g. [react-router-transition](https://github.com/maisano/react-router-transition) 99 | 100 | ```clj 101 | (defn route-transition [pathname children] 102 | (js/React.createElement js/RouteTransition 103 | #js { :pathname pathname 104 | :atEnter #js { :opacity 0 } 105 | :atLeave #js { :opacity 0 } 106 | :atActive #js { :opacity 1 } } 107 | (clj->js children))) 108 | ``` 109 | 110 | Another example [react-component/slider](https://github.com/react-component/slider) 111 | 112 | ```clj 113 | (defn range-slider [min max] 114 | (js/React.createElement js/Slider #js { :min min 115 | :max max 116 | :range true 117 | :defaultValue #js [40 60] })) 118 | ``` 119 | 120 | If you want to mix 3rd-party React components with child elements using the Hiccup-like syntax, you can call directly into the library that provides it, daiquiri. This can be particularly useful for 3rd-party React components that are made to wrap your own components, like drag-and-drop plugins and so on. 121 | 122 | ```clj 123 | (js/React.createElement js/MyComponent 124 | #js { } 125 | (daiquiri.core/html [:div [:p "Hello, world"]])) 126 | ``` 127 | 128 | **Note:** See how `defn` is used here instead of `defc`? Using `defc` would cause two components being created (e.g. `range-slider` and the `Slider` component). Because in many cases you don't need the wrapping component you can just use `defn`. 129 | 130 | # Using Rum component in React 131 | 132 | In order to use Rum component from React JS code you have to create gluing layer in a form of wrapping function (React component) that unwraps React's props object and passes values into Rum component as arguments. 133 | 134 | ```clojure 135 | (rum/defc button [{:keys [on-click]} text] 136 | [:button {:on-click on-click} 137 | text]) 138 | 139 | (defn Button [^js props] 140 | (button {:on-click (.-onClick props)} 141 | (.-children props))) 142 | ``` 143 | 144 | # Get displayName of component 145 | 146 | This might be useful for development when you want to know which component this mixin is handling: 147 | 148 | ```clojure 149 | (ns ... 150 | (:require [goog.object :as gobj])) 151 | 152 | (defn display-name 153 | "Returns the displayname of the component" 154 | [state] 155 | (gobj/getValueByKeys 156 | (:rum/react-component state) 157 | "constructor" 158 | "displayName")) 159 | ``` 160 | -------------------------------------------------------------------------------- /doc/react-suspense-and-code-splitting.md: -------------------------------------------------------------------------------- 1 | # React Suspense and code-splitting 2 | 3 | > This requires Rum 0.11.5-SNAPSHOT or newer 4 | 5 | [React Suspense](https://reactjs.org/docs/code-splitting.html) allows loading components lazily from code chunks while displaying a fallback UI when a chunk is loading. 6 | 7 | Create build configuration in `build.edn`. `:modules` describe code chunks with their corresponding entry points, read more about code-splitting at [https://clojurescript.org/guides/code-splitting](https://clojurescript.org/guides/code-splitting) 8 | 9 | ```clojure 10 | {:output-dir "resources/public/out" 11 | :asset-path "out" 12 | :optimizations :advanced 13 | :modules {:core 14 | {:entries #{example.core} 15 | :output-to "resources/public/out/core.js"} 16 | :components 17 | {:entries #{example.components} 18 | :output-to "resources/public/out/components.js"}}} 19 | ``` 20 | 21 | In `example.components` namespace we declare a component that will be used later in `example.core` ns, which will be in another chunk. For that to work we have to instruct explicitly chunks loader runtime that it was loaded. `cljs.loader/set-loaded!` takes the name of the chunk as specified in build config. 22 | 23 | ```clojure 24 | (ns example.components 25 | (:require [rum.core :as rum] 26 | [cljs.loader :as loader])) 27 | 28 | (rum/defc alert [arg] 29 | [:h1 arg]) 30 | 31 | (loader/set-loaded! :components) 32 | ``` 33 | 34 | In `example.core` we do the same to instruct about when chunk loading is done, but we also using `require-lazy` macro to require `alert` component from a namespace in another chunk. Then the component is wrapped in `suspense` component that takes care of loading and displaying a fallback. 35 | 36 | ```clojure 37 | (ns example.core 38 | (:require [cljs.loader :as loader] 39 | [rum.core :as rum] 40 | [rum.lazy-loader :refer [require-lazy]])) 41 | 42 | (require-lazy '[example.components :refer [alert]]) 43 | 44 | (rum/defc root [] 45 | (rum/suspense {:fallback "Hello!"} 46 | (alert "ARGUMENT"))) 47 | 48 | (loader/set-loaded! :core) 49 | 50 | (rum/mount (root) (.getElementById js/document "root")) 51 | ``` 52 | 53 | Now if you build the code `clj -m cljs.main -co build.edn -c example.core` you'll get 3 chunks: `core.js`, `components.js` and `cljs_base.js` which includes the code shared between those two chunks. 54 | 55 | Your HTML should look like this. 56 | 57 | ```html 58 |
59 | 60 | 61 | ``` 62 | -------------------------------------------------------------------------------- /doc/useful-mixins.md: -------------------------------------------------------------------------------- 1 | # Request stuff on mount by AJAX 2 | 3 | Components using this mixin will do an AJAX request on mount and will update themselves when they got a reply. Mixin puts an atom to the state whose value (after deref) is either nil (request pending) or returned value. 4 | 5 | ```clojure 6 | (defn ajax-mixin [url key] 7 | { :will-mount 8 | (fn [state] 9 | (let [*data (atom nil) 10 | comp (:rum/react-component state)] 11 | (ajax 12 | url 13 | (fn [data] 14 | (reset! *data data) 15 | (rum/request-render comp))) 16 | (assoc state key *data))) }) 17 | 18 | 19 | (rum/defcs user-info < (ajax-mixin "/api/user/info" ::user) 20 | [state] 21 | (if-let [user @(::user state)] 22 | ... 23 | [:div "Loading..."])) 24 | ``` 25 | 26 | Customize to your taste: dynamic URL generation from component args, AJAX retries, failed state, callback on finish, deserialization. 27 | 28 | # Debouncer 29 | 30 | ```clojure 31 | (ns ... (:import [goog.async Debouncer])) 32 | 33 | (defn debouncer-mixin 34 | "Creates a debouncer in (:debouncer state) which can be called with (.fire). 35 | Invokes the callback cb or invokes the first argument passed to fire. 36 | Usage: 37 | 1. (debouncer-mixin 200) 38 | (.fire (:debouncer state) #(do-actual-action ...)) 39 | 40 | 2. (debouncer-mixin 200 #(do-an-action ...)) 41 | (.fire (:debouncer state))" 42 | ([ms] (debouncer-mixin ms nil)) 43 | ([ms cb] 44 | {:will-mount 45 | (fn debouncer-mount [state] 46 | (assoc state :debouncer (Debouncer. (if (nil? cb) #(%1) cb) ms))) 47 | :will-unmount 48 | (fn debouncer-unmount [state] 49 | (.dispose (:debouncer state)) 50 | state)})) 51 | ``` 52 | 53 | # Install CSS styles on mount 54 | 55 | For your root rum component, you can install CSS styles on mount and uninstall them on unmount. 56 | This is useful if you have your (garden) styles defined in `cljc` file. In production you generate 57 | a css file and include it with a normal `0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |||
---|---|---|---|---|---|---|---|---|---|---|---|
Renders: 32 |
1
Server: CAUGHT: clojure.lang.ExceptionInfo: render error {}
Client: