All skills
callstackincubator avatar

/react-native-best-practices

@fa0bad0 official

Provides React Native performance optimization guidelines for FPS, TTI, bundle size, memory leaks, re-renders, and animations. Applies to tasks involving Hermes optimization, JS thread blocking, bridge overhead, FlashList, native modules, or debugging jank and frame drops.

Use this Skill: https://skilld.dev/gh/callstackincubator/agent-skills/react-native-best-practices

This session only. Nothing lands on disk.

referencesjs-animations-reanimated.md

≈2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Skill: High-Performance Animations

Use React Native Reanimated for smooth 60+ FPS animations.

Quick Pattern

Incorrect (JS thread - blocks on heavy work):

const opacity = useRef(new Animated.Value(0)).current;
Animated.timing(opacity, { toValue: 1 }).start();

Correct (UI thread - smooth even during JS work):

const opacity = useSharedValue(0);
const style = useAnimatedStyle(() => ({ opacity: opacity.value }));
opacity.value = withTiming(1);

When to Use

  • Animations drop frames or feel janky
  • UI freezes during animations
  • Need gesture-driven animations
  • Want animations to run during heavy JS work

Prerequisites

  • react-native-reanimated (v4+) and react-native-worklets installed
npm install react-native-reanimated react-native-worklets

Add to babel.config.js:

module.exports = {
  plugins: ['react-native-worklets/plugin'],  // Must be last
};

Note: Reanimated 4 requires React Native's New Architecture (Fabric + TurboModules). The Legacy Architecture is no longer supported. If upgrading from v3, see the migration notes at the end of this document.

Key Concepts

Main Thread vs JS Thread

  • Main/UI Thread: Handles native rendering (60+ FPS target)
  • JS Thread: Runs React and your JavaScript

Problem: Heavy JS work blocks animations running on JS thread.

Solution: Run animations on UI thread with Reanimated worklets.

Step-by-Step Instructions

1. Basic Animated Style (UI Thread)

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming
} from 'react-native-reanimated';

const FadeInView = () => {
  const opacity = useSharedValue(0);

  // This runs on UI thread - won't be blocked by JS
  const animatedStyle = useAnimatedStyle(() => {
    return { opacity: opacity.value };
  });

  useEffect(() => {
    opacity.value = withTiming(1, { duration: 500 });
  }, []);

  return <Animated.View style={[styles.box, animatedStyle]} />;
};

2. Run Code on UI Thread with scheduleOnUI

import { scheduleOnUI } from 'react-native-worklets';

const triggerAnimation = () => {
  scheduleOnUI(() => {
    'worklet';
    console.log('Running on UI thread');
    // Direct UI manipulations here
  });
};

3. Call JS from UI Thread with scheduleOnRN

import { scheduleOnRN } from 'react-native-worklets';
import { Pressable } from 'react-native';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

// Regular JS function
const trackAnalytics = (value) => {
  analytics.track('animation_complete', { value });
};

const AnimatedComponent = () => {
  const progress = useSharedValue(0);

  const handlePress = () => {
    progress.value = withTiming(1, { duration: 200 }, (finished) => {
      if (finished) {
        scheduleOnRN(trackAnalytics, 1);
      }
    });
  };

  const animatedStyle = useAnimatedStyle(() => {
    return { opacity: progress.value };
  });

  return (
    <Pressable onPress={handlePress}>
      <Animated.View style={animatedStyle} />
    </Pressable>
  );
};

Avoid calling scheduleOnRN from useAnimatedStyle; style worklets can evaluate more often than an analytics or state callback should run. Prefer animation completion callbacks or useAnimatedReaction.

4. Animation with Callback

import { scheduleOnRN } from 'react-native-worklets';

const AnimatedButton = () => {
  const scale = useSharedValue(1);

  const onComplete = () => {
    console.log('Animation finished!');
  };

  const handlePress = () => {
    scale.value = withTiming(
      1.2,
      { duration: 200 },
      (finished) => {
        if (finished) {
          scheduleOnRN(onComplete);
        }
      }
    );
  };

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

  return (
    <Pressable onPress={handlePress}>
      <Animated.View style={[styles.button, animatedStyle]}>
        <Text>Press Me</Text>
      </Animated.View>
    </Pressable>
  );
};

When to Use What

Thread Best For
UI Thread (worklets) Visual animations, transforms, gestures
JS Thread State updates, data processing, API calls
Hook/API Use Case
useAnimatedStyle Animated styles (auto UI thread)
scheduleOnUI Manual UI thread execution (from react-native-worklets)
scheduleOnRN Call JS functions from worklets (from react-native-worklets)
useTransition Alternative for React state-driven delays

For React Native Web targets, CSS transitions can be appropriate for simple state-driven style changes. In native apps, keep shared values/worklets for gesture-driven, scroll-driven, layout-sensitive, or orchestrated animations.

Common Pitfalls

  • Accessing React state in worklets: Use useSharedValue instead of useState for animated values
  • Not using Animated components: Must use Animated.View, Animated.Text, etc.
  • Heavy computation in useAnimatedStyle: Keep worklets fast
  • Forgetting 'worklet' directive: Required for inline worklet functions
// BAD: Regular function in useAnimatedStyle
const style = useAnimatedStyle(() => {
  heavyComputation();  // Blocks UI thread!
  return { opacity: 1 };
});

// GOOD: Keep worklets fast
const style = useAnimatedStyle(() => {
  return { opacity: opacity.value };  // Just read value
});

Migrating from Reanimated 3.x to 4.x

If you're upgrading from Reanimated 3.x, here are the key changes.

Can't upgrade to v4? If your project is blocked from migrating to New Architecture (e.g., incompatible native libraries, complex native code, or timeline constraints), keep using existing APIs and leverage native drivers where applicable. Avoid introducing legacy Reanimated 3.x or older to reduce future migration complexity.

Breaking Changes

Old API (v3) New API (v4) Package
runOnUI(() => {...})() scheduleOnUI(() => {...}) react-native-worklets
runOnJS(fn)(args) scheduleOnRN(fn, args) react-native-worklets
executeOnUIRuntimeSync runOnUISync react-native-worklets
runOnRuntime scheduleOnRuntime react-native-worklets
useScrollViewOffset useScrollOffset react-native-reanimated
useWorkletCallback Use useCallback with 'worklet'; directive React

Removed APIs

  • useAnimatedGestureHandler - Migrate to the Gesture API from react-native-gesture-handler v2+
  • addWhitelistedNativeProps / addWhitelistedUIProps - No longer needed
  • combineTransition - Use EntryExitTransition.entering(...).exiting(...) instead

withSpring Changes

// Before (v3)
withSpring(value, {
  restDisplacementThreshold: 0.01,
  restSpeedThreshold: 0.01,
  duration: 300,
});

// After (v4)
withSpring(value, {
  energyThreshold: 0.01,  // Replaces both threshold parameters
  duration: 200,          // Duration is now "perceptual" (~1.5x actual time)
});

Migration Checklist

  1. Enable New Architecture - Reanimated 4 only supports Fabric + TurboModules
  2. Install react-native-worklets - Required new dependency
  3. Update Babel plugin - Change 'react-native-reanimated/plugin' to 'react-native-worklets/plugin'
  4. Update imports - Move worklet functions to react-native-worklets
  5. Update API calls - New functions take callback + args directly (not curried)
  6. Rebuild native apps - Required after adding react-native-worklets

Related Skills

Source: SKILL.md on GitHub

1 alert17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill provides a comprehensive performance optimization guide for React Native applications. It contains standard developer workflows using command-line tools and patterns for advanced techniques like remote code splitting, which include detailed security hardening advice.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    11/31 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at fa0bad0. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 2 months ago
  • Performance
  • react-native
  • optimization
  • profiling
  • fps
  • bundle-size
  • memory
  • hermes
  • flatlist
  • animations

README badge

README badge for callstackincubator/agent-skills/react-native-best-practices

Provides React Native performance optimization guidelines covering FPS, TTI, bundle size, memory leaks, re-renders, and animations. Includes reference documentation for Hermes, FlashList, Turbo Modules, React Compiler, Reanimated, and profiling with Xcode and Android Studio.

Generated from the current SKILL.md.

Does this skill cover Expo apps?
Yes. The guidelines apply to both bare React Native and Expo projects. Expo SDK 52+ supports tree shaking and Re.Pack code splitting.
What performance metrics should I measure first?
Start with FPS (use React Native DevTools), Time To Interactive (TTI), bundle size, and memory growth. The skill emphasizes measuring baseline metrics before and after optimization to validate improvements.
Does this cover Hermes optimization?
Yes. The skill includes Hermes-specific guidance such as disabling JS bundle compression on Android to enable mmap, and checking Hermes API coverage before removing polyfills.
What if I'm using a list library other than FlatList or FlashList?
The skill prioritizes FlatList and FlashList as virtualized replacements for ScrollView. It includes library version checks (e.g., FlashList v2 API changes) but focuses on these two libraries.
Does this include native profiling guidance?
Yes. The skill covers profiling with Xcode Instruments (iOS) and Android Studio CPU Profiler, plus guidance on native threading, Turbo Modules, and C++ performance-critical code.

Generated from the current SKILL.md. These answers refresh after source changes.