Refactor React Native and TypeScript code to improve maintainability, readability, and performance for cross-platform mobile applications. This skill transforms complex mobile code into clean, well-structured solutions following React Native New Architecture patterns including Fabric, TurboModules, and JSI. It addresses FlatList performance issues, prop drilling, platform-specific code organization, and inline styles. Leverages Expo SDK 52+ features, React Navigation v7, and Reanimated for smooth 60fps animations.
Refactors React Native and TypeScript code for maintainability, performance, and cross-platform excellence.
/plugin marketplace add SnakeO/claude-debug-and-refactor-skills-plugin/plugin install debug-and-refactor@snakeo-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
You are an elite React Native/TypeScript refactoring specialist with deep expertise in writing clean, maintainable, and performant cross-platform mobile applications. You have mastered React Native's New Architecture (Fabric, TurboModules, JSI), Expo SDK 52+, and modern React patterns.
JavaScript Interface (JSI) JSI replaces the old asynchronous bridge with synchronous, direct communication between JavaScript and native code:
// Old Architecture: Async bridge communication
// Every call goes through JSON serialization
NativeModules.LocationModule.getCurrentLocation()
.then(location => console.log(location));
// New Architecture: Direct JSI binding
// Synchronous, no serialization overhead
const location = LocationModule.getCurrentLocationSync();
TurboModules TurboModules replace NativeModules with lazy-loaded, type-safe native modules:
// TurboModule Specification (Codegen)
// specs/NativeLocationModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getCurrentLocation(): Promise<{
latitude: number;
longitude: number;
accuracy: number;
}>;
watchLocation(callback: (location: Location) => void): number;
clearWatch(watchId: number): void;
}
export default TurboModuleRegistry.getEnforcing<Spec>('LocationModule');
Fabric Renderer Fabric uses an immutable UI tree with C++ core for better performance:
// Fabric enables concurrent features
import { startTransition } from 'react';
function SearchScreen() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleSearch = (text: string) => {
setQuery(text);
// Low-priority update - Fabric can interrupt for high-priority events
startTransition(() => {
setResults(performExpensiveSearch(text));
});
};
return (
<View>
<TextInput value={query} onChangeText={handleSearch} />
<ResultsList results={results} />
</View>
);
}
// android/gradle.properties
newArchEnabled=true
// ios/Podfile
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
// Check architecture at runtime
import { Platform } from 'react-native';
const isNewArch = (global as any).__turboModuleProxy != null;
console.log(`New Architecture: ${isNewArch ? 'Enabled' : 'Disabled'}`);
npx expo-doctor to check library compatibility// Use Expo (managed workflow) when:
// - Building standard features (camera, location, notifications)
// - Need rapid iteration with EAS Build
// - Team has limited native development experience
// - Project doesn't need custom native modules
// Use Bare Workflow when:
// - Need custom native modules not available in Expo
// - Require specific native SDK integrations
// - Need fine-grained control over native configuration
# Delete native directories before upgrading
rm -rf android ios
# Regenerate with prebuild
npx expo prebuild
# Or let EAS Build handle it
eas build --platform all
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack>
);
}
// app/(tabs)/index.tsx
export default function HomeScreen() {
return (
<View>
<Link href="/profile">Go to Profile</Link>
<Link href={{ pathname: '/details/[id]', params: { id: '123' } }}>
View Details
</Link>
</View>
);
}
// app/details/[id].tsx
import { useLocalSearchParams } from 'expo-router';
export default function DetailsScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
return <Text>Details for {id}</Text>;
}
// Modern streaming fetch for AI APIs
import { fetch } from 'expo/fetch';
async function streamResponse(prompt: string) {
const response = await fetch('https://api.ai-service.com/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt }),
});
const reader = response.body?.getReader();
while (true) {
const { done, value } = await reader!.read();
if (done) break;
const text = new TextDecoder().decode(value);
console.log(text);
}
}
// BAD: Unoptimized FlatList
function BadList({ data }) {
return (
<FlatList
data={data}
renderItem={({ item }) => <ListItem item={item} onPress={() => handlePress(item.id)} />}
keyExtractor={(item, index) => index.toString()} // Array index as key!
/>
);
}
// GOOD: Optimized FlatList
const ITEM_HEIGHT = 72;
function OptimizedList({ data, onItemPress }) {
const renderItem = useCallback(
({ item }: { item: DataItem }) => (
<MemoizedListItem item={item} onPress={onItemPress} />
),
[onItemPress]
);
const keyExtractor = useCallback((item: DataItem) => item.id, []);
const getItemLayout = useCallback(
(_: unknown, index: number) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}),
[]
);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
removeClippedSubviews={true}
updateCellsBatchingPeriod={50}
/>
);
}
// Memoized list item
const MemoizedListItem = React.memo(
function ListItem({ item, onPress }: ListItemProps) {
const handlePress = useCallback(() => onPress(item.id), [item.id, onPress]);
return (
<TouchableOpacity onPress={handlePress} style={styles.item}>
<Text>{item.title}</Text>
</TouchableOpacity>
);
}
);
// For lists with 1000+ items, use FlashList from Shopify
import { FlashList } from '@shopify/flash-list';
function HighPerformanceList({ data }) {
return (
<FlashList
data={data}
renderItem={({ item }) => <ListItem item={item} />}
estimatedItemSize={72}
keyExtractor={(item) => item.id}
/>
);
}
// Use expo-image or react-native-fast-image for better caching
import { Image } from 'expo-image';
function OptimizedImage({ uri }: { uri: string }) {
return (
<Image
source={{ uri }}
style={styles.image}
contentFit="cover"
transition={200}
placeholder={blurhash}
cachePolicy="memory-disk"
/>
);
}
// Use Reanimated for 60fps animations on UI thread
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
} from 'react-native-reanimated';
function AnimatedCard() {
const scale = useSharedValue(1);
const opacity = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: opacity.value,
}));
const handlePressIn = () => {
scale.value = withSpring(0.95);
opacity.value = withTiming(0.8);
};
const handlePressOut = () => {
scale.value = withSpring(1);
opacity.value = withTiming(1);
};
return (
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text>Press Me</Text>
</Animated.View>
</Pressable>
);
}
// types/navigation.ts
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
export type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: undefined;
Details: { itemId: string; title: string };
};
export type TabParamList = {
Feed: undefined;
Search: { query?: string };
Notifications: undefined;
};
export type RootStackScreenProps<T extends keyof RootStackParamList> =
NativeStackScreenProps<RootStackParamList, T>;
export type TabScreenProps<T extends keyof TabParamList> =
BottomTabScreenProps<TabParamList, T>;
// Usage in screen
function ProfileScreen({ route, navigation }: RootStackScreenProps<'Profile'>) {
const { userId } = route.params; // Type-safe!
const goToDetails = () => {
navigation.navigate('Details', { itemId: '123', title: 'Test' }); // Type-checked!
};
}
// hooks/useTypedNavigation.ts
import { useNavigation, useRoute } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { RouteProp } from '@react-navigation/native';
import type { RootStackParamList } from '../types/navigation';
export function useAppNavigation() {
return useNavigation<NativeStackNavigationProp<RootStackParamList>>();
}
export function useAppRoute<T extends keyof RootStackParamList>() {
return useRoute<RouteProp<RootStackParamList, T>>();
}
// Usage
function MyComponent() {
const navigation = useAppNavigation();
const route = useAppRoute<'Profile'>();
navigation.navigate('Details', { itemId: '123', title: 'Test' });
}
// navigation/linking.ts
import { LinkingOptions } from '@react-navigation/native';
import * as Linking from 'expo-linking';
const linking: LinkingOptions<RootStackParamList> = {
prefixes: [Linking.createURL('/'), 'myapp://'],
config: {
screens: {
Home: '',
Profile: 'profile/:userId',
Details: 'details/:itemId',
Settings: 'settings',
},
},
};
// App.tsx
function App() {
return (
<NavigationContainer linking={linking}>
<RootStack.Navigator>
{/* screens */}
</RootStack.Navigator>
</NavigationContainer>
);
}
// components/Button/Button.tsx (shared logic)
export interface ButtonProps {
title: string;
onPress: () => void;
disabled?: boolean;
}
// components/Button/Button.ios.tsx
import { TouchableOpacity, Text } from 'react-native';
import type { ButtonProps } from './Button';
export function Button({ title, onPress, disabled }: ButtonProps) {
return (
<TouchableOpacity
onPress={onPress}
disabled={disabled}
style={styles.iosButton}
>
<Text style={styles.iosText}>{title}</Text>
</TouchableOpacity>
);
}
// components/Button/Button.android.tsx
import { TouchableNativeFeedback, Text, View } from 'react-native';
import type { ButtonProps } from './Button';
export function Button({ title, onPress, disabled }: ButtonProps) {
return (
<TouchableNativeFeedback
onPress={onPress}
disabled={disabled}
background={TouchableNativeFeedback.Ripple('#rgba(0,0,0,0.1)', false)}
>
<View style={styles.androidButton}>
<Text style={styles.androidText}>{title}</Text>
</View>
</TouchableNativeFeedback>
);
}
// hooks/useHaptics.ts
import { Platform } from 'react-native';
import * as Haptics from 'expo-haptics';
export function useHaptics() {
const light = async () => {
if (Platform.OS === 'ios') {
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
} else {
await Haptics.selectionAsync();
}
};
const success = async () => {
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
};
const error = async () => {
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
};
return { light, success, error };
}
// stores/authStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface AuthState {
user: User | null;
token: string | null;
isLoading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
hydrated: boolean;
setHydrated: (hydrated: boolean) => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({
user: null,
token: null,
isLoading: false,
hydrated: false,
setHydrated: (hydrated) => set({ hydrated }),
login: async (email, password) => {
set({ isLoading: true });
try {
const { user, token } = await authApi.login(email, password);
set({ user, token, isLoading: false });
} catch (error) {
set({ isLoading: false });
throw error;
}
},
logout: () => {
set({ user: null, token: null });
},
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
onRehydrateStorage: () => (state) => {
state?.setHydrated(true);
},
}
)
);
// Usage with hydration check
function AuthGuard({ children }) {
const { hydrated, user } = useAuthStore();
if (!hydrated) {
return <SplashScreen />;
}
if (!user) {
return <LoginScreen />;
}
return children;
}
// hooks/useProducts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useProducts(categoryId?: string) {
return useQuery({
queryKey: ['products', categoryId],
queryFn: () => productsApi.getAll(categoryId),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
export function useProduct(id: string) {
return useQuery({
queryKey: ['products', id],
queryFn: () => productsApi.getById(id),
enabled: !!id,
});
}
export function useCreateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: productsApi.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['products'] });
},
});
}
// Usage in component
function ProductsList({ categoryId }: { categoryId: string }) {
const { data: products, isLoading, error, refetch } = useProducts(categoryId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} onRetry={refetch} />;
return (
<FlatList
data={products}
renderItem={({ item }) => <ProductCard product={item} />}
keyExtractor={(item) => item.id}
onRefresh={refetch}
refreshing={isLoading}
/>
);
}
// BAD: Inline styles
function BadComponent() {
return (
<View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold', color: '#333' }}>
Title
</Text>
</View>
);
}
// GOOD: StyleSheet.create
function GoodComponent() {
return (
<View style={styles.container}>
<Text style={styles.title}>Title</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: '#fff',
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
},
});
// theme/tokens.ts
export const colors = {
primary: {
50: '#eff6ff',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
},
neutral: {
50: '#fafafa',
100: '#f5f5f5',
900: '#171717',
},
error: '#ef4444',
success: '#22c55e',
} as const;
export const spacing = {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
} as const;
export const typography = {
h1: { fontSize: 32, fontWeight: '700' as const, lineHeight: 40 },
h2: { fontSize: 24, fontWeight: '600' as const, lineHeight: 32 },
body: { fontSize: 16, fontWeight: '400' as const, lineHeight: 24 },
caption: { fontSize: 12, fontWeight: '400' as const, lineHeight: 16 },
} as const;
// theme/ThemeContext.tsx
import { createContext, useContext } from 'react';
import { useColorScheme } from 'react-native';
const ThemeContext = createContext<Theme>(lightTheme);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const colorScheme = useColorScheme();
const theme = colorScheme === 'dark' ? darkTheme : lightTheme;
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}
export const useTheme = () => useContext(ThemeContext);
// Using NativeWind (Tailwind for React Native)
import { styled } from 'nativewind';
const StyledView = styled(View);
const StyledText = styled(Text);
function Card({ title, description }: CardProps) {
return (
<StyledView className="bg-white p-4 rounded-lg shadow-md dark:bg-gray-800">
<StyledText className="text-lg font-bold text-gray-900 dark:text-white">
{title}
</StyledText>
<StyledText className="text-gray-600 dark:text-gray-300">
{description}
</StyledText>
</StyledView>
);
}
src/
app/ # Expo Router pages (if using Expo Router)
(tabs)/
index.tsx
profile.tsx
_layout.tsx
features/ # Feature modules
auth/
components/
LoginForm.tsx
RegisterForm.tsx
SocialLoginButtons.tsx
hooks/
useAuth.ts
useSession.ts
screens/
LoginScreen.tsx
RegisterScreen.tsx
services/
authApi.ts
stores/
authStore.ts
types/
auth.types.ts
index.ts # Public exports
products/
components/
ProductCard.tsx
ProductList.tsx
ProductFilters.tsx
hooks/
useProducts.ts
useProductSearch.ts
screens/
ProductListScreen.tsx
ProductDetailScreen.tsx
services/
productsApi.ts
types/
products.types.ts
index.ts
shared/ # Shared across features
components/
Button/
Button.tsx
Button.ios.tsx
Button.android.tsx
Button.test.tsx
Input/
Card/
hooks/
useDebounce.ts
useNetwork.ts
useHaptics.ts
utils/
formatting.ts
validation.ts
services/
apiClient.ts
storage.ts
theme/
tokens.ts
ThemeContext.tsx
types/
common.ts
navigation.ts
assets/
images/
fonts/
config/
env.ts
constants.ts
// Explicit prop types with JSDoc
interface CardProps {
/** The card title */
title: string;
/** Optional subtitle */
subtitle?: string;
/** Card content */
children: React.ReactNode;
/** Callback when card is pressed */
onPress?: () => void;
/** Visual variant */
variant?: 'default' | 'outlined' | 'elevated';
/** Test ID for E2E testing */
testID?: string;
}
function Card({
title,
subtitle,
children,
onPress,
variant = 'default',
testID,
}: CardProps) {
const Wrapper = onPress ? TouchableOpacity : View;
return (
<Wrapper
onPress={onPress}
style={[styles.card, styles[variant]]}
testID={testID}
>
<Text style={styles.title}>{title}</Text>
{subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
{children}
</Wrapper>
);
}
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
emptyComponent?: React.ReactNode;
ListHeaderComponent?: React.ComponentType | React.ReactElement;
}
function GenericList<T>({
items,
renderItem,
keyExtractor,
emptyComponent,
ListHeaderComponent,
}: ListProps<T>) {
if (items.length === 0 && emptyComponent) {
return <>{emptyComponent}</>;
}
return (
<FlatList
data={items}
renderItem={({ item, index }) => renderItem(item, index)}
keyExtractor={keyExtractor}
ListHeaderComponent={ListHeaderComponent}
/>
);
}
// Usage with type inference
<GenericList
items={users}
renderItem={(user) => <UserCard user={user} />}
keyExtractor={(user) => user.id}
emptyComponent={<EmptyState message="No users found" />}
/>
// types/native.d.ts
declare module 'react-native' {
interface NativeModulesStatic {
CustomModule: {
getValue(): Promise<string>;
setValue(value: string): Promise<void>;
addListener(event: string): void;
removeListeners(count: number): void;
};
}
}
// Usage
import { NativeModules, NativeEventEmitter } from 'react-native';
const { CustomModule } = NativeModules;
const eventEmitter = new NativeEventEmitter(CustomModule);
function useCustomModule() {
useEffect(() => {
const subscription = eventEmitter.addListener('onValueChange', (event) => {
console.log(event);
});
return () => subscription.remove();
}, []);
return {
getValue: CustomModule.getValue,
setValue: CustomModule.setValue,
};
}
// BAD: Inline functions and missing optimizations
<FlatList
data={items}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => handlePress(item.id)}>
<Text>{item.name}</Text>
</TouchableOpacity>
)}
keyExtractor={(item, index) => index.toString()}
/>
// GOOD: Memoized components and proper configuration
const ItemComponent = React.memo(({ item, onPress }) => (
<TouchableOpacity onPress={() => onPress(item.id)}>
<Text>{item.name}</Text>
</TouchableOpacity>
));
const renderItem = useCallback(({ item }) => (
<ItemComponent item={item} onPress={handlePress} />
), [handlePress]);
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={useCallback((item) => item.id, [])}
getItemLayout={useCallback((_, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}), [])}
/>
// BAD: Inline styles create new objects every render
<View style={{ flex: 1, padding: 16 }}>
<Text style={{ fontSize: 18, color: pressed ? 'blue' : 'black' }}>
Hello
</Text>
</View>
// GOOD: StyleSheet + array syntax for dynamic styles
<View style={styles.container}>
<Text style={[styles.text, pressed && styles.textPressed]}>
Hello
</Text>
</View>
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
text: { fontSize: 18, color: 'black' },
textPressed: { color: 'blue' },
});
// BAD: API logic mixed with component
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
// ... render logic
}
// GOOD: Extract to custom hook or use TanStack Query
function useUser(userId: string) {
return useQuery({
queryKey: ['users', userId],
queryFn: () => userApi.getById(userId),
enabled: !!userId,
});
}
function UserProfile({ userId }) {
const { data: user, isLoading, error } = useUser(userId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return <UserCard user={user} />;
}
// BAD: No error handling
function App() {
return (
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
);
}
// GOOD: Error boundary with fallback
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Something went wrong</Text>
<Text style={styles.message}>{error.message}</Text>
<Button title="Try Again" onPress={resetErrorBoundary} />
</SafeAreaView>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error) => crashlytics().recordError(error)}
>
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
</ErrorBoundary>
);
}
// BAD: Heavy computation on JS thread
function DataProcessor({ rawData }) {
// This blocks the JS thread and causes jank
const processedData = heavyComputation(rawData);
return <DataVisualization data={processedData} />;
}
// GOOD: Use InteractionManager or move to native
import { InteractionManager } from 'react-native';
function DataProcessor({ rawData }) {
const [processedData, setProcessedData] = useState(null);
useEffect(() => {
// Wait for animations to complete
InteractionManager.runAfterInteractions(() => {
const result = heavyComputation(rawData);
setProcessedData(result);
});
}, [rawData]);
if (!processedData) return <LoadingSpinner />;
return <DataVisualization data={processedData} />;
}
When refactoring React Native code, provide:
Stop refactoring when:
any types, no type errorsThis skill should be used when the user asks about libraries, frameworks, API references, or needs code examples. Activates for setup questions, code generation involving libraries, or mentions of specific frameworks like React, Vue, Next.js, Prisma, Supabase, etc.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.