React Native + Expo Development with expo-mcp
Overview
React Native/Expo development using expo-mcp for ALL package operations and production patterns from 13k-star libraries.
Core principle: Use expo-mcp "Add package" (NOT npm install). Search docs before implementing. Apply production patterns.
Announce at start: "I'm using the react-native-expo-development skill for React Native development."
When to Use
-
Installing ANY Expo package (Phase 4)
-
Implementing React Native components
-
Implementing screens
-
Searching Expo/RN documentation
-
Working with Zustand, React Navigation, Reanimated
-
Following performance best practices
expo-mcp Integration (MANDATORY)
Package Installation (NEVER npm install)
❌ WRONG:
npm install zustand npm install @react-native-async-storage/async-storage
✅ CORRECT:
"Add zustand and show me how to set up a store with AsyncStorage persistence" "Add @react-native-async-storage/async-storage" "Add react-native-syntax-highlighter for code display" "Add react-native-markdown-display for message rendering"
Why: expo-mcp provides:
-
Correct version for Expo SDK
-
Automatic usage documentation
-
Setup examples
-
Compatibility verification
Documentation Search
Before implementing features:
"Search Expo docs for navigation patterns" "Search Expo docs for AsyncStorage persistence" "Search Expo docs for deep linking setup"
expo-mcp searches with natural language, returns relevant sections
Testing Integration
Add testID to ALL interactive elements:
<TouchableOpacity testID="send-button" onPress={handleSend}> <TextInput testID="message-input" /> <Pressable testID="settings-button"> <FlatList testID="message-list">
Then test with expo-mcp:
"Find view with testID 'send-button' and verify it's enabled" "Tap button with testID 'send-button'" "Take screenshot and verify message sent"
Production Patterns
Message UI (from react-native-gifted-chat)
// MessageBubble component structure interface MessageBubbleProps { message: Message; isUser: boolean; }
export const MessageBubble = React.memo<MessageBubbleProps>(
({message, isUser}) => {
return (
<View testID={message-${message.id}} style={[
styles.bubble,
isUser ? styles.userBubble : styles.assistantBubble
]}>
<Text testID={message-text-${message.id}}>{message.content}</Text>
<Text style={styles.timestamp}>{formatTime(message.timestamp)}</Text>
</View>
);
},
(prev, next) => prev.message.id === next.message.id
);
FlatList Optimization (from Context7 docs)
<FlatList testID="message-list" data={messages} renderItem={renderMessage} keyExtractor={(item) => item.id} inverted={true} // For chat (newest at bottom) windowSize={10} maxToRenderPerBatch={10} removeClippedSubviews={true} initialNumToRender={10} />
const renderMessage = useCallback((info: ListRenderItemInfo<Message>) => ( <MessageBubble message={info.item} isUser={info.item.role === 'user'} /> ), []);
Zustand Store with AsyncStorage
import AsyncStorage from '@react-native-async-storage/async-storage'; import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware';
export const useAppStore = create<AppState>()( persist( (set) => ({ settings: {...}, updateSettings: (settings) => set({settings}), }), { name: 'claude-code-storage', storage: createJSONStorage(() => AsyncStorage), partialize: (state) => ({ settings: state.settings, // Don't persist messages (too large) }) } ) );
Optimistic UI (from stream-chat-react-native)
const sendMessage = (text: string) => {
const optimisticMessage = {
id: temp-${Date.now()},
text,
status: 'sending'
};
addMessage(optimisticMessage); // Show immediately
websocket.send({type: 'message', message: text}); // Update when confirmed };
Component Best Practices
Structure with testID
interface ComponentProps { data: Data; onPress: () => void; }
export const Component = React.memo<ComponentProps>(({data, onPress}) => { const handlePress = useCallback(() => { onPress(); }, [onPress]);
return ( <TouchableOpacity testID="component-container" onPress={handlePress}> <Text testID="component-text">{data.text}</Text> </TouchableOpacity> ); });
Styling
Use StyleSheet.create (NOT inline):
import {COLORS, SPACING, TYPOGRAPHY} from '../constants/theme';
const styles = StyleSheet.create({ container: { padding: SPACING.base, backgroundColor: COLORS.backgroundGradient.start, }, text: { fontSize: TYPOGRAPHY.fontSize.md, color: COLORS.textPrimary, }, });
Common Mistakes
Mistake Reality
"npm install works" WRONG. expo-mcp ensures compatibility.
"I know RN already" WRONG. Patterns prevent bugs, use them.
"Inline styles are faster" WRONG. StyleSheet enables optimization.
"Skip testIDs" WRONG. Required for expo-mcp testing.
Red Flags
-
"npm install is fine" → WRONG. Use expo-mcp.
-
"Don't need patterns" → WRONG. 13k stars prove value.
-
"testID is optional" → WRONG. Required for autonomous testing.
Integration
-
Use WITH: @claude-mobile-metro-manager (Metro required)
-
Use FOR: All Phase 4 implementation
-
Enables: expo-mcp autonomous testing in Gate 4A