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.

referencesnative-turbo-modules.md

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

Skill: Fast Native Modules

Build performant Turbo Modules using modern languages and background threading.

Quick Pattern

Incorrect (sync method blocks JS thread):

@objc func heavyWork() -> NSNumber {
    Thread.sleep(forTimeInterval: 2)  // Blocks JS for 2s!
    return 42
}

Correct (async on background thread):

@objc func heavyWork(
    resolve: @escaping RCTPromiseResolveBlock,
    reject: RCTPromiseRejectBlock
) {
    DispatchQueue.global().async {
        let result = self.compute()
        resolve(result)
    }
}

When to Use

  • Creating new native modules
  • Optimizing existing module performance
  • Heavy computation needs to run off JS thread
  • Cross-platform C++ code needed

Prerequisites

  • React Native Builder Bob for scaffolding
npx create-react-native-library@latest my-library

Step-by-Step Instructions

1. Scaffold with Builder Bob

npx create-react-native-library@latest awesome-library
# Follow prompts: choose Turbo Module, select languages

Creates ready-to-publish library with:

  • iOS (Obj-C/Swift) support
  • Android (Kotlin) support
  • TypeScript definitions
  • Codegen setup

For local modules:

npx create-react-native-library@latest awesome-library --local

2. Run on Background Thread (iOS)

@objc func heavyOperation(
    _ input: Double,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: RCTPromiseRejectBlock
) {
    DispatchQueue.global().async {
        // Heavy work on background thread
        let result = self.expensiveComputation(input)
        resolve(result)
    }
}

3. Run on Background Thread (Android)

class AwesomeLibraryModule(reactContext: ReactApplicationContext) :
    NativeAwesomeLibrarySpec(reactContext) {
    
    private val moduleScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
    
    override fun heavyOperation(input: Double, promise: Promise?) {
        moduleScope.launch {
            // Heavy work on coroutine
            val result = expensiveComputation(input)
            promise?.resolve(result)
        }
    }
    
    override fun invalidate() {
        super.invalidate()
        moduleScope.cancel()  // Prevent memory leaks!
    }
}

Use structured concurrency: keep a module-owned CoroutineScope, cancel it in invalidate(), avoid GlobalScope.launch, use SupervisorJob so one failed operation does not cancel unrelated in-flight work, and choose Dispatchers.Default for CPU work or Dispatchers.IO for disk/network/database work.

4. Use C++ for Cross-Platform Code

Create C++ Turbo Module for shared logic:

// MyCppModule.h
#pragma once

#include <ReactCommon/TurboModule.h>

namespace facebook::react {

class MyCppModule : public TurboModule {
public:
    MyCppModule(std::shared_ptr<CallInvoker> jsInvoker);
    
    double multiply(double a, double b);
};

} // namespace facebook::react

Follow the registration mechanism documented for the React Native version you target. Avoid copying old +load registration workarounds unless current RN docs or template output still require them.

Threading Summary

Method Type Default Thread Best Practice
Sync JS thread Keep fast (<16ms)
Async Native modules thread OK for moderate work
Heavy async Custom background Use DispatchQueue/Coroutines

Language Interop Costs

Interface Overhead Notes
Obj-C / Obj-C++ ↔ C++ Low Common iOS interop path
Swift ↔ C++ Version-dependent Verify supported Swift/Xcode/RN setup
Kotlin ↔ C++ (JNI) Higher Batch calls and avoid per-item crossings
C++ Turbo Module Low JSI direct access when correctly registered

Tip: C++ Turbo Modules skip JNI at runtime since JS holds direct C++ function references via JSI.

Code Example: Complete Async Operation

// TypeScript interface
export interface Spec extends TurboModule {
    multiply(a: number, b: number): number;  // Sync
    heavyOperation(input: number): Promise<number>;  // Async
}
// Android implementation
override fun heavyOperation(input: Double, promise: Promise?) {
    moduleScope.launch {
        try {
            val result = withContext(Dispatchers.Default) {
                // Simulate heavy work
                delay(1000)
                input * 2
            }
            promise?.resolve(result)
        } catch (e: Exception) {
            promise?.reject("ERROR", e.message)
        }
    }
}
// iOS implementation
@objc func heavyOperation(
    _ input: Double,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
) {
    DispatchQueue.global(qos: .userInitiated).async {
        // Simulate heavy work
        Thread.sleep(forTimeInterval: 1.0)
        let result = input * 2
        resolve(result)
    }
}

Common Pitfalls

  • Sync methods that block: Keep sync methods trivial and deterministic; make anything that can block, allocate heavily, perform I/O, or wait on locks async/background work
  • Forgetting to cancel coroutine scope: Causes memory leaks
  • Not handling errors in async: Always try/catch with reject
  • Accessing UI from background: Dispatch to main thread

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.