RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-05-06Intermediate

Animation Jank in Rork Apps — How I Diagnosed Frame Drops and Fixed Them

Animations in your Rork app running smoothly in the preview but stuttering on a real device? This guide explains how to diagnose frame drops, why they happen, and how one line of code often fixes the problem completely.

Rork515React Native209Animation8Performance23useNativeDriverTroubleshooting38

You test your Rork app in the preview and everything looks great. Then you install the TestFlight build on your phone — and the animations are noticeably choppy.

This is one of those problems that tends to sneak up on indie developers right before launch. For a while, I assumed it was just "the nature of Rork-generated code" and moved on. But after actually digging into the root cause, I found that most animation jank in Rork apps comes down to a single missing line — and fixing it takes about ten seconds.

This article walks through how to diagnose the problem and, more importantly, how to fix it.

Why Animations Stutter: The JS Thread vs. UI Thread

React Native runs on two separate threads: the JS thread, which handles your JavaScript logic, and the UI thread, which handles screen rendering. Most animation code generated by Rork uses the Animated API, but by default, Animated performs its calculations on the JS thread.

The problem: when the JS thread gets busy — handling an API response, updating state, running a timer — animation calculations get delayed. To maintain 60fps, each frame needs to be computed within 16.67ms. If the JS thread is doing other work, frames get dropped and the animation stutters.

// ❌ Code Rork commonly generates — runs on JS thread, vulnerable to jank
const fadeAnim = useRef(new Animated.Value(0)).current;
 
Animated.timing(fadeAnim, {
  toValue: 1,
  duration: 300,
  // useNativeDriver not specified — defaults to false, stays on JS thread
}).start();

The UI thread, by contrast, runs independently of the JS thread. If you can offload animation calculations to the UI thread, your animations will stay smooth even when the JS thread is under load.

How to Diagnose Frame Drops

Before jumping to a fix, confirm that frame drops are actually happening. React Native includes a built-in Performance Monitor in development builds.

Open your app in the Expo development build on a real device, open the dev menu, and select "Show Performance Monitor." You'll see two live numbers: UI FPS and JS FPS.

What to look for:

  • If UI FPS is close to 60 but JS FPS is low — the JS thread is the culprit
  • If both values are low — there's a rendering bottleneck elsewhere (often in list views)

Also check the Rork Companion console for the warning: Animated: useNativeDriver was not specified. If this appears, the fix below will resolve it directly.

The Most Common Fix: Add useNativeDriver: true

This is the fix that handles the majority of cases. When useNativeDriver is set to true, animation calculations move from the JS thread to the UI thread.

// ✅ Fixed — animation runs on the UI thread, unaffected by JS thread load
const fadeAnim = useRef(new Animated.Value(0)).current;
 
Animated.timing(fadeAnim, {
  toValue: 1,
  duration: 300,
  useNativeDriver: true, // ← this single line makes the difference
}).start();
 
// Parallel animations: apply to each one individually
Animated.parallel([
  Animated.timing(fadeAnim, { toValue: 1, duration: 300, useNativeDriver: true }),
  Animated.spring(slideAnim, { toValue: 0, useNativeDriver: true }),
]).start();

Important limitation: useNativeDriver: true only works with properties that don't affect layout — specifically opacity and transform (translateX, translateY, scale, rotate). It does not work with width, height, backgroundColor, or other layout properties.

// ❌ This will throw an error — width is not supported by the native driver
Animated.timing(widthAnim, {
  toValue: 200,
  duration: 300,
  useNativeDriver: true,
}).start();
 
// ✅ Workaround: use transform.scaleX to simulate width changes
Animated.timing(scaleAnim, {
  toValue: 2, // visually doubles the width without affecting layout
  duration: 300,
  useNativeDriver: true,
}).start();

For Complex Animations: Switch to Reanimated

For scroll-linked animations, gesture-driven interactions, or anything more sophisticated, react-native-reanimated is the better long-term solution. It's already included in Expo's managed workflow.

// Scroll-linked fade-in using Reanimated
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
 
function AnimatedCard() {
  const opacity = useSharedValue(0);
 
  useEffect(() => {
    opacity.value = withTiming(1, { duration: 400 });
  }, []);
 
  const animatedStyle = useAnimatedStyle(() => ({
    opacity: opacity.value,
  }));
 
  return (
    <Animated.View style={[styles.card, animatedStyle]}>
      {/* card content */}
    </Animated.View>
  );
}

Reanimated's worklets run directly on the UI thread by default, completely bypassing the JS thread. For scroll-driven and gesture-driven animations, this makes a noticeable difference.

Fixing FlatList Scroll Jank

If your animations are fine but scrolling still stutters, the issue is usually in how FlatList items are rendered.

// ❌ Each scroll event triggers expensive re-renders
<FlatList
  data={items}
  renderItem={({ item }) => (
    <View style={[styles.item, { backgroundColor: item.color }]}>
      <Image source={{ uri: item.imageUrl }} />
      <Text>{item.title}</Text>
    </View>
  )}
/>
 
// ✅ Memoize the item component to prevent unnecessary re-renders
const ItemComponent = React.memo(({ item }) => (
  <View style={[styles.item, { backgroundColor: item.color }]}>
    <Image source={{ uri: item.imageUrl }} />
    <Text>{item.title}</Text>
  </View>
));
 
<FlatList
  data={items}
  renderItem={({ item }) => <ItemComponent item={item} />}
  windowSize={5}           // limit off-screen rendering
  initialNumToRender={8}
  maxToRenderPerBatch={5}
/>

Stutter Right After Navigation: Defer Heavy Work

If a screen transition itself stutters, the cause is often heavy data loading or initialization running during the transition animation. InteractionManager defers work until the animation completes:

import { InteractionManager } from 'react-native';
 
useEffect(() => {
  const task = InteractionManager.runAfterInteractions(() => {
    // Runs after the transition animation finishes
    loadHeavyData();
  });
  return () => task.cancel();
}, []);

In my own app, switching the list-to-detail transition to this pattern produced an immediately noticeable difference in perceived smoothness. Running heavy work in useEffect right at mount is a common shape in generated and hand-written code alike — for transition jank, suspect this first.

Prevent the Problem at the Prompt Level

The most efficient approach is to prevent the issue from appearing in the first place. When asking Rork to add animations, being explicit in your prompt makes a real difference:

"Add a fade-in animation. Use useNativeDriver: true and ensure the implementation maintains 60fps."

"For scroll-linked animations, use react-native-reanimated."

In my experience, explicit instructions like these consistently produce better output than relying on Rork's defaults — especially for more complex animation sequences where useNativeDriver tends to get omitted.

Pre-Release Checklist

After applying fixes, verify on a real device (not just the simulator — performance characteristics are very different):

  • Performance Monitor shows both UI FPS and JS FPS near 60
  • Animations remain smooth while scrolling
  • No useNativeDriver was not specified warnings in the console
  • Acceptable smoothness on older hardware (iPhone XS equivalent)

Animation smoothness is one of those things users notice immediately, even if they can't articulate what's wrong. Most of the time, adding useNativeDriver: true is all it takes — so if something looks off, that's the first place to check. I hope this saves you the detour it took me.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-05-22
Why FlatList's onEndReached Fires Multiple Times — and How to Stop It
After wiring up infinite scroll in a Rork-generated FlatList, you may notice the same paginated request hitting your API two or three times in a row. Here's why onEndReached fires more often than you expect and how to add a two-layer defense that survives production.
Dev Tools2026-03-30
Rork × React Native Reanimated & Gesture Handler — Building 60fps Animations and Advanced Gesture Interactions
Keeping Reanimated 3 and Gesture Handler at 60fps, told through the production pitfalls I hit running wallpaper and calming-tone apps: delayed runOnJS, useDerivedValue caching, FlashList pairing, and missing cancelAnimation — each with real measurements.
Dev Tools2026-07-10
Adding React Compiler to Expo Let Me Delete 41 Hand-Written memo Calls
I enabled React Compiler on Rork-generated React Native screens and measured the rerender counts with Profiler. Here is how I decided which memo and useCallback calls were safe to delete, how to find the components the compiler bailed out on, and how to catch regressions in CI.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →