mcpField
R
react-native

react-native

github.com/s-hiraokuDevelopment1972 installsUpdated 4w ago
Repository

About react-native

A collection of performance-focused patterns for React Native and Expo developers. It prioritizes list rendering, animations, navigation, UI, state management, and monorepo dependency placement.

Key features

  1. 01FlashList and memoized list items
  2. 02Stable callbacks and sized images
  3. 03GPU-friendly animation patterns
  4. 04Native navigation and UI components
  5. 05Selector-based state subscriptions
  6. 06Monorepo dependency guidance

Use cases

  1. 01Improving scrolling in large lists
  2. 02Reducing animation jank
  3. 03Choosing native navigation and menus
  4. 04Limiting unnecessary state-driven renders

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/s-hiraoku/synapse-a2a --skill react-native

After installing, type/react-nativein Claude Code to confirm it loaded.

Details

From the project README

React Native

Performance-first patterns for React Native and Expo, organized by impact.

Priority 1: List Performance (CRITICAL)

Lists are the #1 performance bottleneck in RN apps. Get these right first.

Use FlashList

Replace FlatList with @shopify/flash-list for large lists.

import { FlashList } from '@shopify/flash-list';

<FlashList
  data={items}
  renderItem={({ item }) => <ItemRow item={item} />}
  estimatedItemSize={80}
  keyExtractor={(item) => item.id}
/>
Memoize List Items

Every list item must be memoized.

const ItemRow = memo(function ItemRow({ item }: { item: Item }) {
  return (
    <View style={styles.row}>
      <Text>{item.title}</Text>
    </View>
  );
});
Stabilize Callbacks

Extract callbacks and avoid inline objects in list items.

// BAD: New function + new style object every render
<Pressable onPress={() => onSelect(item.id)} style={{ padding: 16 }}>

// GOOD: Stable references
const handlePress = useCallback(() => onSelect(item.id), [item.id, onSelect]);
<Pressable onPress={handlePress} style={styles.pressable}>
Optimize Images in Lists

Use expo-image with proper sizing and caching.

import { Image } from 'expo-image';

<Image
  source={{ uri: item.thumbnailUrl }}
  style={styles.thumbnail}
  contentFit="cover"
  placeholder={item.blurhash}
  transition={200}
  recyclingKey={item.id}
/>
Item Types for Heterogeneous Lists

Use getItemType to help FlashList reuse cells efficiently.

<FlashList
  data={mixedItems}
  renderItem={renderItem}
  getItemType={(item) => item.type} // 'header' | 'content' | 'ad'
  estimatedItemSize={100}
/>

Priority 2: Animation (HIGH)

GPU-Only Properties

Only animate transform and opacity. Everything else triggers layout.

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

const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ scale: withSpring(isPressed.value ? 0.95 : 1) }],
  opacity: withSpring(isVisible.value ? 1 : 0),
}));
Derived Values

Use useDerivedValue for computed animations to avoid redundant calculations.

const progress = useSharedValue(0);
const rotation = useDerivedValue(() => `${progress.value * 360}deg`);

const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ rotate: rotation.value }],
}));
Gesture Handling

Use react-native-gesture-handler for 60fps gesture tracking.

import { Gesture, GestureDetector } from 'react-native-gesture-handler';

const pan = Gesture.Pan()
  .onUpdate((e) => {
    translateX.value = e.translationX;
    translateY.value = e.translationY;
  })
  .onEnd(() => {
    translateX.value = withSpring(0);
    translateY.value = withSpring(0);
  });

// Use Gesture.Tap() instead of Pressable for animated press feedback
const tap = Gesture.Tap()
  .onBegin(() => { scale.value = withSpring(0.95); })
  .onFinalize(() => { scale.value = withSpring(1); });

Priority 3: Navigation (HIGH)

Native Navigators

Always prefer native stack and tabs over JS-based alternatives.

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

// BAD: JS-based stack (slower transitions, no native gestures)
import { createStackNavigator } from '@react-navigation/stack';

// GOOD: Native stack (native transitions + gestures)
const Stack = createNativeStackNavigator();
Screen Options

Configure headers and animations natively.

<Stack.Screen
  name="Detail"
  component={DetailScreen}
  options={{
    headerLargeTitle: true,    // iOS large title
    animation: 'slide_from_right',
  }}
/>

Priority 4: UI Patterns (HIGH)

Safe Areas

Handle safe areas correctly for all device shapes.

import { SafeAreaView } from 'react-native-safe-area-context';

// For scrollable content
<SafeAreaView edges={['top']} style={{ flex: 1 }}>
  <ScrollView contentInsetAdjustmentBehavior="automatic">
    {children}
  </ScrollView>
</SafeAreaView>
Native Modals

Use native modal presentation instead of JS overlays.

<Stack.Screen
  name="Settings"
  component={SettingsScreen}
  options={{ presentation: 'modal' }}
/>
Native Menus

Use context menus instead of custom dropdown components.

import * as ContextMenu from 'zeego/context-menu';

<ContextMenu.Root>
  <ContextMenu.Trigger>
    <Pressable><Text>Options</Text></Pressable>
  </ContextMenu.Trigger>
  <ContextMenu.Content>
    <ContextMenu.Item key="edit" onSelect={handleEdit}>
      <ContextMenu.ItemTitle>Edit</ContextMenu.ItemTitle>
    </ContextMenu.Item>
    <ContextMenu.Item key="delete" onSelect={handleDelete} destructive>
      <ContextMenu.ItemTitle>Delete</ContextMenu.ItemTitle>
    </ContextMenu.Item>
  </ContextMenu.Content>
</ContextMenu.Root>
Pressable Over TouchableOpacity
// BAD: Legacy touch component
<TouchableOpacity onPress={onPress}>{children}</TouchableOpacity>

// GOOD: Modern Pressable with feedback
<Pressable
  onPress={onPress}
  style={({ pressed }) => [styles.button, pressed && styles.pressed]}
  android_ripple={{ color: 'rgba(0,0,0,0.1)' }}
>
  {children}
</Pressable>

Priority 5: State Management (MEDIUM)

Minimize Re-renders

Subscribe only to the state you need.

// BAD: Re-renders on any store change
const store = useStore();
return <Text>{store.user.name}</Text>;

// GOOD: Selector extracts only needed value
const name = useStore((s) => s.user.name);
return <Text>{name}</Text>;
React Compiler Compatibility

When using React Compiler with Reanimated:

// Destructure shared value functions for compiler compatibility
const { value } = useSharedValue(0);

// Use worklet directive for Reanimated callbacks
const animatedStyle = useAnimatedStyle(() => {
  'worklet';
  return { opacity: value };
});

Priority 6: Monorepo (MEDIUM)

Native Dependencies

Keep native dependencies in the app package, not shared packages.

packages/
  ui/              # Pure React components (no native deps)
  shared/          # Business logic, types
apps/
  mobile/          # Native deps (expo-image, reanimated) here
Single Dependency Versions

Enforce one version per dependency across the monorepo.

// Root package.json
{
  "resolutions": {
    "react-native": "0.76.x",
    "react-native-reanimated": "3.x"
  }
}

Quick Reference

IssueFixPriority
Slow scrolling listsFlashList + memoized itemsCRITICAL
Inline objects in listsExtract to StyleSheetCRITICAL
Janky animationsOnly transform/opacityHIGH
JS-based navigationNative stack/tabsHIGH
Custom dropdown menusNative context menusHIGH
Full store subscriptionSelectorsMEDIUM
Native deps in shared pkgMove to app packageMEDIUM

Comments

Markdown supported