└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # React Native With ClojureScript 2 | 3 | 4 | This repo is the result from my experimentations writing a native iOS application using react-native with Clojurescript. 5 | I've tried re-natal with om-next first, but found it quite difficult to use due to the lack of documentation. Then I tried re-natal with re-frame and found that I got something working more rapidly. 6 | 7 | Below is a collection of links, interesting ramblings on Slack from the #cljsrn channel. I'll try to add simple examples to get started and keep it up-to-date with newest versions of react-native and re-natal. 8 | 9 | ## Real apps written in React-Native 10 | 11 | - Uber https://eng.uber.com/ubereats-react-native/ 12 | - Airbnb https://speakerdeck.com/felipecsl/react-native-at-airbnb 13 | 14 | 15 | ## Interop with JavaScript 16 | 17 | It's important to understand how to do proper interop between ClojureScript and JavaScript, both ways. 18 | 19 | - Very useful page from @buskana to translate javascript idioms into ClojureScript: https://kanaka.github.io/clojurescript/web/synonym.html 20 | 21 | - How to import nodejs modules from ClojureScript https://anmonteiro.com/2017/03/requiring-node-js-modules-from-clojurescript-namespaces/ 22 | 23 | - Types and ClojureScript ops 24 | By D. Nolen https://github.com/cljs/api/issues/128#issuecomment-122271806) 25 | 26 | | Type | Op | 27 | | --- | --- | 28 | |ILookup | get or get-in | 29 | |js/Array | aget | 30 | |js/Object | goog.object/get or goog.object/getValueByKeys | 31 | 32 | - Master the Dot syntax with http://cljs.github.io/api/syntax/dot. Ex to get the value of an InputText: 33 | `(.-target (.-value %))` 34 | 35 | 36 | ## Tutorials 37 | - http://cljsrn.org/, talks and videos section 38 | - https://github.com/rockiger/re-natal-tutorial 39 | 40 | 41 | ## Example apps 42 | - https://github.com/Slowyn/DevDayToDo 43 | - https://github.com/areina/elfeed-cljsrn 44 | - https://github.com/madvas/catlantis 45 | - https://github.com/drapanjanas/pneumatic-tubes/tree/master/examples/group-chat-app (web socket w/o sente, w/ http-kit) 46 | - https://github.com/Trustroots/Trustroots-React-Native 47 | - https://github.com/alwx/luno-react-native + [Experience Report](https://medium.com/@alwxdev/react-native-summing-up-86838441d289) 48 | - https://github.com/tiensonqin/exponent-cljs-template (examples for nav, supports om-next and re-frame) 49 | - https://github.com/Quantisan/re-frame-firebase-example (firebase + re-frame) 50 | 51 | 52 | ## nice components 53 | - https://github.com/24ark/react-native-step-indicator 54 | 55 | ## Frontend libraries/frameworks 56 | You can use om-next,[re-frame](https://github.com/Day8/re-frame), or rum. 57 | 58 | #### Om-next 59 | If you use om-next, check out this blog post on writing om-next reloadable code : https://anmonteiro.com/2016/01/writing-om-next-reloadable-code-a-checklist/. 60 | 61 | #### Re-Frame 62 | - Read its stellar documentation: https://github.com/Day8/re-frame 63 | - Reagents components: https://github.com/reagent-project/reagent/wiki/Links-and-Resources 64 | - Re-frame workers: https://github.com/seantempesta/cljsrn-re-frame-workers 65 | 66 | 67 | ## Tooling 68 | 69 | #### Re-natal 70 | 71 | #### How to add an external library 72 | 1. `re-natal use-component react-native-store re-natal use-figwheel` 73 | 2. Check component is listed in `.re-natal` and in `package.json` 74 | 3. `re-natal use-figwheel` 75 | 4. `lein figwheel ios` since `re-natal use-figwheel` does a clean first. 76 | 5. Check that `localhost:8081/index.ios.bundle` lists the component. 77 | 6. Run `(js/require "react-native-store")` in the repl. 78 | 79 | #### Boot-react-native 80 | 81 | Source maps have been disabled for a while : 82 | https://github.com/mjmeintjes/boot-react-native/issues/58 83 | 84 | @pesterhazy: one thing that is great is that brn integrates with RN's packager directly, so you get all its features. including pretty fast reloading, requiring images just works 85 | @pesterhazy: boot-react-native badly needs updating, code and docs 86 | 87 | Blog post of Aug 2016: http://presumably.de/boot-react-native.html 88 | 89 | Troubleshooting: https://github.com/mjmeintjes/boot-react-native/wiki/Troubleshooting 90 | 91 | Proper restart 92 | - restart the app (not relying on boot-reload) 93 | - clearing the packager cache `react-native start --refresh-cache true` 94 | - check the bundle output `http://localhost:8081/index.ios.bundle?platform=ios&dev=true&hot=true` 95 | 96 | 97 | ## Navigation 98 | Facebook has deprecated all pre-existing navigation mechanism in favor of their new contribution: [react-navigation](https://reactnavigation.org). Its run by the react community, so not by Facebook directly, but it's endorsed: https://github.com/react-community/react-navigation. 99 | 100 | Sean Tempesta has written and published an excellent starter lib which specced out react-navigation. You can use whatever your wrapper is (currently it only provides out-of-the-box bindings for reagent and re-frame but it can be extended to support Rum and Om-Next: https://github.com/seantempesta/cljs-react-navigation 101 | 102 | Two other examples of using it is 103 | - Using Re-Frame : https://github.com/vikeri/re-navigate. 104 | - Using Om-Next: https://github.com/amorokh/om-navigate 105 | 106 | So, these libraries aren't useful anymore : 107 | - [Navigator](https://facebook.github.io/react-native/docs/navigation.html#navigator) (and its close cousin [NavigatorIOS] 108 | (https://facebook.github.io/react-native/docs/navigation.html#navigatorios)): included with RN, janky imperative interface 109 | - [NavigationExperimental](https://facebook.github.io/react-native/docs/navigation.html#navigationexperimental): also included in RN, more functional but still has "experimental" in it name 110 | - [exponent/ex-navigator](https://github.com/exponent/ex-navigator): provided by exponent, a wrapper around Navigator with a better interface (*deprecated in favor of react-navigation?*) 111 | - [exponent/ex-navigation](https://github.com/exponent/ex-navigation): also by exponent, a successor (?) of ex-navigator (*deprecated in favor of react-navigation?*) 112 | - [wix/react-native-navigation](https://github.com/wix/react-native-navigation): an independent "native" navigator, iOS only so far 113 | - [react-native-router-flux](https://github.com/aksonov/react-native-router-flux) 114 | 115 | 116 | #### Customize navigation 117 | 118 | > gphilipp [16:12] 119 | > @seantempesta do you know how to customize the header from the screen ? Basically, replicate this example from the react-navigation doc: https://reactnavigation.org/docs/intro/headers 120 | 121 | > seantempesta 122 | > @gphilipp: So, you’ve got two options. You can statically assign `navigationOptions` or you can use a function and dynamically return the `navigationOptions`. Here are some examples from the login screen in my re-frame example: 123 | 124 | ```clojure 125 | (def static-navigationOptions {:headerTitle "Login" 126 | :headerRight (fn [] 127 | [:> Button {:title "Sign In" 128 | :onPress #(dispatch [:login])}])}) 129 | 130 | (defn dynamic-navigationOptions [{:keys [navigation] :as props}] 131 | (let [navigate (:navigate navigation)] 132 | {:headerTitle "Login" 133 | :headerRight (fn [] 134 | [:> Button {:title "Sign In" 135 | :onPress #(navigate "Loading")}])})) 136 | 137 | ``` 138 | 139 | 140 | 141 | #### High-level design 142 | - Routers define the relationship between URIs, actions, and navigation state. They allow to share navigation logic between mobile apps, web apps, and server rendering. 143 | - Navigators allow you to define your application's navigation structure. Navigators also render common elements such as headers and tab bars which you can configure. Under the hood, navigators are plain React components. 144 | 145 | ## Components 146 | 147 | - Nice component wrapper lib: https://github.com/re-native 148 | 149 | 150 | ## Lists 151 | 152 | Facebook has implemented a virtualized list to reduce memory consumption: https://facebook.github.io/react-native/blog/2017/03/13/better-list-views.html 153 | 154 | > @raspasov [on 2017-03-30](https://clojurians-log.clojureverse.org/cljsrn/2017-03-30.html): ...I’ve replaced all my old ListView cases with VirtualizedList, works very well so far. 155 | 156 | - Example of VirtualizedList with Om-Next: https://gist.github.com/raspasov/e9a1008f2c0d5be2d202f0a4cdebe009 157 | - Example of VirtualizedList with Reagent: https://gist.github.com/seantempesta/8b01e71148d9b01f8591d083926e2c89 158 | 159 | 160 | ## Running on device 161 | 1. Get IP address from iphone settings 162 | 2. Run `re-natal use-ios-device` with that IP address. 163 | 3. Run `lein prod-build` 164 | 4. Open Xcode, changed scheme configuration from _Debug_ to _Release_, change platform to _physical device_ 165 | 5. Hit Run on Xcode. 166 | 167 | 168 | ## Debugging 169 | - shake the device simulator -> enable browser debugging. It'll print exceptions in the console. some will have only js code "lines", some will have cljs ones (https://www.jetbrains.com/help/idea/2016.1/debugging-javascript.html?origin=old_help) 170 | - Device log: `react-native log-ios` 171 | 172 | #### Frisk to visualize state 173 | - https://github.com/flexsurfer/re-frisk/wiki/Using-re-frisk-with-re-natal 174 | - https://github.com/flexsurfer/lein-re-frisk/blob/master/plugin/src/leiningen/re_frisk.clj 175 | 176 | 177 | ## Re-natal Hack 178 | See the support directory and the build.boot file: https://github.com/kennyjwilli/postal-app 179 | 180 | ## Re-natal import external components 181 | - `react-native link react-native-sound && npm install react-native-sound —save && re-natal use-component react-native-sound` 182 | - Optionally : `re-natal use-figwheel` 183 | 184 | ## Shipping 185 | - fastlane allows you to push to 186 | flight, using a tool called pilot 187 | - Blog post: http://blog.thebakery.io/continuous-integration-for-react-native-applications-with-fastlane-and-bitrise-ios-version/ 188 | - https://facebook.github.io/react-native/docs/running-on-device-ios.html#building-your-app-for-production 189 | 190 | > @savelichalex: continuous integration that pushes every commit to master to testflight is a total life-saver you can build the archive e.g. using xctool 191 | 192 | 193 | ## Testing 194 | 195 | - At Facebook, we use Jest to test React Native applications. https://facebook.github.io/jest/docs/tutorial-react-native.html 196 | 197 | - @pesterhazy: personally I've concluded that integration testing isn't worth it for small teams when using react native. Because it's hard to make reliable. 198 | 199 | 200 | - Vikeri: @seantempesta What we’re doing is using https://github.com/airbnb/enzyme and shallow-render the components. That will not generate anything useful but at least it will throw if there are any js-errors. But fb have released a new snapshot test feature for jest that would probably be more useful: https://facebook.github.io/jest/docs/tutorial-react-native.html. We’re doing this for spec tests: 201 | 202 | ``` clojure 203 | (defmacro generative-tests 204 | "Takes a list of fn symbols and runs generative tests on them" 205 | [fn-syms] 206 | (let [opts# {:clojure.test.check/opts {:num-tests 1}} 207 | long-res# (cljs.spec.test/check ~fn-syms opts#) 208 | res# (cljs.spec.test/summarize-results long-res#)] 209 | (cljs.test/is (-> ~fn-syms empty? not) "A namespace was empty") 210 | (cljs.test/is 211 | (not (or (:check-failed res#) 212 | (:check-threw res#) 213 | (:instrument res#))) 214 | (str "Spec failed for " (map :sym (filter :failure long-res#)))))) 215 | 216 | (s/fdef generative-tests :args (s/cat :syms (s/coll-of any?))) 217 | 218 | (defn- gen-test ([n] (gen-test n #{})) 219 | ([n {:keys [exclude]}] `(generative-tests 220 | (clojure.set/difference 221 | (enumerate-namespace ~n) ~exclude)))) 222 | 223 | (defmacro gen-test-ns 224 | "Takes a ns and optionally excluded syms and runs generative tests for all the functions" 225 | [& args] 226 | (apply gen-test args)) 227 | 228 | ``` 229 | 230 | - Acceptance Testing: https://github.com/wix/detox 231 | - Integration Testing framework for RN: https://github.com/pixielabs/cavy 232 | - Vikeri: We are using doo + re-frame-test for testing. I talk a little about it here: https://youtu.be/6IYm34nDL64?t=9m3s (didn’t use re-frame-test then though.) 233 | - Sean Tempesta: No idea if it works with RN, but I just attended a talk on iOS testing where this guy did 2 months of research and he recommended Calabash. http://calaba.sh/ 234 | 235 | - @ilmirajat (author of https://github.com/Trustroots/Trustroots-React-Native) : Btw. do any of you have good refence how unit tests code properly in Cljsrn. The best reference I've found, is this https://github.com/futurice/pepperoni-app-kit. It uses Enzyme (https://github.com/airbnb/enzyme) and React-native-mock for mocking hardware. It was pure pain to get unit tests work reasonably well, and I'm not quite satisfied to my solution. 236 | 237 | ## Logging 238 | - Use `react-native log-ios` in a separate terminal (cluttered with random, useless messages that it's really hard to read says @pesterhazy) 239 | - You can use timbre in conjunction with cljs-devtools, you can use this code https://github.com/ptaoussanis/timbre/issues/132#issuecomment-180268825 240 | 241 | ## Building 242 | 243 | Avanced compilation for production requires rn-externs: https://github.com/artemyarulin/react-native-externs 244 | > @artemyarulin: well, this is a way how advanced compilation works - without externs file Google Closure rename a lot of important staff 245 | Then @pesterhazy says that advanced compilation is not necessarily worth the effort on react native because bundle size doesn't matter as much as on the web. @artemyarulin replies that there are still to cases for that: 1) If you want to do hot deploys and do it daily or more often (like we used to do in web) and 2) GC is best obfuscator - not a big deal for certain cases, but it drives me crazy a bit that somebody can unpack bundle and recreate the app in 10 minutes. @pesterhazy agrees and adds that there might be speed bumps too. 246 | (https://clojurians-log.clojureverse.org/cljsrn/2016-08-05.html) 247 | 248 | 249 | 250 | ## Fetching Data 251 | 252 | ```clojure 253 | (def fetch (.-fetch js/window)) (register-handler :load-data (fn [db _] (.then (fetch "[https://api.github.com/repositories](https://api.github.com/repositories)") #((.warn js/console (.stringify (.-JSON js/window) %1)(dispatch :process-data %1)))))) 254 | ``` 255 | 256 | pesterhazy 12:02:06 257 | @debug, now what you mention it, I saw a similar issue when porting my code to Android, and also ended up using js/fetch directly 258 | 259 | ```clojure 260 | (.then 261 | (js/window.fetch 262 | "https://api.github.com/repositories") 263 | #(println %) 264 | #(println "rejected" %)) 265 | ``` 266 | 267 | ```clojure 268 | (defn request* 269 | [uri {:keys [request-method on-success on-error retry? retries params] 270 | :or {retries default-retries 271 | request-method :get} 272 | :as opts}] 273 | (assert (#{:get :post} request-method) (str "invalid request method " request-method)) 274 | (let [uri* (str (client-conf-api) 275 | (to-uri uri) 276 | (when (= request-method :get) 277 | (str "?" (query-string params))))] 278 | (log/info "fetch:" uri*) 279 | (-> uri* 280 | (js/fetch (clj->js (merge {:method ({:post "POST" :get "GET"} request-method) 281 | :headers (if (= request-method :post) 282 | {"Accept" "application/transit+json" 283 | "Content-Type" "application/transit+json"} 284 | {"Accept" "application/transit+json"})} 285 | (when (= request-method :post) 286 | {:body (transit/write params)})))) 287 | (.then (fn [response] 288 | (log/info "got resonse") 289 | (.text response))) 290 | (.then (fn [text] 291 | (log/info "got text") 292 | (log/info "decoded:" (transit/read text)) 293 | (on-success (transit/read text)))) 294 | (.catch (fn [err] 295 | (log/error "Oops, an error occurred:" err)))))) 296 | ``` 297 | 298 | 299 | Issues 300 | - https://github.com/facebook/react-native/issues/5347 301 | - https://github.com/JulianBirch/cljs-ajax/blob/master/src/ajax/xml_http_request.cljs#L20 302 | 303 | 304 | ## Animations 305 | 306 | oh my! 60 fps UI on iphone 5 device :aw_yeah: 307 | ```clojure 308 | (def ReactNative (js/require "react-native")) (def interaction-manager (.-InteractionManager ReactNative)) 309 | (defn on-click [f] (fn [] (.-requestAnimationFrame js/window #(.runAfterInteractions interaction-manager f)))) 310 | ... (touchable-highlight {:onPress (on-click #(...))} 311 | ``` 312 | 313 | 314 | ## Disable Logs 315 | 316 | 1. You can disable painful messages in XCode like `"nw_connection_get_connected_socket_block_invoke 476 Connection has no connected handler` with this trick https://github.com/facebook/react-native/issues/10027#issuecomment-249338419 317 | 318 | 2. Until [it's done properly](https://github.com/Day8/re-frame/pull/254), you can also disable re-frame warning messages caused by figwheel reloads : 319 | https://github.com/Day8/re-frame/issues/204#issuecomment-250337344 320 | 321 | 322 | ## Tips 323 | 1. you can enable custom formatters in Chrome with [cljs-devtools](https://github.com/binaryage/cljs-devtools) 324 | 2. Recently many dot-forms did not work for me because those required symbol instead of expression as a first argument in things like: 325 | 326 | ```clojure 327 | (.. (expr) -someAttr -anotherAttr) 328 | ``` 329 | 330 | so I used let, and it worked 331 | ``` clojure 332 | (let [s (expr)] (.. s -someAttr -anotherAttr) 333 | ``` 334 | 335 | 3. use refs w/ reagent : [example1](https://clojurians.slack.com/files/andre.richards/F1Y699AMV/drawer___ref.clj), [example 2](https://gist.github.com/pesterhazy/4d9df2edc303e5706d547aeabe0e17e1) 336 | 337 | 338 | ## Build plugin offline 339 | https://github.com/vikeri/rn-cljs-tools 340 | 341 | 342 | ## CI 343 | https://pilloxa.gitlab.io/posts/ci-with-gitlab-and-docker/ 344 | @vikeri: 345 | You only have to install gitlab-ci-muti-runner not a full Gitlab instance. Actually I’m just doing nodejs tests at the moment. No integration tests with an iOS simulator yet. But yeah that should work in the future as well: https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/ 346 | 347 | ## Local storage 348 | 349 | https://realm.io/docs/react-native/latest/#getting-started 350 | 351 | > @tiensonqin: I used Realm.js. I have to store contacts and chat history in disk, so datascript along not works for me. 352 | 353 | 354 | ## Custom Native Components 355 | - A helper lib to create custom native ios components https://github.com/frostney/react-native-create-library 356 | 357 | > another warn is file and properties names, don't use names that start with RTC, use another namespace, don't use properties like margin, font-size and so one, this is cause conflicts and RN overwrite it. Also remember that if you write your file with Manager at the end, then in your code you don't need to write it, i.e file name - MyViewManager in cljs (require-native-component "MyView") (https://clojurians-log.clojureverse.org/cljsrn/2016-11-10.html#inst-2016-11-10T08:59:13.002600Z) 358 | 359 | 360 | ## Troubleshooting 361 | 362 | ### Memory issues 363 | Sometimes the node packager will run out of memory. 364 | 365 | One solution for iOS is to edit the following file: ios/YourProjectName.xcodeproj/project.pbxproj and change the following line (~600) 366 | 367 | `shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh";` 368 | to 369 | 370 | `shellScript = "export NODE_BINARY='node --max_old_space_size=4092'\n../node_modules/react-native/packager/react-native-xcode.sh";` 371 | 372 | ##### Update 8 Jul 2017 373 | It seems the memory problem comes from the SourceMapGenerator.toJSON function https://gist.github.com/gphilipp/10cebb3aba7afbafbc1cca7b265a7b7e#file-short_build_rn_memory-log-L108-L109. the log includes gc traces. 374 | I can’t find any settings which can effectively increase the maximum memory allocated to the node process, and I’ve tried them all, with various versions of node. The trick above sort of worked ok with RN 0.44 but falls short with RN 0.45. On my desktop, the `--max_old_space_size=4092` has no effect on the maximum memory of the node process. It will not go higher than 1.5 gb. My theory is that the packager uses more memory in RN 0.45 than in RN 0.44 or there might be a leak, so the node process will OOM with 1.5 gb only. 375 | The good news it that there might be some hope, as we have the exact same issue than someone who filed it against https://github.com/facebookincubator/create-react-app. Watch this issue: https://github.com/facebookincubator/create-react-app/issues/2555 376 | And since someone uploaded a project to reproduce it reliably, the issue will most likely be fixed in the near future: https://github.com/facebookincubator/create-react-app/issues/2555#issuecomment-313774797 377 | 378 | 379 | ##### Other tricks 380 | (thx @ronb : https://clojurians.slack.com/archives/C0E1SN0NM/p1498904841241132): 381 | 382 | - /Users/r/s/wbl/node_modules/react-native/packager/src/JSTransformer/index.js increase transform timeout interval 383 | - Add --max_old_space_size=16384 to node command line 384 | - If you get node gc-timeouts -> https://github.com/facebook/react-native/issues/5196#issuecomment-237549879 385 | - https://github.com/drapanjanas/re-natal/issues/75 is also helpful with that issue 386 | - this is usually caused by (js/require)‘ing large json files and a large compiled js file. It is much better to use https://github.com/futurepress/react-native-static-server or a similar package 387 | 388 | 389 | 390 | https://stackoverflow.com/a/38198512/50114 391 | 392 | ### Other issues 393 | -  `nth not supported on this type cljs.core/PersistentHashMap` 394 | Often caused by an invalid destructuring, usually a wrong call to rf/dispatch (missing params, or forgot to add []). 395 | 396 | - `Undefined is not an object (evaluating 'blah.core.init.call')` 397 | This is usually a compilation issue. Perform a `lein cljs build` to locate the problem. 398 | 399 | - HAXM for Android and Docker cannot run at the same time (http://stackoverflow.com/a/39155604/50114) 400 | 401 | - How to prevent node from running out of memory when bundling js for React Native 402 | http://stackoverflow.com/questions/38198511/how-to-prevent-node-from-running-out-of-memory-when-bundling-js-for-react-native/38198512 403 | 404 | - Never ever use a `println` or a `prn` inside your code, use the logging method described above with `react-native log-ios`, it will works correctly with the simulator or in dev mode on real device but will _crash abruptly_ when deployed in production (like with beta testflight). 405 | 406 | - Don't require images by concatenating strings, it will work in development but will crash in production (or beta testflight). See https://facebook.github.io/react-native/docs/images.html 407 | 408 | - If you need react native crash analysis tools: http://stackoverflow.com/questions/36947752/whats-a-good-setup-for-react-native-crash-reporting 409 | 410 | 411 | # Useful Code 412 | 413 | ```clojure 414 | (defn obj->vec [obj] 415 | "Put object properties into a vector" 416 | (vec (map (fn [k] {:key k :val (aget obj k)}) (.keys js/Object obj)))) 417 | ``` 418 | 419 | --------------------------------------------------------------------------------