Loading...
Loading...
Use when developing React Native components, installing packages via expo-mcp, implementing screens, or following RN best practices - integrates expo-mcp workflows (add_library, search_documentation) with production patterns from Gifted Chat and Stream
npx skill4agent add krzemienski/claude-mobile-expo react-native-expo-developmentnpm install zustand
npm install @react-native-async-storage/async-storage"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""Search Expo docs for navigation patterns"
"Search Expo docs for AsyncStorage persistence"
"Search Expo docs for deep linking setup"<TouchableOpacity testID="send-button" onPress={handleSend}>
<TextInput testID="message-input" />
<Pressable testID="settings-button">
<FlatList testID="message-list">"Find view with testID 'send-button' and verify it's enabled"
"Tap button with testID 'send-button'"
"Take screenshot and verify message sent"// 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
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'} />
), []);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)
})
}
)
);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
};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>
);
});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,
},
});| 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. |
@claude-mobile-metro-manager