React Native best practices expert. PROACTIVELY use when working with React Native, mobile apps, Expo. Triggers: react-native, expo, mobile, iOS, Android, NativeWind
/plugin marketplace add nguyenthienthanh/aura-frog/plugin install aura-frog@aurafrogThis skill is limited to using the following tools:
Expert-level React Native patterns, mobile-specific optimizations, navigation, and platform handling.
This skill activates when:
react-native or expo in package.jsonsrc/
├── components/ # Shared components
│ ├── ui/ # Base UI components
│ └── common/ # Business components
├── screens/ # Screen components
├── navigation/ # Navigation config
├── hooks/ # Custom hooks
├── services/ # API services
├── stores/ # State management
├── utils/ # Utilities
├── constants/ # App constants
├── types/ # TypeScript types
└── assets/ # Images, fonts
// ✅ GOOD - Detect and use project's styling approach
// Check package.json for: nativewind, emotion, styled-components, or use StyleSheet
// NativeWind (Tailwind)
import { styled } from 'nativewind';
const StyledView = styled(View);
<StyledView className="flex-1 bg-white p-4" />
// StyleSheet (default)
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white', padding: 16 },
});
<View style={styles.container} />
// Emotion
import styled from '@emotion/native';
const Container = styled.View`flex: 1; background-color: white;`;
// ✅ GOOD - Memoized list item
const ListItem = React.memo(function ListItem({ item, onPress }: Props) {
const handlePress = useCallback(() => {
onPress(item.id);
}, [item.id, onPress]);
return (
<Pressable onPress={handlePress}>
<Text>{item.title}</Text>
</Pressable>
);
});
// ✅ GOOD - Pressable over TouchableOpacity
<Pressable
onPress={handlePress}
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed
]}
android_ripple={{ color: 'rgba(0,0,0,0.1)' }}
>
<Text>Press Me</Text>
</Pressable>
// ✅ GOOD - Optimized FlatList
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={keyExtractor}
// Performance props
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={5}
initialNumToRender={10}
getItemLayout={getItemLayout} // If fixed height
// Memoized functions
ListEmptyComponent={EmptyComponent}
ListHeaderComponent={HeaderComponent}
ListFooterComponent={FooterComponent}
// Prevent re-renders
extraData={selectedId}
/>
// ✅ GOOD - Memoized renderItem
const renderItem = useCallback(({ item }: { item: Item }) => (
<ListItem item={item} onPress={handlePress} />
), [handlePress]);
// ✅ GOOD - Stable keyExtractor
const keyExtractor = useCallback((item: Item) => item.id, []);
// ✅ GOOD - getItemLayout for fixed height items
const getItemLayout = useCallback((
_data: Item[] | null | undefined,
index: number
) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}), []);
// ✅ BETTER - Use FlashList for large lists
import { FlashList } from '@shopify/flash-list';
<FlashList
data={items}
renderItem={renderItem}
estimatedItemSize={ITEM_HEIGHT}
keyExtractor={keyExtractor}
/>
// ✅ GOOD - Define navigation types
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: { section?: string };
};
type ProfileScreenProps = NativeStackScreenProps<RootStackParamList, 'Profile'>;
// ✅ GOOD - Type-safe navigation hook
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
function MyComponent() {
const navigation = useNavigation<NavigationProp>();
const goToProfile = (userId: string) => {
navigation.navigate('Profile', { userId });
};
}
// ✅ GOOD - Configure deep linking
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: '',
Profile: 'user/:userId',
Settings: 'settings',
},
},
};
<NavigationContainer linking={linking}>
{/* ... */}
</NavigationContainer>
import { Platform, StyleSheet } from 'react-native';
// ✅ GOOD - Platform.select
const styles = StyleSheet.create({
shadow: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
android: {
elevation: 5,
},
default: {},
}),
});
// ✅ GOOD - Platform-specific files
// Button.ios.tsx
// Button.android.tsx
// Button.tsx (fallback)
import Button from './Button'; // Auto-selects platform
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
// ✅ GOOD - SafeAreaView for screens
function Screen() {
return (
<SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}>
<Content />
</SafeAreaView>
);
}
// ✅ GOOD - useSafeAreaInsets for custom handling
function CustomHeader() {
const insets = useSafeAreaInsets();
return (
<View style={{ paddingTop: insets.top }}>
<HeaderContent />
</View>
);
}
import FastImage from 'react-native-fast-image';
// ✅ GOOD - Use FastImage for network images
<FastImage
source={{
uri: imageUrl,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable,
}}
style={styles.image}
resizeMode={FastImage.resizeMode.cover}
/>
// ✅ GOOD - Preload images
FastImage.preload([
{ uri: 'https://example.com/image1.jpg' },
{ uri: 'https://example.com/image2.jpg' },
]);
// ✅ GOOD - Local images with require
<Image source={require('./assets/logo.png')} />
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
runOnJS,
} from 'react-native-reanimated';
// ✅ GOOD - Worklet-based animations
function AnimatedCard() {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const handlePressIn = () => {
scale.value = withSpring(0.95);
};
const handlePressOut = () => {
scale.value = withSpring(1);
};
return (
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
<Animated.View style={[styles.card, animatedStyle]}>
<Content />
</Animated.View>
</Pressable>
);
}
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
// ✅ GOOD - Gesture-based interactions
function SwipeableCard() {
const translateX = useSharedValue(0);
const gesture = Gesture.Pan()
.onUpdate((e) => {
translateX.value = e.translationX;
})
.onEnd(() => {
translateX.value = withSpring(0);
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<GestureDetector gesture={gesture}>
<Animated.View style={animatedStyle}>
<Card />
</Animated.View>
</GestureDetector>
);
}
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
// ✅ GOOD - AsyncStorage for non-sensitive data
async function saveSettings(settings: Settings) {
await AsyncStorage.setItem('settings', JSON.stringify(settings));
}
// ✅ GOOD - SecureStore for sensitive data
async function saveToken(token: string) {
await SecureStore.setItemAsync('authToken', token);
}
// ✅ GOOD - MMKV for performance-critical storage
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
storage.set('user.name', 'John');
const name = storage.getString('user.name');
import { render, fireEvent, waitFor } from '@testing-library/react-native';
// ✅ GOOD - Component testing
describe('LoginButton', () => {
it('calls onPress when pressed', () => {
const onPress = jest.fn();
const { getByText } = render(<LoginButton onPress={onPress} />);
fireEvent.press(getByText('Login'));
expect(onPress).toHaveBeenCalled();
});
});
// ✅ GOOD - Async testing
it('shows loading then content', async () => {
const { getByTestId, queryByTestId } = render(<DataScreen />);
expect(getByTestId('loading')).toBeTruthy();
await waitFor(() => {
expect(queryByTestId('loading')).toBeNull();
expect(getByTestId('content')).toBeTruthy();
});
});
checklist[12]{area,best_practice}:
Lists,Use FlashList or optimized FlatList
Images,Use FastImage for network images
Navigation,Type-safe with RootStackParamList
Styling,Detect project approach (NativeWind/StyleSheet)
Platform,Use Platform.select for differences
Safe area,Use SafeAreaView or useSafeAreaInsets
Animations,Use Reanimated for 60fps
Gestures,Use Gesture Handler
Storage,SecureStore for tokens AsyncStorage for prefs
Performance,Memoize components and callbacks
Testing,React Native Testing Library
Pressable,Use over TouchableOpacity
Version: 1.3.0
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.