Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/flexsurfer/clojurernproject
Simple React Native application with ClojureScript, re-frame and react navigation v5
https://github.com/flexsurfer/clojurernproject
clojure clojurescript re-frame react-native react-navigation react-navigation-v5 shadow-cljs shadow-cljs-app
Last synced: 14 days ago
JSON representation
Simple React Native application with ClojureScript, re-frame and react navigation v5
- Host: GitHub
- URL: https://github.com/flexsurfer/clojurernproject
- Owner: flexsurfer
- Created: 2020-02-24T17:11:35.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2024-12-10T11:05:12.000Z (24 days ago)
- Last Synced: 2024-12-11T10:37:22.870Z (23 days ago)
- Topics: clojure, clojurescript, re-frame, react-native, react-navigation, react-navigation-v5, shadow-cljs, shadow-cljs-app
- Language: Clojure
- Homepage:
- Size: 462 KB
- Stars: 50
- Watchers: 3
- Forks: 7
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
hackmd: https://hackmd.io/@byc70E6fQy67hPMN0WM9_A/rJilnJxE8
# Confidence and Joy: React Native Development with ClojureScript and re-frame
Clojure: https://clojure.org/guides/getting_started
Code editor: IntelliJ IDEA Community https://www.jetbrains.com/idea/download/
with Cursive plugin https://cursive-ide.com/shadow-cljs: http://shadow-cljs.org/
re-frame-steroid: https://github.com/flexsurfer/re-frame-steroid
rn-shadow-steroid: https://github.com/flexsurfer/rn-shadow-steroidPROJECT SOURCES: https://github.com/flexsurfer/ClojureRNProject
### 1. Create a new React Native Project or open existing one
`react-native init ClojureRNProject`
`cd ClojureRNProject`
Open project in IDE
![](https://i.imgur.com/GFLzmOi.png)
Edit `App.js`
```jsx=
import React from 'react';
import {
SafeAreaView,
View,
Text,
} from 'react-native';const App: () => React$Node = () => {
return (
<>
Hello CLojure!
>
);
};export default App;
```Run the app
Terminal 1: `yarn start`
Terminal 2: `yarn ios`![](https://i.imgur.com/uO6xvCK.png)
OK, now we have RN project and we want to run the same app but with clojure
### 2. Add shadow-cljs
`yarn add shadow-cljs`
If you already have it, make sure you are using the latest version
Create `shadow-cljs.edn`
```clojure
{:source-paths ["src"]:dependencies [[reagent "0.10.0"]
[re-frame "0.12.0"]
[re-frame-steroid "0.1.1"]
[rn-shadow-steroid "0.2.1"]
[re-frisk-remote "1.3.3"]]:builds {:dev
{:target :react-native
:init-fn clojurernproject.core/init
:output-dir "app"
:compiler-options {:closure-defines
{"re_frame.trace.trace_enabled_QMARK_" true}}
:devtools {:after-load steroid.rn.core/reload
:build-notify steroid.rn.core/build-notify
:preloads [re-frisk-remote.preload]}}}}
```Next, we need to initialize project as Clojure Deps, `deps.edn` will be used only for code inspection in IDE, if you know a better way pls file a PR
### 3. Create cljs project
create `deps.edn` file
```clojure
{:deps {org.clojure/clojure {:mvn/version "1.10.0"}
org.clojure/clojurescript {:mvn/version "1.10.339"}
reagent {:mvn/version "0.10.0"}
re-frame {:mvn/version "0.12.0"}
re-frame-steroid {:mvn/version "0.1.1"}
rn-shadow-steroid {:mvn/version "0.2.1"}}
:paths ["src"]}
```Right click on the file and `Add as Clojure Deps Project`
![](https://i.imgur.com/C110quU.png)
Optional turn off a spelling
Indellij IDEA -> Preferences
![](https://i.imgur.com/eqWzrqM.png)
create `src` folder and `clojurernproject` package with `core.cljs` file
![](https://i.imgur.com/gDEWfL3.png)
core.cljs
```clojure
(ns clojurernproject.core
(:require [steroid.rn.core :as rn]))(defn root-comp []
[rn/safe-area-view
[rn/view
[rn/text "Hello CLojure! from CLJS"]]])(defn init []
(rn/register-reload-comp "ClojureRNProject" root-comp))```
index.js
```javascript=
import "./app/index.js";
```Terminal 3: `shadow-cljs watch dev`
Reload the app
**Disable Fast Refresh**
Cmnd+D
![](https://i.imgur.com/7sOO4Ko.png)
Now try to change the code, you should see it reloaded by shadow-cljs in the app
now you have clojurescript RN app configured with hot reload
### 4. App state with re-frame
To update app state, we need to use events, let's create `events.cljs` and register our first events
events.cljs
```clojure
(ns clojurernproject.events
(:require [steroid.fx :as fx]))(fx/defn
init-app-db
{:events [:init-app-db]}
[_]
{:db {:counter 0}})(fx/defn
update-counter
{:events [:update-counter]}
[{:keys [db]}]
{:db (update db :counter inc)})
```Set cursor on `fx/defn` and press `option+return`
![](https://i.imgur.com/4ahMkVJ.png)
Move selection to `Resolve .. as...` and press `return` then select `defn`
To update a view when the state is changed, we need to use subscriptions, let's create `subs.cljs` and register subscriptions.
subs.cljs
```clojure
(ns clojurernproject.subs
(:require [steroid.subs :as subs]))(subs/reg-root-subs #{:counter})
```Now we can update our view
core.cljs
```clojure
(ns clojurernproject.core
(:require [steroid.rn.core :as rn]
[steroid.views :as views]
[re-frame.core :as re-frame]
clojurernproject.events
clojurernproject.subs))(views/defview root-comp []
(views/letsubs [counter [:counter]]
[rn/safe-area-view {:style {:flex 1}}
[rn/view {:style {:align-items :center :justify-content :center :flex 1}}
[rn/text (str "Counter: " counter)]
[rn/touchable-opacity {:on-press #(re-frame/dispatch [:update-counter])}
[rn/view {:style {:background-color :gray :padding 5}}
[rn/text "Update counter"]]]]]))(defn init []
(re-frame/dispatch [:init-app-db])
(rn/register-reload-comp "ClojureRNProject" root-comp))
```Resolve `defview` as `defn` and `letsubs` as `let` same way how we did it for `fx/defn`
you can press "Update counter" button, and then change your code, and you can see app updated, but app state remained the same
![](https://i.imgur.com/T5wfvnX.png)
now you have clojurescript RN app configured with hot reload and re-frame state
There are three major rules when working with re-frame
1) views are pure and dumb, just render UI with data from subscriptions and dispatch eventsBad:
```clojure
(views/defview comp []
(views/letsubs [counter [:counter]
delta [:delta]]
[rn/text (str "Counter: " (+ counter delta))]
[rn/touchable-opacity
{:on-press #(re-frame/dispatch
[:update-counter (if (> delta 12)
counter
delta)])}]))
```Good:
```clojure
(views/defview comp []
(views/letsubs [counter-with-delta [:counter-with-delta]]
[rn/text (str "Counter: " counter-with-delta)]
[rn/touchable-opacity
{:on-press #(re-frame/dispatch [:update-counter])}]))
```
we have a separate subscription and event will get all data from the state2. Only root keys should be subscribed on app-db
Bad:
```clojure
(re-frame/reg-sub :counter (fn [db] (get db :counter)))(re-frame/reg-sub :delta (fn [db] (get db :delta)))
(re-frame/reg-sub :counter-with-delta (fn [db] (+ (get db :counter) (get db :delta)))
```Good:
```clojure
(subs/reg-root-subs #{:counter :delta})(re-frame/reg-sub
:counter-with-delta
:<- [:counter]
:<- [:delta]
(fn [[counter delta]]
(+ counter delta)))
```3. Events must be pure and do all computations
Bad:
```clojure
(fx/defn
update-counter
{:events [:update-counter]}
[{:keys [db]}]
(do-something)
{:db (update db :counter inc)})
```Good:
```clojure
(re-frame/reg-fx
:do-something
(fn []
(do-something)))(fx/defn
update-counter
{:events [:update-counter]}
[{:keys [db]}]
{:db (update db :counter inc)
:do-something nil})
```### 6. Devtools
let's run re-frisk debugging tool and see what's exactly happening in the app
Terminal 4: `shadow-cljs run re-frisk-remote.core/start`
and open `http://localhost:4567`
![](https://i.imgur.com/6ty7nbr.png)
You can see all that is happening with the app: events, app-db (state) and subscriptions
### 6. Tests
Add test folder and configure test build in the project
```clojure
{:source-paths ["src" "test"]:dependencies [[...]]
:builds {:dev
{...}:test
{:target :node-test
:output-to "out/node-tests.js"
:autorun true}}}
```Let's add some tests
events/counter_test.cljs
```clojure
(ns events.counter-test
(:require [cljs.test :refer (deftest is)]
[clojurernproject.events :as events]))(deftest events-counter-test
(is (= (events/update-counter {:db {:counter 0}})
{:db {:counter 1}})))
```And run tests
Terminal 3: `shadow-cljs compile test`
![](https://i.imgur.com/28gspBL.png)
### 7. Navigation
React Navigation 5
Terminal 2: `yarn add @react-navigation/native @react-navigation/stack @react-navigation/bottom-tab react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view`
Terminal 2: `cd ios; pod install; cd ..`
Terminal 2: `yarn ios`
core.cljs
```clojure
(ns clojurernproject.core
(:require [steroid.rn.core :as rn]
[re-frame.core :as re-frame]
[steroid.rn.navigation.core :as rnn]
[steroid.rn.navigation.stack :as stack]
[steroid.rn.navigation.bottom-tabs :as bottom-tabs]
[clojurernproject.views :as screens]
[steroid.rn.navigation.safe-area :as safe-area]
steroid.rn.navigation.events
clojurernproject.events
clojurernproject.subs))(defn main-screens []
[bottom-tabs/bottom-tab
[{:name :home
:component screens/home-screen}
{:name :basic
:component screens/basic-screen}
{:name :ui
:component screens/ui-screen}
{:name :list
:component screens/list-screen}
{:name :storage
:component screens/storage-screen}]])(defn root-stack []
[safe-area/safe-area-provider
[(rnn/create-navigation-container-reload
{:on-ready #(re-frame/dispatch [:init-app-db])}
[stack/stack {:mode :modal :header-mode :none}
[{:name :main
:component main-screens}
{:name :modal
:component screens/modal-screen}]])]])(defn init []
(rn/register-comp "ClojureRNProject" root-stack))
```For hot reload we need to register components differently, we register `root-stack` as regular not reloadable component `rn/register-comp` but we use `rnn/create-navigation-container-reload` for navigation container
After we've required `steroid.rn.navigation.events` ns we can dispatch `:navigate-to` and `:navigate-back` events for navigation between screens
Try to open modal screen and change the code you will see that navigation state isn't changed, the modal screen will be still opened
![IMG](https://github.com/flexsurfer/rn-shadow-steroid/raw/master/screencast.gif)
КОНЕЦ