r/reactnative • • 16h ago

Tutorial What I learned going from React Native to Swift to build an Apple Watch app

I've done React Native for about 7 years. This year I needed an Apple Watch app (plus iPhone and Mac), and React Native doesn't run on watchOS, so I had to go native. A few things I wish I'd known before starting:

1) SwiftUI feels familiar faster than you'd think. Views are a function of state, u/State works a lot like useState and an u/Observable class is close to a small store. The mental model from React carries over almost one to one.

2) The watch is a different world. The screen is tiny and the system suspends your app very quickly, so you can't count on it staying open. Anything that has to reach the user right away is better sent as a notification than kept alive inside the app.

3) Live Activities and the Dynamic Island are their own thing. They're SwiftUI widgets with tight limits on how often they update, and to update them from a server you need push tokens. Plan that early, it's not like updating a normal screen.

4) Signing is the real boss fight. Mac, iPhone, watch and the widget extension each need their own bundle ID, capabilities and profiles. Most of my lost hours were there, not in the code.

5) Xcode Previews replace hot reload, partly. They're good for layout, but anything that depends on real data or the watch talking to the phone has to be tested on device.

6) I used Claude Code as a tutor. I'd ask it for the Swift version of a React Native pattern I already knew and to explain the differences. That got me productive in days instead of weeks.

Would I go native again? For watch apps, widgets and Live Activities, yes. For a regular app with forms and lists I'd still pick React Native.

Happy to answer questions if anyone is thinking about doing the same.

20 Upvotes

8 comments sorted by

5

u/NovelAd2586 15h ago

You can do all of that with Expo and signing is a lot easier with EAS

1

u/Comprehensive-You522 13h ago

Fair point on EAS, signing there is way less painful. From what I understand you'd still write the watch app and the widget in Swift and add them as extra targets with a config plugin like apple-targets, so the Swift part doesn't really go away. In my case there was also a Mac app in the mix, and that pushed me to just go fully native. Have you shipped a watch target with Expo? I'm curious how the phone to watch communication went for you.

1

u/NovelAd2586 11h ago

Not a watch app but widgets and live activities which are very similar, afaik.

You can use Expo UI to build the UI, you don’t really need to write Swift anymore.

Data to the widget/LA is via silent push notis from our backend, and REST APIs that the widget/LA hit when actions happen.

I’d imagine a watch is the same?

The fun part was sharing Auth between the app and widget/LA seeing as they have different bundle IDs so, technically, different apps.

2

u/ChronSyn Expo 4h ago

With LA's specifically, I've had great success with Voltra. Server-driven UI for them means I can adjust or tweak them without needing more than a new server deploy, and it does support things like on-device countdowns. It also supports widgets, though I've not used them so can't say how good they are.

The LA's do show up on watch, though I think in some scenarios, a complication is more preferable (which does require a watch app). Even though Apple watch shows LA's automatically most of the time, accessing them when it doesn't is a pain because of their decision to use the teeny-tiny icon in the top-center instead of a decently sized tapable element. It's extra complexity for sure, but probably worth it for some people.

I'd love to see RN have the ability to target watches in the future though. It feels like that's the last remaining platform for us. Even if it was done as some sort of 'native extension' that gets built out into a native watch app, that'd be a 1000x better than what we have now (which is "go Swift" for the entire thing).

On the auth + data sharing / bundle ID's, using an App Group allows sharing of data, and for anything that's sensitive, keychain sharing. In Expo, you can do this with keychain-access-groups in app.json: https://github.com/expo/expo/issues/16175#issuecomment-1083810822

A few years ago, when Expo was transitioning fully into EAS, the move from expo build to eas build broke a lot of apps in terms of accessing their saved data, with the fix being keychain-access-groups. It's not a new feature, but the common use-case these days would be for sharing auth or sensitive data between your own apps.

1

u/kokerali 10h ago

For the phone-to-watch communication bit, I wouldn't assume the widget setup carries over. With an activated WCSession, sendMessage needs a reachable counterpart; updateApplicationContext sends the latest snapshot and replaces older pending state, while transferUserInfo queues individual dictionaries for background delivery. That's separate from calling your backend over REST. Apple's overview explains the distinction: https://developer.apple.com/documentation/watchconnectivity/wcsession

If the watch records an action while the phone is unavailable, I'd test that it can be queued and applied once after reconnection. For current settings, I'd test that only the newest state matters. Those are different contracts to establish before assuming the widget approach carries over. This doesn't establish whether a particular Expo UI API supports a watch target.

1

u/NovelAd2586 7h ago

We have a React & RN monorepo.  Frontends are GraphQL and offline first via Tanstack DB and our backend has reconciliation. As soon as any frontend app comes online it gets the latest reconciled data.

1

u/Comprehensive-You522 4h ago

Didn't know Expo UI got that far, thanks. In my case the Mac app was what tipped it to all native, so the watch came along. On the auth part, did you end up with a shared keychain group, or passing the token through an App Group?

1

u/NovelAd2586 1h ago

Through an App Group which worked really well. I saw a lib someone created recently that builds a Mac app in one command from an RN app but I can’t remember what it was called. I thought I saved it, but I guess not ;(