connecting native wearable targets to React Native
React Native does not render on the Apple Watch or a Wear OS watch, watch apps are always standalone native targets. This article covers the communication strategy through WatchConnectivity and the Wearable Data Layer API, plus the realistic limits of code reuse.
Table of Contents
- 1. A realistic framing: React Native does not render on the watch
- 2. watchOS companion architecture: a standalone SwiftUI target
- 3. Wear OS companion architecture: Kotlin and Jetpack Compose for Wear
- 4. WatchConnectivity: communication channels between iPhone and Apple Watch
- 5. Wear OS Data Layer API: MessageClient, DataClient, and CapabilityClient
- 6. The bridge: a native module between React Native and watch APIs
- 7. Practical example: order status and quick reorder on the watch
- 8. Limits of code reuse
- 9. Testing and shipping the watch companion app
- 10. Summary
- 11. FAQ
1. A realistic framing: React Native does not render on the watch
A common misconception in React Native projects is that you can simply bring the existing app to the Apple Watch or a Wear OS watch by running the React Native renderer there as well. Technically, that is out of the question: neither watchOS nor Wear OS provide a runtime environment where a phone app's JavaScript engine and React Native renderer could execute. A watch app is always a standalone, fully native application with its own app target, its own lifecycle, and its own noticeably tighter memory and compute budget.
That does not mean React Native teams cannot build a watch connection, it means the watch app has to be treated architecturally as a separate project that exchanges data with the React Native phone app through defined communication channels. Realistic expectations about scope and effort matter here: a watch companion app typically shows a heavily reduced subset of the phone app's functionality, not a full parallel implementation.
2. watchOS companion architecture: a standalone SwiftUI target
A watchOS app is created as an additional target in the same Xcode project that contains the iOS app, but runs as its own process on its own runtime and is implemented entirely in SwiftUI. Since more recent watchOS versions, standalone watch apps without a mandatory iOS companion app are possible, but for a React Native companion app the classic model remains relevant, where the watch app ships as an extension to the existing iOS app under the same App Store listing.
Important for planning: the watchOS app does not share process memory with the iOS app, every data transfer runs explicitly through one of Apple's dedicated communication APIs, there is no direct function call from the watch app into the React Native JavaScript context of the phone app. This isolation is deliberate, so the watch app keeps working even when the iPhone is not currently in range or the phone app is not actively loaded in memory.
3. Wear OS companion architecture: Kotlin and Jetpack Compose for Wear
On the Android side, a Wear OS app runs as a separate APK inside the same app bundle, implemented in Kotlin with Jetpack Compose for Wear OS as the current UI framework, while older projects sometimes still use classic view-based wear layouts. Wear OS additionally distinguishes between a full-fledged app running standalone on the watch and lighter-weight formats like tiles for quick access or complications for watch face integration, each bringing its own, even more constrained APIs.
Similar to watchOS, a Wear OS app runs as its own process, regardless of whether the paired smartphone is currently reachable. Google also supports standalone wear apps that work on the watch even without a companion smartphone app, establishing connectivity directly over the watch's own WiFi or LTE, though that is usually not the relevant use case for a pure companion app to a React Native phone app.
4. WatchConnectivity: communication channels between iPhone and Apple Watch
The WatchConnectivity framework provides several transfer paths through a WCSession instance, differing in urgency and reliability. sendMessage delivers data in real time, but requires the counterpart to be currently reachable, otherwise the call fails. For data that needs to arrive reliably even when the counterpart is unreachable, transferUserInfo works better, queuing the transfer and automatically retrying once the connection is restored.
For the most common case in companion apps, keeping the current overall state in sync, say the most recently known order status, updateApplicationContext is usually the right choice: it overwrites earlier, not-yet-delivered context updates, so only the most current state is ever transmitted instead of working through a queue of stale intermediate states. These three APIs complement each other, and picking the right one depends on the specific data type and the freshness required.
// ios/WatchBridge.swift
import WatchConnectivity
import ExpoModulesCore
public class WatchBridge: Module {
public func definition() -> ModuleDefinition {
Name("WatchBridge")
Function("syncState") { (payload: [String: Any]) in
guard WCSession.default.activationState == .activated else { return }
try WCSession.default.updateApplicationContext(payload)
}
}
}
5. Wear OS Data Layer API: MessageClient, DataClient, and CapabilityClient
The Android counterpart is called the Wearable Data Layer API and offers, with MessageClient, an analog to sendMessage for one-off, near-real-time messages between phone and watch. For persistent, automatically synced data objects that stay consistent even across connection drops, DataClient is the right API, comparable to updateApplicationContext on watchOS, just with more granular control over individual data paths instead of a single overall context.
Additionally, CapabilityClient determines which connected devices currently offer which capabilities, for example whether a Wear OS app is even installed on a paired watch, before attempting to send data there. This reachability check matters in practice, because otherwise a React Native app might try to send data to a device that does not have the companion app installed at all, which then either fails silently or forces unnecessary error handling in the JavaScript layer.
// android/src/main/java/com/example/watch/WatchBridgeModule.kt
package com.example.watch
import com.google.android.gms.wearable.PutDataMapRequest
import com.google.android.gms.wearable.Wearable
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class WatchBridgeModule : Module() {
override fun definition() = ModuleDefinition {
Name("WatchBridge")
AsyncFunction("syncState") { orderStatus: String ->
val context = appContext.reactContext ?: return@AsyncFunction
val request = PutDataMapRequest.create("/order-status").apply {
dataMap.putString("status", orderStatus)
}
Wearable.getDataClient(context).putDataItem(request.asPutDataRequest())
}
}
}
6. The bridge: a native module between React Native and watch APIs
The practical connection runs through a native module inside the React Native app that implements the WCSession delegate methods on iOS and wraps MessageClient or DataClient on Android, each exposing a thin JavaScript interface for sending and receiving state changes. If, say, the cart contents or the order status change inside the React Native app, the business logic calls a function like WatchBridge.syncState(payload), which internally talks to the matching native API.
In the other direction, from the watch app back to the React Native app, the same native module receives incoming messages through the respective delegate callback method and forwards them to the JavaScript layer via an event emitter, where they are processed like a regular app event, such as a button press or a user action on the watch. This two-way bridge has to be implemented separately for each platform, since the underlying APIs are conceptually similar but technically completely different.
// app/services/watchBridge.ts
import { requireNativeModule } from "expo-modules-core";
const WatchBridge = requireNativeModule("WatchBridge");
export function syncOrderStatusToWatch(status: string): void {
WatchBridge.syncState({ status });
}
7. Practical example: order status and quick reorder on the watch
A realistic use case for a shop with a React Native app is a watch view showing the status of the most recently placed order, such as Shipped, Out for Delivery, or Delivered, together with a single button for a quick reorder of the last order. The actual order logic, including payment processing, deliberately stays on the phone, the watch merely triggers a request that gets forwarded through the bridge to the React Native app and processed there in full.
This deliberate feature reduction is typical of successful watch companion apps: instead of trying to replicate the full checkout flow on a small screen with heavily constrained input, the watch app limits itself to fast status checks and a single, clearly defined action, while all more complex interactions continue to run through the phone, where a keyboard, a bigger screen, and the full React Native surface are all available.
8. Limits of code reuse
The UI layer fundamentally cannot be shared between the React Native app and the watch app, neither components nor styling logic, because SwiftUI and Jetpack Compose for Wear share nothing conceptually or technically with React Native components. What is reusable, on the other hand, is pure data models and simple business rules, provided they are moved into a platform-specific Swift or Kotlin package that is independent of the React Native bridge and consumed both by the phone app's bridge and by the watch app.
In practice, this means for most teams: one shared Swift package with pure data structures and validation logic for the iOS side, a corresponding Kotlin module for the Android side, but four separate UI implementations overall, React Native for the phone, SwiftUI for the Apple Watch, Jetpack Compose for Wear OS, and possibly a shared API layer on the server that serves all four clients equally.
9. Testing and shipping the watch companion app
A watchOS app is built and tested through its own scheme in Xcode, either in the watch simulator paired with the iOS simulator or on a real Apple Watch paired with the test iPhone. For App Store release, the watch app is submitted as part of the same App Store listing as the iOS app, but has to provide its own screenshots and meet partly separate review criteria, particularly around meaningful standalone value of the watch features.
On the Android side, the Wear OS app is built as an additional APK inside the same Android App Bundle and distributed through the Play Console, tested first in the Wear OS emulator and later on real hardware through the same internal test track infrastructure as the phone app. Both platforms require the watch app to show at least a sensible empty state when there is no network connection to the phone app, instead of appearing completely blank or crashing.
| Aspect | watchOS | Wear OS |
|---|---|---|
| UI framework | SwiftUI | Jetpack Compose for Wear OS |
| Communication API | WatchConnectivity (WCSession) | Wearable Data Layer API |
| Real-time message | sendMessage | MessageClient |
| Persistent state | updateApplicationContext | DataClient |
| Standalone operation | Possible since more recent watchOS versions | Standalone apps officially supported |
Mironsoft
React Native app development and Magento integration
A mobile app for the Magento shop that actually runs smoothly?
We build React Native apps cleanly connected to the Magento REST or GraphQL API, from the first line of code to publishing on the App Store and Google Play.
App Concept
Plan the architecture and feature scope of a Magento-connected app together.
Magento API Integration
Cleanly connect product catalog, cart, and checkout to the shop API.
Store Publishing
Guide the App Store and Google Play release process without pitfalls.
10. Summary
watchOS and Wear OS Companion Apps: The Essentials at a Glance
Core takeaway
React Native does not render on the watch, watch apps are always standalone native targets.
Communication
WatchConnectivity on iOS, the Wearable Data Layer API on Wear OS.
Reusable
Pure data models and business rules through platform-specific shared packages.
Not reusable
The entire UI layer, SwiftUI and Jetpack Compose stay separate.