How to approach React Native from ClojureScript today, with Expo and shadow-cljs as the realistic modern path instead of older wrapper-first workflows.
Cross-platform development with ClojureScript and React Native still makes sense when the team wants one shared UI and state model across Android and iOS, but the workflow should reflect the current React Native ecosystem rather than older wrapper-based tutorials.
React Native’s current documentation increasingly frames the story around frameworks, and Expo is one of the clearest mainstream entry points. On the ClojureScript side, shadow-cljs is the practical build tool to pair with that world. That is a more current model than teaching Re-Natal and Figwheel as the assumed default.
The best way to think about this stack in 2026 is:
shadow-cljs owns compilation, watch mode, and hot reload on the ClojureScript sideThis keeps the mobile project aligned with the current host ecosystem while still letting ClojureScript shape the application code.
The combination is attractive when the app benefits from:
This is especially effective when the mobile app is data-rich and stateful rather than platform-effect-heavy.
That distinction matters. A cross-platform ClojureScript stack is strongest when the shared value is in state transitions, validation, view derivation, and business flow. It is weaker when the product is dominated by device APIs, sensor behavior, complex media pipelines, or platform-specific interaction polish.
Historically, Re-Natal gave ClojureScript developers an easier entry path into React Native. That mattered at the time. But the current safer guidance is to treat Re-Natal as historical context, not as the core path for new work.
The reason is practical:
shadow-cljs has first-class React Native and Expo guidanceSo the modern lesson should not be “install a global wrapper and let it own the project.” It should be “start from the host ecosystem, then add ClojureScript deliberately.”
On the ClojureScript side, a small shadow-cljs.edn can define a React Native target:
1{:deps true
2 :builds
3 {:app
4 {:target :react-native
5 :init-fn my-app.core/init
6 :output-dir "app"
7 :devtools {:reload-strategy :full}}}}
This reflects the actual shape documented in the shadow-cljs guide: the generated app/index.js becomes the JavaScript entry point that the React Native or Expo side can load.
For the ClojureScript side, the app can stay small and clear:
1(ns my-app.core
2 (:require [reagent.core :as r]
3 [shadow.expo :as expo]
4 ["react-native" :as rn]))
5
6(defn root []
7 [:> (.-View rn)
8 {:style {:flex 1
9 :justifyContent "center"
10 :alignItems "center"}}
11 [:> (.-Text rn) "Hello from ClojureScript"]])
12
13(defn start
14 {:dev/after-load true}
15 []
16 (expo/render-root (r/as-element [root])))
17
18(defn init []
19 (start))
This is intentionally modest. The important thing is the shape:
shadow-cljs / Expo-friendly startup and reload behaviorOnce the shell is running, the real application work is the same as in other React-style systems:
That means the team should spend less energy memorizing one historical bootstrap command and more energy choosing a sustainable architecture for:
Real mobile products often need some native capability anyway:
That does not invalidate the ClojureScript approach. It means the app needs a deliberate bridge shape. A good rule is to expose native capabilities as a small host-friendly API, then let ClojureScript orchestrate them at a higher level. That is usually easier to maintain than sprinkling platform interop throughout the UI code.
Hot reload remains one of the best reasons to use ClojureScript productively, but the tooling story is no longer “use Figwheel for everything.” In current React Native work, shadow-cljs watch plus the host mobile dev workflow is the more relevant development loop.
That is an important freshness fix for this guide. Readers should leave with the current idea of how live iteration works, not with a deprecated-era mental model.
If the product depends heavily on:
then a fully cross-platform ClojureScript shell may not be the best default. The right answer can still be native UI with a smaller shared logic layer.
flowchart LR
A["Expo or React Native App Shell"] --> B["shadow-cljs Output"]
B --> C["ClojureScript Components and State"]
C --> D["Shared Business Logic"]
A --> E["Native Device and Platform APIs"]
The key thing to notice is shared ownership. The host ecosystem still matters, and ClojureScript becomes one strong part of the system instead of pretending to be the whole mobile stack.
shadow-cljs, not a wrapper-first workflow.shadow-cljs and the host mobile framework.