Skip to main content
  1. Posts/

React Native performance: InteractionManager, Animated and the JSI revolution

·3 mins

React Native isn’t slow. You’re just using it like it’s the DOM.

Optimizing performance in React Native is not optional, it’s what makes the difference between an app that feels native and one that feels like a student project.

In this post, I’ll walk you through three powerful tools to make your app buttery smooth:

  • InteractionManager.runAfterInteractions
  • Animated (and why Reanimated is better)
  • The new JSI runtime and TurboModules

1. InteractionManager.runAfterInteractions #

When you render a new screen, especially one with animations or transitions, running expensive tasks right away is a terrible idea. Those tasks will compete with gestures and animations on the JS thread.

That’s where InteractionManager comes in. It schedules code to run only after all interactions are complete, including gestures and animations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { InteractionManager } from 'react-native'

useEffect(() => {
  const task = InteractionManager.runAfterInteractions(() => {
    // This code runs AFTER interactions are finished
    loadHeavyData()
  })

  return () => task.cancel()
}, [])

Perfect for background tasks, expensive calculations, or lazy-loading components.


2. Animated: native vs JS animations #

React Native ships with an Animated API for performing animations. But by default, many of them run on the JS thread, which can lead to stutters if your app is doing other things.

You can (and should) use useNativeDriver: true whenever possible:

1
2
3
4
5
Animated.timing(opacity, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true,
}).start()

This offloads the animation to the native side, smoother and more reliable. But it has limitations: only works with transform and opacity, not height, width, or backgroundColor.

Bonus: Reanimated 3 to the rescue #

If you want truly powerful, fluid, and interruptible animations, you need Reanimated 3. It lets you write “worklets” that run outside the JS thread using JSI.

1
2
3
4
5
const sharedValue = useSharedValue(0)

const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ translateX: sharedValue.value }],
}))

Animations are synchronous, ultra-performant, and interruptible.


3. JSI and TurboModules: no more bridge #

JSI (JavaScript Interface) is the new way for JavaScript to talk to native code in React Native without the legacy bridge.

Before:

  • JS <-> Native communication went through a slow async bridge.
  • Serialization, queues, and delays. Ugh.

Now:

  • JSI allows direct, fast, synchronous access to native code.
  • No serialization, no queues. Just raw performance.

What are TurboModules? #

TurboModules are native modules that expose their methods directly to JS via JSI. That means:

  • No more NativeModules.SomeModule.doSomething()
  • No need for promises or callbacks for simple operations

Example in C++:

1
2
3
4
5
6
// Native TurboModule method
jsi::Value MyModule::multiply(jsi::Runtime &rt, const jsi::Value *args, size_t count) {
  double a = args[0].asNumber();
  double b = args[1].asNumber();
  return jsi::Value(a * b);
}

And in JS:

1
const result = MyTurboModule.multiply(3, 5) // Returns 15 instantly

TurboModules are ideal for:

  • Cryptography (e.g., AES encryption, signing)
  • High-performance networking
  • Access to device sensors and hardware
  • Native modules for Reanimated or Skia

Conclusion #

If your React Native app feels sluggish, don’t blame the framework. Look at your threading and animation strategy.

  • Use InteractionManager.runAfterInteractions() for non-urgent work.
  • Delegate animations to the native thread with useNativeDriver: true.
  • For advanced animations, use Reanimated 3.
  • And if you’re building native modules: ditch the old bridge and go all-in on JSI + TurboModules.

React Native has grown up. It’s time our apps do too.


Are you already using JSI or TurboModules in your codebase? Got an epic performance win to share? Ping me on X (@mordonez_me)