Cross-Platform Development with ClojureScript and React Native

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 Current Mental Model

The best way to think about this stack in 2026 is:

  • React Native or Expo owns the application shell
  • ClojureScript owns shared components, state, and logic
  • shadow-cljs owns compilation, watch mode, and hot reload on the ClojureScript side

This keeps the mobile project aligned with the current host ecosystem while still letting ClojureScript shape the application code.

Why This Stack Can Work Well

The combination is attractive when the app benefits from:

  • shared UI logic across platforms
  • immutable application state
  • fast iteration on state transitions and transforms
  • a React-style component model
  • teams that already understand front-end or functional UI architecture

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.

Avoid a Wrapper-First Story

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:

  • the React Native and Expo ecosystems now have stronger mainstream workflows
  • shadow-cljs has first-class React Native and Expo guidance
  • keeping the host mobile setup closer to what React Native expects reduces drift

So 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.”

A Simple Project Shape

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.

A Minimal Root Component

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:

  • React Native components from the host ecosystem
  • Reagent-style component composition
  • shadow-cljs / Expo-friendly startup and reload behavior

Once the shell is running, the real application work is the same as in other React-style systems:

  • routing and navigation
  • state ownership
  • async data fetching
  • error boundaries and loading states
  • interop with native modules when needed

That means the team should spend less energy memorizing one historical bootstrap command and more energy choosing a sustainable architecture for:

  • navigation
  • state transitions
  • platform bridges
  • offline and sync behavior

Keep Native Modules Behind a Smaller Bridge

Real mobile products often need some native capability anyway:

  • camera or media access
  • secure storage
  • biometric authentication
  • push notification plumbing
  • background or platform-specific integrations

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 Is Still Valuable, but the Story Changed

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.

Know When Native Should Still Win

If the product depends heavily on:

  • highly polished platform-specific UX
  • deep device integration
  • performance-sensitive animation or media work
  • extensive background behavior

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.

A Better Architecture View

    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.

Key Takeaways

  • The current practical path is React Native or Expo plus shadow-cljs, not a wrapper-first workflow.
  • ClojureScript is strongest when it owns shared state, component logic, and transformations.
  • Treat Re-Natal as historical context, not as the best default for new projects.
  • Live reload still matters, but the current development loop is built around shadow-cljs and the host mobile framework.
  • Focus on navigation, state, and platform boundaries once the shell is working.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026