refactor:react-native

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.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "refactor:react-native" with this command: npx skills add snakeo/claude-debug-and-refactor-skills-plugin/snakeo-claude-debug-and-refactor-skills-plugin-refactor-react-native

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.

Core Refactoring Principles

DRY (Don't Repeat Yourself)

  • Extract repeated JSX into reusable components

  • Create custom hooks for shared stateful logic (API calls, platform APIs)

  • Use utility functions for repeated computations

  • Consolidate similar navigation handlers and animations

Single Responsibility Principle (SRP)

  • Each component should do ONE thing well

  • Screen components handle navigation and layout; UI components handle display

  • Custom hooks encapsulate single pieces of logic (useAuth, useLocation, useCamera)

  • Separate business logic from presentation

Early Returns and Guard Clauses

  • Return early for loading, error, and empty states

  • Avoid deeply nested conditionals in JSX

  • Use guard clauses to handle edge cases first (permissions, network state)

Small, Focused Functions

  • Components under 150 lines (ideally under 100)

  • Custom hooks under 50 lines

  • Event handlers under 20 lines

  • Extract complex logic into helper functions

React Native New Architecture (2025)

Understanding the Three Pillars

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> ); }

Migrating to New Architecture

// 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'});

Best Practices for New Architecture

  • Enable on pilot screens first, measure, then expand

  • Convert high-traffic NativeModules first

  • Use Codegen for type-safe bindings

  • Pair Fabric with list virtualization and memoized renderers

  • Run npx expo-doctor to check library compatibility

Expo Best Practices (SDK 52+)

Expo vs Bare Workflow Decision

// 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

Continuous Native Generation (CNG)

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

Expo Router for Navigation

// 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>; }

Expo Fetch (WinterCG-compliant)

// 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); } }

Performance Optimization

FlatList Optimization

// 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 (
  &#x3C;TouchableOpacity onPress={handlePress} style={styles.item}>
    &#x3C;Text>{item.title}&#x3C;/Text>
  &#x3C;/TouchableOpacity>
);

} );

FlashList for Better Performance

// 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} /> ); }

Image Optimization

// 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" /> ); }

Animation Performance

// 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> ); }

Navigation Patterns (React Navigation v7)

Type-Safe Navigation

// 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! }; }

Custom Navigation Hooks

// 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' }); }

Deep Linking Configuration

// 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> ); }

Platform-Specific Code

File-Based Platform Extensions

// 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> ); }

Platform-Specific Hooks

// 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 }; }

State Management

Zustand for React Native

// 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; }

TanStack Query for Server State

// 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} /> ); }

Styling Patterns

StyleSheet Best Practices

// 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', }, });

Design Tokens and Theme

// 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);

Styled Components / NativeWind

// 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> ); }

Feature-Based Folder Structure

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

TypeScript Best Practices

Component Props Typing

// 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> ); }

Generic Components

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" />} />

Native Module Types

// 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, }; }

Anti-Patterns to Refactor

  1. FlatList Performance Issues

// 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, }), [])} />

  1. Inline Styles

// 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' }, });

  1. API Calls in Components

// 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} />; }

  1. Missing Error Boundaries

// 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> ); }

  1. Blocking the JS Thread

// 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} />; }

Refactoring Process

Step 1: Analyze the Component/Screen

  • Read the entire component and understand its purpose

  • Identify responsibilities (data fetching, state management, UI rendering, navigation)

  • List all props, state, effects, and native dependencies

  • Note any code smells or anti-patterns

  • Check platform-specific requirements

Step 2: Plan the Refactoring

  • Determine if the component should be split

  • Identify logic that can be extracted to custom hooks

  • Plan platform-specific code separation

  • Consider navigation and deep linking improvements

  • Plan TypeScript type improvements

Step 3: Execute Incrementally

  • Extract custom hooks first (preserves behavior)

  • Split large screens into smaller components

  • Apply memoization for lists and expensive computations

  • Separate platform-specific code into .ios.js/.android.js files

  • Add/improve TypeScript types

  • Fix anti-patterns

Step 4: Verify

  • Test on both iOS and Android

  • Verify navigation works correctly

  • Check performance with Flipper/React DevTools

  • Ensure TypeScript compiles without errors

  • Test edge cases (loading, error, empty states, offline)

Output Format

When refactoring React Native code, provide:

  • Analysis Summary: Brief description of issues found

  • Refactored Code: Complete, working code with improvements

  • Changes Made: Bulleted list of specific changes

  • Platform Considerations: Any iOS/Android-specific notes

  • Performance Impact: Expected performance improvements

Quality Standards

Code Quality Checklist

  • Components follow Single Responsibility Principle

  • No prop drilling beyond 2 levels

  • All hooks follow rules of hooks

  • Dependency arrays are complete and correct

  • TypeScript types are explicit and accurate

  • StyleSheet.create used instead of inline styles

  • Keys are stable and unique (not array indices)

  • Error boundaries wrap critical sections

  • Loading and error states are handled

  • Platform-specific code properly separated

Performance Checklist

  • FlatList has memoized renderItem

  • FlatList has getItemLayout for fixed-height items

  • Images use caching (expo-image or FastImage)

  • Animations use Reanimated (UI thread)

  • Heavy computations use InteractionManager

  • Bundle uses code splitting and lazy loading

  • New Architecture enabled for compatible libraries

React Native-Specific Checklist

  • SafeAreaView used for notched devices

  • StatusBar configured properly

  • Keyboard avoiding behavior implemented

  • Deep linking configured

  • Push notifications handled

  • App state changes handled (foreground/background)

When to Stop Refactoring

Stop refactoring when:

  • Component is under 100 lines and has single responsibility

  • State is colocated - each piece of state is as close as possible to where it's used

  • Props are minimal - component receives only what it needs

  • No obvious code smells remain

  • TypeScript is happy - no any types, no type errors

  • Tests pass - behavior is preserved on both platforms

  • Further changes would be premature optimization - don't optimize without evidence of performance issues

  • FlatList performs well - smooth scrolling at 60fps

References

  • React Native New Architecture

  • Expo SDK 52 Documentation

  • React Navigation v7

  • Optimizing FlatList Configuration

  • React Native Performance

  • Expo Router

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

refactor:flutter

No summary provided by upstream source.

Repository SourceNeeds Review
General

refactor:nestjs

No summary provided by upstream source.

Repository SourceNeeds Review
General

debug:flutter

No summary provided by upstream source.

Repository SourceNeeds Review