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-react-compiler.md

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

Skill: React Compiler

Set up React Compiler to automatically memoize components and eliminate unnecessary re-renders.

Quick Pattern

Before (manual memoization):

const MemoizedButton = memo(({ onPress }) => <Pressable onPress={onPress} />);
const handler = useCallback(() => doSomething(), []);

After (automatic with React Compiler):

// No memo/useCallback needed - compiler handles it
const Button = ({ onPress }) => <Pressable onPress={onPress} />;
const handler = () => doSomething();

When to Use

  • Want automatic performance optimization without manual memo/useMemo/useCallback
  • Codebase follows Rules of React
  • React Native 0.76+ or Expo SDK 52+
  • Ready to remove boilerplate memoization code

Prerequisites

  • Babel-based build system
  • Code follows Rules of React
  • Check current React Native, Expo, and React Compiler release notes before copying version-specific setup

Step-by-Step Instructions

Step 1: Check Compatibility

Before enabling the compiler, verify your project is compatible:

npx react-compiler-healthcheck@latest

This checks if your app follows the Rules of React and identifies potential issues.

Step 2: Install React Compiler

Expo

Use Expo's SDK-specific path:

# SDK 54 and later: Babel is auto-configured
npx expo install babel-plugin-react-compiler@beta

# SDK 53: install runtime too
npx expo install babel-plugin-react-compiler@beta react-compiler-runtime@beta

Then enable the experiment in app config:

{
  "expo": {
    "experiments": {
      "reactCompiler": true
    }
  }
}
React Native without Expo
npm install -D babel-plugin-react-compiler@latest

For React 17 or 18 targets, also install the compiler runtime:

npm install react-compiler-runtime@latest

Prefer the setup path documented for the app's exact Expo SDK, React Native, and React versions.

Step 3: Configure Babel (React Native without Expo)

For non-Expo React Native projects, configure Babel manually and keep the compiler first in the plugin pipeline:

// babel.config.js
const ReactCompilerConfig = {
  target: '19', // Use '18' for React Native < 0.78
};

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['module:@react-native/babel-preset'],
    plugins: [
      ['babel-plugin-react-compiler', ReactCompilerConfig],
      // ... other plugins
    ],
  };
};

Step 4: Set Up ESLint (Recommended)

Use the React Hooks/Compiler lint rules that match the app's React version. For Expo, SDK 55+ includes React Compiler lint rules through eslint-config-expo; SDK 54 and earlier need eslint-plugin-react-compiler. Fix rule violations before treating a component as compiler-optimized; skipped components are safe but do not get the intended memoization.

Step 5: Verify Optimizations

Verify with agent-device react-devtools before/after render measurements. For release-build verification, connect @callstack/inspector first so React DevTools can attach. Some visual DevTools versions show compiler memoization badges, but profiler evidence is the stable signal.

Incremental Adoption

You can incrementally adopt React Compiler using two strategies:

Strategy 1: Limit to Specific Directories

Configure the Babel plugin to only run on specific files, e.g. src/path/to/dir in the following examples:

Expo (create babel.config.js with npx expo customize babel.config.js):

// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: [
      [
        'babel-preset-expo',
        {
          'react-compiler': {
            sources: (filename) => {
              return filename.includes('src/path/to/dir');
            },
          },
        },
      ],
    ],
  };
};

React Native (without Expo):

// babel.config.js
const ReactCompilerConfig = {
  target: '19',
  sources: (filename) => {
    return filename.includes('src/path/to/dir');
  },
};

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['module:@react-native/babel-preset'],
    plugins: [['babel-plugin-react-compiler', ReactCompilerConfig]],
  };
};

After changing Babel config, restart Metro with a cleared cache.

Strategy 2: Opt Out Specific Components

Use the "use no memo" directive to skip optimization for specific components or files:

function ProblematicComponent() {
  'use no memo';

  return <Text>Will not be optimized</Text>;
}

This is useful for temporarily opting out components that cause issues. Fix the underlying problem and remove the directive once resolved.

Code Examples

React Compiler Playground

Test transformations at React Playground.

What Gets Optimized

// Components - auto-memoized
const Button = ({ onPress, label }) => (
  <Pressable onPress={onPress}>
    <Text>{label}</Text>
  </Pressable>
);

// Callbacks - auto-cached (no useCallback needed)
const handlePress = () => {
  console.log('pressed');
};

// Expensive computations - auto-cached (no useMemo needed)
const filtered = items.filter((item) => item.active);

What Breaks Compilation

// BAD: Mutating props
const BadComponent = ({ items }) => {
  items.push('new item'); // Mutation!
  return <List data={items} />;
};

// BAD: Mutating during render
const BadMutation = () => {
  const [items, setItems] = useState([]);
  items.push('new'); // Mutation during render!
  return <List data={items} />;
};

// BAD: Non-idempotent render
let counter = 0;
const BadRender = () => {
  counter++; // Side effect during render!
  return <Text>{counter}</Text>;
};

Should You Remove Manual Memoization?

Improvements are primarily automatic. You can remove instances of useCallback, useMemo, and React.memo in favor of automatic memoization once the compiler is working correctly in your project.

Note: Class components will not be optimized. Migrate to function components for full benefits.

Expo's implementation only runs on application code (not node_modules), and only when bundling for the client (disabled in server rendering).

Expected Performance Improvements

Expect the largest wins in components that currently rely on manual memoization discipline or have cascading re-renders. Already well-memoized code may show little change; keep the compiler only when profiling or maintenance cost justifies it.

Common Pitfalls

  • Not fixing ESLint errors first: When ESLint reports an error, the compiler skips that component—this is safe but means you miss optimization
  • Expecting it to fix bad patterns: Compiler optimizes good code, doesn't fix bad code
  • Forgetting shallow comparison: Like memo, compiler uses shallow comparison for objects/arrays
  • Not running healthcheck: Always run npx react-compiler-healthcheck@latest before enabling

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.