Back to all posts
March 28, 2025Charlie BrownDevelopment

React Native Performance Optimization: Advanced Techniques

Master React Native performance optimization with techniques for reducing re-renders, optimizing images, improving list performance, and memory management.

React Native Performance Optimization: Advanced Techniques

React Native Performance Optimization: Advanced Techniques

React Native apps can suffer from performance issues if not optimized properly. This article explores advanced techniques to improve app performance, reduce memory usage, and create smooth user experiences.

Understanding React Native Performance

Key performance metrics:

  • Frame Rate: Target 60 FPS
  • Time to Interactive: First meaningful paint
  • Memory Usage: Monitor for leaks
  • Bundle Size: Minimize JavaScript bundle

Reducing Re-renders

1. Use React.memo

Prevent unnecessary re-renders:

typescript
import React, { memo } from 'react';

interface UserCardProps {
  name: string;
  email: string;
  onPress: () => void;
}

const UserCard = memo(({ name, email, onPress }: UserCardProps) => {
  return (
    <TouchableOpacity onPress={onPress}>
      <Text>{name}</Text>
      <Text>{email}</Text>
    </TouchableOpacity>
  );
}, (prevProps, nextProps) => {
  // Custom comparison
  return prevProps.name === nextProps.name &&
         prevProps.email === nextProps.email;
});

export default UserCard;

2. useMemo for Expensive Calculations

typescript
import { useMemo } from 'react';

function ProductList({ products, filters }) {
  const filteredProducts = useMemo(() => {
    return products.filter(product => {
      return filters.every(filter => filter(product));
    });
  }, [products, filters]);

  return (
    <FlatList
      data={filteredProducts}
      renderItem={({ item }) => <ProductItem product={item} />}
    />
  );
}

3. useCallback for Function References

typescript
import { useCallback, useState } from 'react';

function UserList({ users }) {
  const [selectedId, setSelectedId] = useState(null);

  const handleSelect = useCallback((id: string) => {
    setSelectedId(id);
  }, []); // Empty deps - function never changes

  return (
    <FlatList
      data={users}
      renderItem={({ item }) => (
        <UserItem
          user={item}
          onSelect={handleSelect}
          isSelected={item.id === selectedId}
        />
      )}
    />
  );
}

Optimizing Lists

FlatList Best Practices

typescript
import { FlatList } from 'react-native';

function OptimizedList({ items }) {
  return (
    <FlatList
      data={items}
      renderItem={({ item }) => <ListItem item={item} />}
      keyExtractor={(item) => item.id}
      
      // Performance optimizations
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      initialNumToRender={10}
      windowSize={10}
      
      // Memory optimization
      getItemLayout={(data, index) => ({
        length: ITEM_HEIGHT,
        offset: ITEM_HEIGHT * index,
        index,
      })}
    />
  );
}

Virtualized Lists for Large Data

typescript
import { VirtualizedList } from 'react-native';

function LargeList({ items }) {
  const getItem = (data: any, index: number) => data[index];
  const getItemCount = () => items.length;

  return (
    <VirtualizedList
      data={items}
      renderItem={({ item }) => <ListItem item={item} />}
      keyExtractor={(item) => item.id}
      getItem={getItem}
      getItemCount={getItemCount}
      initialNumToRender={10}
      maxToRenderPerBatch={10}
    />
  );
}

Image Optimization

1. Use Optimized Image Formats

typescript
import { Image } from 'react-native';

function OptimizedImage({ source, ...props }) {
  return (
    <Image
      source={source}
      resizeMode="cover"
      // Use WebP on Android for better compression
      defaultSource={require('./placeholder.png')}
      // Progressive loading
      progressiveRenderingEnabled={true}
      {...props}
    />
  );
}

2. Lazy Loading Images

typescript
import { useState, useEffect } from 'react';
import { Image } from 'react-native';

function LazyImage({ uri, style }) {
  const [loaded, setLoaded] = useState(false);

  return (
    <>
      {!loaded && <Placeholder style={style} />}
      <Image
        source={{ uri }}
        style={[style, { opacity: loaded ? 1 : 0 }]}
        onLoad={() => setLoaded(true)}
      />
    </>
  );
}

3. Image Caching

typescript
import FastImage from 'react-native-fast-image';

function CachedImage({ uri, style }) {
  return (
    <FastImage
      source={{
        uri,
        priority: FastImage.priority.normal,
        cache: FastImage.cacheControl.immutable,
      }}
      style={style}
      resizeMode={FastImage.resizeMode.cover}
    />
  );
}

Memory Management

1. Clean Up Subscriptions

typescript
import { useEffect } from 'react';
import { AppState } from 'react-native';

function useAppState() {
  useEffect(() => {
    const subscription = AppState.addEventListener('change', (nextAppState) => {
      if (nextAppState === 'background') {
        // Clean up resources
      }
    });

    return () => {
      subscription.remove();
    };
  }, []);
}

2. Avoid Memory Leaks

typescript
import { useEffect, useRef } from 'react';

function Component() {
  const mountedRef = useRef(true);

  useEffect(() => {
    return () => {
      mountedRef.current = false;
    };
  }, []);

  const fetchData = async () => {
    const data = await api.getData();
    if (mountedRef.current) {
      setData(data);
    }
  };
}

Animation Performance

1. Use Native Driver

typescript
import { Animated } from 'react-native';

function AnimatedComponent() {
  const fadeAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true, // Use native driver for better performance
    }).start();
  }, []);

  return (
    <Animated.View style={{ opacity: fadeAnim }}>
      {/* Content */}
    </Animated.View>
  );
}

2. Reanimated for Complex Animations

typescript
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

function SpringAnimation() {
  const translateX = useSharedValue(0);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ translateX: translateX.value }],
    };
  });

  const handlePress = () => {
    translateX.value = withSpring(100);
  };

  return (
    <Animated.View style={animatedStyle}>
      <Button onPress={handlePress} title="Animate" />
    </Animated.View>
  );
}

Bundle Size Optimization

1. Code Splitting

typescript
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyComponent />
    </Suspense>
  );
}

2. Tree Shaking

typescript
// ❌ Bad: Imports entire library
import _ from 'lodash';

// ✅ Good: Import only what you need
import debounce from 'lodash/debounce';

3. Metro Bundler Configuration

javascript
// metro.config.js
module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true, // Enable inline requires
      },
    }),
  },
};

Performance Monitoring

1. Use Flipper

typescript
import { PerformanceMonitor } from 'react-native-performance-monitor';

function App() {
  return (
    <>
      <PerformanceMonitor />
      {/* Your app */}
    </>
  );
}

2. Custom Performance Tracking

typescript
import { InteractionManager } from 'react-native';

function trackPerformance(name: string, fn: () => void) {
  const start = Date.now();
  InteractionManager.runAfterInteractions(() => {
    fn();
    const duration = Date.now() - start;
    console.log(`${name} took ${duration}ms`);
  });
}

Best Practices

1. Avoid Inline Functions

typescript
// ❌ Bad: Creates new function on every render
<FlatList
  data={items}
  renderItem={({ item }) => <Item item={item} />}
/>

// ✅ Good: Stable function reference
const renderItem = useCallback(({ item }) => <Item item={item} />, []);

<FlatList data={items} renderItem={renderItem} />

2. Optimize Style Objects

typescript
// ❌ Bad: Creates new object on every render
<View style={{ padding: 10, margin: 5 }} />

// ✅ Good: Use StyleSheet.create
const styles = StyleSheet.create({
  container: {
    padding: 10,
    margin: 5,
  },
});

<View style={styles.container} />

3. Debounce User Input

typescript
import { useMemo } from 'react';
import { debounce } from 'lodash';

function SearchInput() {
  const [query, setQuery] = useState('');

  const debouncedSearch = useMemo(
    () => debounce((text: string) => {
      // Perform search
    }, 300),
    []
  );

  return (
    <TextInput
      onChangeText={(text) => {
        setQuery(text);
        debouncedSearch(text);
      }}
    />
  );
}

Conclusion

React Native performance optimization requires understanding rendering cycles, memory management, and platform-specific optimizations. By implementing these techniques—reducing re-renders, optimizing lists, managing memory, and monitoring performance—you can create smooth, responsive React Native applications that provide excellent user experiences.

References

Want more insights?

Subscribe to our newsletter or follow us for more updates on software development and team scaling.

Contact Us