Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build React Native 0.76+ apps with Expo SDK 52. Covers mandatory New Architecture (0.82+), React 19 changes (propTypes/forwardRef removal), new CSS (display: contents, mixBlendMode, outline), Swift iOS template, and DevTools migration. Use when: building Expo apps, migrating to New Architecture, or troubleshooting "Fabric component not found", "propTypes not a function", "TurboModule not registered", or Swift AppDelegate errors.
.claude/skills/microck-react-native-expo/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 178% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 216% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 266% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 234% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 288% | 0% |
Status: Production Ready Last Updated: 2025-11-22 Dependencies: Node.js 18+, Expo CLI Latest Versions: react-native@0.82, expo@~52.0.0, react@19.1
bash# Create new Expo app with React Native 0.76+ npx create-expo-app@latest my-app cd my-app # Install latest dependencies npx expo install react-native@latest expo@latest
Why this matters:
bash# Check if New Architecture is enabled (should be true by default) npx expo config --type introspect | grep newArchEnabled
CRITICAL:
bash# Start Expo dev server npx expo start # Press 'i' for iOS simulator # Press 'a' for Android emulator # Press 'j' to open React Native DevTools (NOT Chrome debugger!)
CRITICAL:
console.log() - use DevTools ConsoleWhat Changed:
Impact:
bash# This will FAIL in 0.82+: # gradle.properties (Android) newArchEnabled=false # ❌ Ignored, build fails # iOS RCT_NEW_ARCH_ENABLED=0 # ❌ Ignored, build fails
Migration Path:
What Changed: React 19 removed propTypes completely. No runtime validation, no warnings - silently ignored.
Before (Old Code):
typescriptimport PropTypes from 'prop-types'; function MyComponent({ name, age }) { return <Text>{name} is {age}</Text>; } MyComponent.propTypes = { // ❌ Silently ignored in React 19 name: PropTypes.string.isRequired, age: PropTypes.number };
After (Use TypeScript):
typescripttype MyComponentProps = { name: string; age?: number; }; function MyComponent({ name, age }: MyComponentProps) { return <Text>{name} is {age}</Text>; }
Migration:
bash# Use React 19 codemod to remove propTypes npx @codemod/react-19 upgrade
What Changed: forwardRef no longer needed - pass ref as a regular prop.
Before (Old Code):
typescriptimport { forwardRef } from 'react'; const MyInput = forwardRef((props, ref) => { // ❌ Deprecated return <TextInput ref={ref} {...props} />; });
After (React 19):
typescriptfunction MyInput({ ref, ...props }) { // ✅ ref is a regular prop return <TextInput ref={ref} {...props} />; }
What Changed: New projects use Swift AppDelegate.swift instead of Objective-C AppDelegate.mm.
Old Structure:
ios/MyApp/
├── main.m # ❌ Removed
├── AppDelegate.h # ❌ Removed
└── AppDelegate.mm # ❌ RemovedNew Structure:
swift// ios/MyApp/AppDelegate.swift ✅ import UIKit import React @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, ...) -> Bool { // App initialization return true } }
Migration (0.76 → 0.77): When upgrading existing projects, you MUST add this line:
swift// Add to AppDelegate.swift during migration import React import ReactCoreModules RCTAppDependencyProvider.sharedInstance() // ⚠️ CRITICAL: Must add this!
Source: React Native 0.77 Release Notes
What Changed: Metro terminal no longer streams console.log() output.
Before (0.76):
bash# console.log() appeared in Metro terminal $ npx expo start > LOG Hello from app! # ✅ Appeared here
After (0.77+):
bash# console.log() does NOT appear in Metro terminal $ npx expo start # (no logs shown) # ❌ Removed # Workaround (temporary, will be removed): $ npx expo start --client-logs # Shows logs, deprecated
Solution: Use React Native DevTools Console instead (press 'j' in CLI).
Source: React Native 0.77 Release Notes
What Changed: Old Chrome debugger (chrome://inspect) removed. Use React Native DevTools instead.
Old Method (Removed):
bash# ❌ This no longer works: # Open Dev Menu → "Debug" → Chrome DevTools opens
New Method (0.76+):
bash# Press 'j' in CLI or Dev Menu → "Open React Native DevTools" # ✅ Uses Chrome DevTools Protocol (CDP) # ✅ Reliable breakpoints, watch values, stack inspection # ✅ JS Console (replaces Metro logs)
Limitations:
Source: React Native 0.79 Release Notes
What Changed: JavaScriptCore (JSC) moved out of React Native core, Hermes is default.
Before (0.78):
After (0.79+):
json// If you still need JSC (rare): { "dependencies": { "@react-native-community/javascriptcore": "^1.0.0" } }
Expo Go:
Note: JSC will eventually be removed entirely from React Native.
What Changed: Importing from internal paths will break.
Before (Old Code):
typescript// ❌ Deep imports deprecated import Button from 'react-native/Libraries/Components/Button'; import Platform from 'react-native/Libraries/Utilities/Platform';
After:
typescript// ✅ Import only from 'react-native' import { Button, Platform } from 'react-native';
Source: React Native 0.80 Release Notes
React Native now supports many CSS properties previously only available on web:
display: contentsMakes an element "invisible" but keeps its children in the layout:
typescript<View style={{ display: 'contents' }}> {/* This View disappears, but Text still renders */} <Text>I'm still here!</Text> </View>
Use case: Wrapper components that shouldn't affect layout.
boxSizingControl how width/height are calculated:
typescript// Default: padding/border inside box <View style={{ boxSizing: 'border-box', // Default width: 100, padding: 10, borderWidth: 2 // Total width: 100 (padding/border inside) }} /> // Content-box: padding/border outside <View style={{ boxSizing: 'content-box', width: 100, padding: 10, borderWidth: 2 // Total width: 124 (100 + 20 padding + 4 border) }} />
mixBlendMode + isolationBlend layers like Photoshop:
typescript<View style={{ backgroundColor: 'red' }}> <View style={{ mixBlendMode: 'multiply', // 16 modes available backgroundColor: 'blue' // Result: purple (red × blue) }} /> </View> // Prevent unwanted blending: <View style={{ isolation: 'isolate' }}> {/* Blending contained within this view */} </View>
Available modes: multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity
outline PropertiesVisual outline that doesn't affect layout (unlike border):
typescript<View style={{ outlineWidth: 2, outlineStyle: 'solid', // solid | dashed | dotted outlineColor: 'blue', outlineOffset: 4, // Space between element and outline outlineSpread: 2 // Expand outline beyond offset }} />
Key difference: Outline doesn't change element size or trigger layout recalculations.
Source: React Native 0.77 Release Notes
Use native Android vector drawables (XML) as Image sources:
typescript// Load XML drawable at build time import MyIcon from './assets/my_icon.xml'; <Image source={MyIcon} style={{ width: 40, height: 40 }} /> // Or with require: <Image source={require('./assets/my_icon.xml')} style={{ width: 40, height: 40 }} />
Benefits:
Constraints:
Source: React Native 0.78 Release Notes
useActionState (replaces form patterns)typescriptimport { useActionState } from 'react'; function MyForm() { const [state, submitAction, isPending] = useActionState( async (prevState, formData) => { // Async form submission const result = await api.submit(formData); return result; }, { message: '' } // Initial state ); return ( <form action={submitAction}> <TextInput name="email" /> <Button disabled={isPending}> {isPending ? 'Submitting...' : 'Submit'} </Button> {state.message && <Text>{state.message}</Text>} </form> ); }
useOptimistic (optimistic UI updates)typescriptimport { useOptimistic } from 'react'; function LikeButton({ postId, initialLikes }) { const [optimisticLikes, addOptimisticLike] = useOptimistic( initialLikes, (currentLikes, amount) => currentLikes + amount ); async function handleLike() { addOptimisticLike(1); // Update UI immediately await api.like(postId); // Then update server } return ( <Button onPress={handleLike}> ❤️ {optimisticLikes} </Button> ); }
use (read promises/contexts during render)typescriptimport { use } from 'react'; function UserProfile({ userPromise }) { // Read promise directly during render (suspends if pending) const user = use(userPromise); return <Text>{user.name}</Text>; }
Source: React 19 Upgrade Guide
Access:
j in CLIFeatures:
Source: React Native DevTools Announcement
This skill prevents 12 documented issues:
Error: No error - propTypes just doesn't work Source: React 19 Upgrade Guide Why It Happens: React 19 removed runtime propTypes validation Prevention: Use TypeScript instead, run npx @codemod/react-19 upgrade to remove
Error: Warning: forwardRef is deprecated Source: React 19 Upgrade Guide Why It Happens: React 19 allows ref as a regular prop Prevention: Remove forwardRef wrapper, pass ref as prop directly
Error: Build fails with newArchEnabled=false Source: React Native 0.82 Release Notes Why It Happens: Legacy architecture completely removed from codebase Prevention: Migrate to New Architecture before upgrading to 0.82+
Error: Fabric component descriptor provider not found for component Source: New Architecture Migration Guide Why It Happens: Component not compatible with New Architecture (Fabric) Prevention: Update library to New Architecture version, or use interop layer (0.76-0.81)
Error: TurboModule '[ModuleName]' not found Source: New Architecture Migration Guide Why It Happens: Native module needs New Architecture support (TurboModules) Prevention: Update library to support TurboModules, or use interop layer (0.76-0.81)
Error: RCTAppDependencyProvider not found Source: React Native 0.77 Release Notes Why It Happens: When migrating from Objective-C to Swift template Prevention: Add RCTAppDependencyProvider.sharedInstance() to AppDelegate.swift
Error: console.log() doesn't show in terminal Source: React Native 0.77 Release Notes Why It Happens: Metro log forwarding removed in 0.77 Prevention: Use React Native DevTools Console (press 'j'), or --client-logs flag (temporary)
Error: Chrome DevTools doesn't connect Source: React Native 0.79 Release Notes Why It Happens: Old Chrome debugger removed in 0.79 Prevention: Use React Native DevTools instead (press 'j')
Error: Module not found: react-native/Libraries/... Source: React Native 0.80 Release Notes Why It Happens: Internal paths deprecated, strict API enforced Prevention: Import only from 'react-native', not deep paths
Error: App crashes on Redux store creation Source: Redux Toolkit Migration Guide Why It Happens: Old redux + redux-thunk incompatible with New Architecture Prevention: Use Redux Toolkit (@reduxjs/toolkit) instead
Error: Translations not updating, or app crashes Source: Community reports (GitHub issues) Why It Happens: i18n-js not fully compatible with New Architecture Prevention: Use react-i18next instead
Error: Android crashes looking for bundle named null Source: CodePush GitHub Issues Why It Happens: Known incompatibility with New Architecture Prevention: Avoid CodePush with New Architecture, or wait for official support
Why: Can't skip directly to 0.82 if using legacy architecture - you'll lose the interop layer.
bash# Check current version npx react-native --version # Upgrade to 0.81 first (last version with interop layer) npm install react-native@0.81 npx expo install --fix
bash# Android (gradle.properties) newArchEnabled=true # iOS RCT_NEW_ARCH_ENABLED=1 bundle exec pod install # Rebuild npm run ios npm run android
Common incompatibilities:
bash# Replace Redux with Redux Toolkit npm uninstall redux redux-thunk npm install @reduxjs/toolkit react-redux # Replace i18n-js with react-i18next npm uninstall i18n-js npm install react-i18next i18next # Update React Navigation (if old version) npm install @react-navigation/native@latest
bash# Run on both platforms npm run ios npm run android # Test all features: # - Navigation # - State management (Redux) # - API calls # - Deep linking # - Push notifications
bash# Run React 19 codemod npx @codemod/react-19 upgrade # Manually verify: # - Remove all propTypes declarations # - Remove forwardRef wrappers # - Update to new hooks (useActionState, useOptimistic)
bash# Only after testing with New Architecture enabled! npm install react-native@0.82 npx expo install --fix # Rebuild npm run ios npm run android
New projects (0.77+) use Swift by default. For existing projects:
bash# Follow upgrade helper # https://react-native-community.github.io/upgrade-helper/ # Select: 0.76 → 0.77 # CRITICAL: Add this line to AppDelegate.swift RCTAppDependencyProvider.sharedInstance()
typescriptimport { useActionState } from 'react'; function LoginForm() { const [state, loginAction, isPending] = useActionState( async (prevState, formData) => { try { const user = await api.login(formData); return { success: true, user }; } catch (error) { return { success: false, error: error.message }; } }, { success: false } ); return ( <View> <form action={loginAction}> <TextInput name="email" placeholder="Email" /> <TextInput name="password" secureTextEntry /> <Button disabled={isPending}> {isPending ? 'Logging in...' : 'Login'} </Button> </form> {!state.success && state.error && ( <Text style={{ color: 'red' }}>{state.error}</Text> )} </View> ); }
When to use: Form submission with loading/error states
typescript// Define prop types with TypeScript type ButtonProps = { title: string; onPress: () => void; disabled?: boolean; variant?: 'primary' | 'secondary'; }; function Button({ title, onPress, disabled = false, variant = 'primary' }: ButtonProps) { return ( <Pressable onPress={onPress} disabled={disabled} style={[styles.button, styles[variant]]} > <Text style={styles.text}>{title}</Text> </Pressable> ); }
When to use: Always (propTypes removed in React 19)
typescript// Glowing button with outline and blend mode function GlowButton({ title, onPress }) { return ( <Pressable onPress={onPress} style={{ backgroundColor: '#3b82f6', padding: 16, borderRadius: 8, // Outline doesn't affect layout outlineWidth: 2, outlineColor: '#60a5fa', outlineOffset: 4, // Blend with background mixBlendMode: 'screen', isolation: 'isolate' }} > <Text style={{ color: 'white', fontWeight: 'bold' }}> {title} </Text> </Pressable> ); }
When to use: Visual effects without affecting layout (New Architecture only)
check-rn-version.sh - Detects React Native version and warns about architecture requirements
Example Usage:
bash./scripts/check-rn-version.sh # Output: ✅ React Native 0.82 - New Architecture mandatory # Output: ⚠️ React Native 0.75 - Upgrade to 0.76+ recommended
react-19-migration.md - Detailed React 19 breaking changes and migration steps
new-architecture-errors.md - Common build errors when enabling New Architecture
expo-sdk-52-breaking.md - Expo SDK 52+ specific breaking changes
When Claude should load these: When encountering migration errors, build failures, or detailed React 19 questions
new-arch-decision-tree.md - Decision tree for choosing React Native version
css-features-cheatsheet.md - Complete examples of new CSS properties
JSC Removed from Expo Go:
json// This no longer works in Expo Go (SDK 52+): { "jsEngine": "jsc" // ❌ Ignored, Hermes only }
Google Maps Removed from Expo Go (SDK 53+):
bash# Must use custom dev client for Google Maps npx expo install expo-dev-client npx expo run:android
Push Notifications Warning: Expo Go shows warnings for push notifications - use custom dev client for production testing.
expo/fetch (WinterCG-compliant):
typescriptimport { fetch } from 'expo/fetch'; // Standards-compliant fetch for Workers/Edge runtimes const response = await fetch('https://api.example.com/data');
React Navigation v7:
bashnpm install @react-navigation/native@^7.0.0
json{ "dependencies": { "react": "^19.1.0", "react-native": "^0.82.0", "expo": "~52.0.0", "@react-navigation/native": "^7.0.0", "@reduxjs/toolkit": "^2.0.0", "react-i18next": "^15.0.0" }, "devDependencies": { "@types/react": "^19.0.0", "typescript": "^5.7.0" } }
Solution: Library not compatible with New Architecture. Check library docs for New Architecture support, or use interop layer (0.76-0.81 only).
Solution: React 19 removed propTypes. Use TypeScript for type checking instead. Run npx @codemod/react-19 upgrade.
Solution: Metro log forwarding removed in 0.77. Use React Native DevTools Console (press 'j') or npx expo start --client-logs (temporary workaround).
Solution: Add RCTAppDependencyProvider.sharedInstance() to AppDelegate.swift. See Swift migration section.
Solution: Use Redux Toolkit instead of legacy redux + redux-thunk. Install @reduxjs/toolkit.
Solution: New Architecture is mandatory in 0.82+. If you need legacy, stay on 0.81 or earlier (not recommended).
Use this checklist to verify your setup:
react-native/Libraries/*)Questions? Issues?
references/new-architecture-errors.md for build errorsreferences/react-19-migration.md for React 19 issuesKnowledge Gap Filled: This skill covers React Native updates from December 2024+ that LLMs won't know about. Without this skill, Claude would suggest deprecated APIs, removed features, and outdated patterns.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | pass→pass | 14,234 | 12,141 | -15% | 1 | 1 | 0% | 2,423 | 9,394 | +288% | 0 | 0 | — |
case-04 | pass→pass | 9,751 | 8,268 | -15% | 1 | 1 | 0% | 1,888 | 8,877 | +370% | 0 | 0 | — |
case-01 | fail→pass | 21,865 | 19,580 | -10% | 1 | 1 | 0% | 3,887 | 10,798 | +178% | 0 | 0 | — |
case-05 | pass→pass | 10,608 | 6,107 | -42% | 1 | 1 | 0% | 1,630 | 8,316 | +410% | 0 | 0 | — |
case-02 | fail→pass | 16,063 | 8,822 | -45% | 1 | 1 | 0% | 2,817 | 8,905 | +216% | 0 | 0 | — |
case-06 | pass→pass | 9,124 | 7,582 | -17% | 1 | 1 | 0% | 1,466 | 8,160 | +457% | 0 | 0 | — |
case-07 | pass→pass | 6,783 | 7,392 | +9% | 1 | 1 | 0% | 1,198 | 8,369 | +599% | 0 | 0 | — |
case-08 | fail→pass | 14,637 | 11,303 | -23% | 1 | 1 | 0% | 2,468 | 9,039 | +266% | 0 | 0 | — |
case-09 | fail→pass | 18,184 | 13,087 | -28% | 1 | 1 | 0% | 2,835 | 9,465 | +234% | 0 | 0 | — |
case-10 | pass→pass | 10,552 | 5,305 | -50% | 1 | 1 | 0% | 1,730 | 8,006 | +363% | 0 | 0 | — |
case-11 | pass→pass | 5,349 | 5,541 | +4% | 1 | 1 | 0% | 885 | 8,047 | +809% | 0 | 0 | — |
case-12 | pass→pass | 12,434 | 6,488 | -48% | 1 | 1 | 0% | 2,089 | 8,556 | +310% | 0 | 0 | — |
case-13 | pass→pass | 10,103 | 4,433 | -56% | 1 | 1 | 0% | 1,658 | 7,934 | +379% | 0 | 0 | — |
case-14 | pass→pass | 15,557 | 6,754 | -57% | 1 | 1 | 0% | 2,426 | 8,376 | +245% | 0 | 0 | — |
case-15 | pass→pass | 8,081 | 5,500 | -32% | 1 | 1 | 0% | 1,433 | 8,314 | +480% | 0 | 0 | — |
case-16 | pass→pass | 5,687 | 4,835 | -15% | 1 | 1 | 0% | 939 | 8,134 | +766% | 0 | 0 | — |
case-17 | pass→pass | 7,821 | 7,892 | +1% | 1 | 1 | 0% | 1,340 | 8,588 | +541% | 0 | 0 | — |
case-18 | pass→pass | 5,743 | 4,266 | -26% | 1 | 1 | 0% | 918 | 7,861 | +756% | 0 | 0 | — |
case-19 | pass→pass | 14,118 | 6,920 | -51% | 1 | 1 | 0% | 2,102 | 8,314 | +296% | 0 | 0 | — |
case-20 | pass→pass | 6,422 | 10,152 | +58% | 1 | 1 | 0% | 1,296 | 8,957 | +591% | 0 | 0 | — |
case-21 | pass→pass | 18,904 | 9,852 | -48% | 1 | 1 | 0% | 1,868 | 8,808 | +372% | 0 | 0 | — |
case-22 | pass→pass | 8,364 | 8,558 | +2% | 1 | 1 | 0% | 1,575 | 8,916 | +466% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.