Beyond the Re-Render: Engineering Signal-Based Reactivity in High-Performance Expo Apps
Stop fighting the React lifecycle for high-frequency updates. Discover how I use signal-based reactivity to bypass the component tree and achieve 120FPS in complex Expo applications.

I’ve spent the better part of the last decade squeezing performance out of React Native. We’ve all been there: you’re building a complex dashboard or a real-time telemetry feed in Expo, and suddenly, your frame rate drops. You start sprinkling useMemo and useCallback like holy water, but the core issue remains—React's reconciliation model is a top-down bottleneck for high-frequency data.
Recently, I’ve moved away from the 'standard' state management patterns for my performance-critical views. The breakthrough? Signals.
The Problem: The V-Sync Struggle
In a typical React Native app, when a piece of state changes, the component (and potentially its children) re-renders. React produces a new element tree, diffs it, and sends commands over the bridge (or via JSI) to update the native views.
When you're dealing with a sensor stream or a high-speed stock ticker, doing this 60 times a second is expensive. Even with the New Architecture (Fabric), the overhead of the React lifecycle can eat into your frame budget. I found myself asking: Why am I re-rendering an entire component just to change a single text value?
The Breakthrough: Fine-Grained Reactivity
Signals change the mental model. Instead of a component 'consuming' state, the signal 'targets' a specific node in the UI. When the signal's value changes, it updates the view directly, bypassing the React reconciliation process entirely for that update.
In my recent Expo projects, I’ve been using @preact/signals-react. It’s lightweight and integrates surprisingly well with the React Native runtime.
Implementing Signals in Expo
Here is a pattern I’ve been using to handle a high-frequency data stream—think of a GPS coordinate display or a real-time heart rate monitor.
Why This Works for Us
When you run the profiler on this, you'll notice something beautiful: HighPerformanceScreen renders exactly once. The updates to the Text component happen via a direct subscription. By avoiding the diffing of the virtual DOM, I’ve seen my JS thread load drop by as much as 40% in data-heavy views.
Bridging Signals with Reanimated
One of my biggest challenges was making signals play nice with react-native-reanimated. Since Reanimated runs on the UI thread and signals (typically) live on the JS thread, you have to be intentional.
I’ve found that using signals as the "source of truth" for business logic and then syncing them to a SharedValue only when an animation is triggered provides the best of both worlds: fine-grained UI updates for text/data and hardware-accelerated animations for movement.
Lessons from the Trenches
If you're going to try this in your next Expo project, keep these three things in mind:
- Don't over-signal: Use them for high-frequency updates. For standard navigation or form state, standard
useStateorZustandis still perfectly fine and more predictable for most teams. - Debugging is different: Traditional React DevTools won't show you why a signal-based value changed, because the component didn't re-render. You'll need to rely more on logging within the signal's
.subscribemethod. - The Ecosystem is Catching Up: We're seeing a massive shift. With the advent of the React Compiler (React Forget), some of these manual optimizations might become obsolete, but for now, signals provide a level of surgical precision that the compiler can't yet match in a React Native environment.
Wrapping Up
Moving "Beyond the Re-render" isn't about abandoning React; it's about knowing when the tool is working against the hardware. In the high-stakes world of mobile performance, signals are the most potent weapon I've added to my arsenal this year. If you're building in Expo and feeling the lag, it might be time to stop re-rendering and start signaling.