reactnative
/

React Native Components – Core, Custom & Performance

Last Sync: Today

On this page

10
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

reactnative

React Native Components – Core, Custom & Performance

React Native Components – The Building Blocks

React Native provides a set of core components that map directly to native UI elements. Unlike React for web (where you use <div>, <span>, etc.), React Native uses platform-agnostic components that render to native iOS (UIKit) and Android (Material Design) widgets. Understanding these components, their behavior, and optimization techniques is critical for building performant cross-platform apps in 2026.

  1. Core Components Overview

Every React Native app is built using these fundamental components. They handle layout, user input, scrolling, and content display.

React JSXRead-only
1
import React from 'react';
import {
  View,
  Text,
  ScrollView,
  FlatList,
  SectionList,
  Image,
  TextInput,
  Button,
  Pressable,
  TouchableOpacity,
  ActivityIndicator,
  Modal,
  StatusBar,
  SafeAreaView,
  KeyboardAvoidingView,
  Platform
} from 'react-native';

// Component reference table:
// View        - Container component (like div), handles layout (flexbox, padding, margin)
// Text        - Displays text (all text must be in <Text>)
// ScrollView  - Generic scrolling container (renders all children at once)
// FlatList    - Performant list for large datasets (lazy rendering)
// SectionList - FlatList with section headers
// Image       - Display images (local or remote)
// TextInput   - Text input field
// Button      - Simple native button
// Pressable   - Modern touchable wrapper (recommended over TouchableOpacity)
// TouchableOpacity - Legacy touchable with opacity feedback
// ActivityIndicator - Loading spinner
// Modal       - Modal dialog component
// StatusBar   - Control device status bar
// SafeAreaView - Handles notches, status bars, home indicators
// KeyboardAvoidingView - Moves content up when keyboard appears

  1. Layout Components (View, SafeAreaView, KeyboardAvoidingView)

View is the most fundamental component — a container that supports layout with flexbox, style, touch handling, and accessibility. Always wrap content in SafeAreaView on iOS to avoid notches and status bars. Use KeyboardAvoidingView for forms.

React JSXRead-only
1
import React from 'react';
import {
  View,
  Text,
  SafeAreaView,
  KeyboardAvoidingView,
  Platform,
  StyleSheet
} from 'react-native';

const LayoutExample = () => {
  return (
    <SafeAreaView style={styles.safeArea}>
      {/* KeyboardAvoidingView handles keyboard push-up on iOS/Android */}
      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        style={styles.keyboardView}
        keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
      >
        {/* Main container using flexbox */}
        <View style={styles.container}>
          {/* Header */}
          <View style={styles.header}>
            <Text style={styles.title}>Layout Example</Text>
          </View>

          {/* Content with flex: 1 takes remaining space */}
          <View style={styles.content}>
            <View style={styles.card}>
              <Text>Card content here</Text>
            </View>
          </View>

          {/* Footer with absolute positioning alternative */}
          <View style={styles.footer}>
            <Text>Footer</Text>
          </View>
        </View>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#f5f5f5'
  },
  keyboardView: {
    flex: 1
  },
  container: {
    flex: 1,
    padding: 16
  },
  header: {
    paddingVertical: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0'
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold'
  },
  content: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  card: {
    backgroundColor: 'white',
    padding: 20,
    borderRadius: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3 // Android shadow
  },
  footer: {
    paddingVertical: 16,
    alignItems: 'center'
  }
});

export default LayoutExample;

// Flexbox properties in React Native (subset of CSS Flexbox):
// flexDirection: 'row' | 'column' (default 'column')
// justifyContent: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly'
// alignItems: 'flex-start' | 'center' | 'flex-end' | 'stretch'
// flexWrap: 'wrap' | 'nowrap'
// flex: number (0 = no grow, 1+ = distribute space)

  1. Text Components & Styling

All text in React Native must be wrapped in a <Text> component. Text components can be nested for styling inheritance. Use numberOfLines for truncation and selectable for user copy.

React JSXRead-only
1
import React from 'react';
import { View, Text, StyleSheet, Platform } from 'react-native';

const TextComponents = () => {
  return (
    <View style={styles.container}>
      {/* Basic text */}
      <Text style={styles.basicText}>Basic Text</Text>

      {/* Nested text for mixed styles */}
      <Text style={styles.paragraph}>
        This is a sentence with{' '}
        <Text style={styles.boldText}>bold text</Text>
        {' '} and{' '}
        <Text style={styles.coloredText}>colored text</Text>
        {' '} inside.
      </Text>

      {/* Truncated text */}
      <Text 
        numberOfLines={2} 
        ellipsizeMode="tail"
        style={styles.truncatedText}
      >
        This is a very long text that will be truncated after two lines. 
        The user will see an ellipsis at the end. This is useful for 
        previews and cards where space is limited.
      </Text>

      {/* Selectable text (users can copy) */}
      <Text selectable style={styles.selectableText}>
        This text can be selected and copied by users (iOS menu, Android long-press).
      </Text>

      {/* Text with adjustsFontSizeToFit (iOS only) */}
      {Platform.OS === 'ios' && (
        <Text 
          adjustsFontSizeToFit 
          numberOfLines={1}
          style={styles.adjustingText}
        >
          This text shrinks to fit in one line on iOS
        </Text>
      )}

      {/* Text with line height */}
      <Text style={styles.lineHeightText}>
        This text has custom line height for better readability.
        It adds spacing between lines which is important for accessibility.
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 16,
    gap: 16
  },
  basicText: {
    fontSize: 16,
    color: '#333333'
  },
  paragraph: {
    fontSize: 14,
    lineHeight: 20,
    color: '#666666'
  },
  boldText: {
    fontWeight: 'bold',
    color: '#000000'
  },
  coloredText: {
    color: '#007AFF',
    textDecorationLine: 'underline'
  },
  truncatedText: {
    fontSize: 14,
    color: '#666666'
  },
  selectableText: {
    fontSize: 14,
    color: '#007AFF',
    backgroundColor: '#f0f0f0',
    padding: 8,
    borderRadius: 4
  },
  adjustingText: {
    fontSize: 24,
    fontWeight: 'bold',
    width: 200,
    backgroundColor: '#f0f0f0'
  },
  lineHeightText: {
    fontSize: 14,
    lineHeight: 24,
    color: '#333333'
  }
});

export default TextComponents;

  1. Scrolling Components: ScrollView vs FlatList

Choose the right scrolling component based on your data size. ScrollView renders all children at once (good for small, fixed content). FlatList renders items lazily (essential for large or dynamic lists).

React JSXRead-only
1
import React, { useState, useCallback } from 'react';
import {
  View,
  Text,
  ScrollView,
  FlatList,
  SectionList,
  ActivityIndicator,
  RefreshControl,
  StyleSheet,
  SafeAreaView
} from 'react-native';

// MARK: - ScrollView (for small/fixed content)
const ScrollViewExample = () => {
  return (
    <ScrollView
      showsVerticalScrollIndicator={false}
      contentContainerStyle={styles.scrollContent}
      // Horizontal scrolling
      // horizontal={true}
      // pagingEnabled={true}
    >
      {Array.from({ length: 20 }).map((_, index) => (
        <View key={index} style={styles.scrollItem}>
          <Text>Item {index + 1}</Text>
        </View>
      ))}
    </ScrollView>
  );
};

// MARK: - FlatList (for large, dynamic lists)
const FlatListExample = () => {
  const [data, setData] = useState(
    Array.from({ length: 100 }).map((_, i) => ({ id: i.toString(), title: `Item ${i + 1}` }))
  );
  const [refreshing, setRefreshing] = useState(false);
  const [loadingMore, setLoadingMore] = useState(false);

  // Pull to refresh
  const onRefresh = useCallback(() => {
    setRefreshing(true);
    setTimeout(() => {
      setData(Array.from({ length: 100 }).map((_, i) => ({ id: i.toString(), title: `Refreshed ${i + 1}` })));
      setRefreshing(false);
    }, 1500);
  }, []);

  // Infinite scroll (pagination)
  const loadMore = useCallback(() => {
    if (loadingMore) return;
    setLoadingMore(true);
    setTimeout(() => {
      const newItems = Array.from({ length: 20 }).map((_, i) => ({
        id: (data.length + i).toString(),
        title: `Item ${data.length + i + 1}`
      }));
      setData(prev => [...prev, ...newItems]);
      setLoadingMore(false);
    }, 1000);
  }, [data.length, loadingMore]);

  const renderItem = useCallback(({ item, index }) => (
    <View style={styles.listItem}>
      <Text style={styles.itemIndex}>{index + 1}</Text>
      <Text style={styles.itemTitle}>{item.title}</Text>
    </View>
  ), []);

  const renderFooter = () => {
    if (!loadingMore) return null;
    return (
      <View style={styles.footerLoader}>
        <ActivityIndicator size="small" />
        <Text style={styles.footerText}>Loading more...</Text>
      </View>
    );
  };

  return (
    <SafeAreaView style={styles.safeArea}>
      <FlatList
        data={data}
        renderItem={renderItem}
        keyExtractor={item => item.id}
        // Performance optimizations
        initialNumToRender={10}
        maxToRenderPerBatch={10}
        windowSize={21}
        removeClippedSubviews={true}
        // Pull to refresh
        refreshControl={
          <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
        }
        // Infinite scroll
        onEndReached={loadMore}
        onEndReachedThreshold={0.5}
        ListFooterComponent={renderFooter}
        // Empty state
        ListEmptyComponent={
          <View style={styles.emptyState}>
            <Text>No items found</Text>
          </View>
        }
        // Optimize for item updates
        extraData={data.length}
      />
    </SafeAreaView>
  );
};

// MARK: - SectionList (grouped data with headers)
const SectionListExample = () => {
  const sections = [
    {
      title: 'Fruits',
      data: ['Apple', 'Banana', 'Orange', 'Mango']
    },
    {
      title: 'Vegetables',
      data: ['Carrot', 'Broccoli', 'Spinach', 'Potato']
    },
    {
      title: 'Dairy',
      data: ['Milk', 'Cheese', 'Yogurt']
    }
  ];

  const renderSectionHeader = ({ section: { title } }) => (
    <View style={styles.sectionHeader}>
      <Text style={styles.sectionHeaderText}>{title}</Text>
    </View>
  );

  const renderItem = ({ item }) => (
    <View style={styles.sectionItem}>
      <Text>{item}</Text>
    </View>
  );

  return (
    <SectionList
      sections={sections}
      keyExtractor={(item, index) => item + index}
      renderSectionHeader={renderSectionHeader}
      renderItem={renderItem}
      stickySectionHeadersEnabled={true}
      contentContainerStyle={styles.sectionList}
    />
  );
};

const styles = StyleSheet.create({
  safeArea: { flex: 1, backgroundColor: '#fff' },
  scrollContent: { padding: 16, gap: 12 },
  scrollItem: {
    padding: 16,
    backgroundColor: '#f0f0f0',
    borderRadius: 8,
    marginBottom: 8
  },
  listItem: {
    flexDirection: 'row',
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
    alignItems: 'center'
  },
  itemIndex: {
    width: 40,
    fontSize: 14,
    color: '#666'
  },
  itemTitle: {
    flex: 1,
    fontSize: 16
  },
  footerLoader: {
    padding: 16,
    alignItems: 'center',
    flexDirection: 'row',
    justifyContent: 'center',
    gap: 8
  },
  footerText: { fontSize: 12, color: '#666' },
  emptyState: { padding: 48, alignItems: 'center' },
  sectionHeader: {
    backgroundColor: '#f8f8f8',
    padding: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0'
  },
  sectionHeaderText: { fontSize: 18, fontWeight: 'bold' },
  sectionItem: { padding: 12, borderBottomWidth: 1, borderBottomColor: '#f0f0f0' },
  sectionList: { paddingBottom: 20 }
});

export { ScrollViewExample, FlatListExample, SectionListExample };

  1. Touchable Components (Pressable, TouchableOpacity, Buttons)

React Native offers multiple touchable components. Pressable is the modern, recommended approach (React Native 0.63+). It provides fine-grained feedback control and better accessibility than legacy Touchable components.

React JSXRead-only
1
import React, { useState } from 'react';
import {
  View,
  Text,
  Pressable,
  TouchableOpacity,
  TouchableHighlight,
  Button,
  Alert,
  StyleSheet,
  Vibration,
  Platform
} from 'react-native';

const TouchableComponents = () => {
  const [pressState, setPressState] = useState('');

  // MARK: - Pressable (modern, recommended)
  const PressableExamples = () => (
    <View style={styles.section}>
      <Text style={styles.sectionTitle}>Pressable (Recommended)</Text>

      {/* Basic Pressable */}
      <Pressable
        onPress={() => Alert.alert('Pressed', 'Basic press detected')}
        style={styles.pressableBasic}
      >
        <Text style={styles.buttonText}>Basic Pressable</Text>
      </Pressable>

      {/* Pressable with hover/pressed states */}
      <Pressable
        onPress={() => setPressState('Pressed!')}
        onPressIn={() => setPressState('Pressing...')}
        onPressOut={() => setPressState('Released')}
        onLongPress={() => {
          Vibration.vibrate(100);
          Alert.alert('Long Press', 'Long press detected');
        }}
        style={({ pressed, hovered }) => [
          styles.pressableState,
          pressed && styles.pressablePressed,
          hovered && styles.pressableHovered
        ]}
      >
        {({ pressed }) => (
          <Text style={styles.buttonText}>
            {pressed ? 'Holding...' : 'Press with State'}
          </Text>
        )}
      </Pressable>
      <Text style={styles.pressState}>{pressState}</Text>

      {/* Pressable with ripple effect (Android) / opacity (iOS) */}
      <Pressable
        onPress={() => {}}
        android_ripple={{ color: '#e0e0e0', borderless: false, radius: 100 }}
        style={({ pressed }) => [
          styles.pressableRipple,
          Platform.OS === 'ios' && pressed && { opacity: 0.7 }
        ]}
      >
        <Text style={styles.buttonText}>Press with Ripple (Android) / Opacity (iOS)</Text>
      </Pressable>

      {/* Disabled state */}
      <Pressable
        onPress={() => {}}
        disabled={true}
        style={[styles.pressableBasic, styles.disabled]}
      >
        <Text style={[styles.buttonText, styles.disabledText]}>Disabled Pressable</Text>
      </Pressable>
    </View>
  );

  // MARK: - TouchableOpacity (legacy, still common)
  const TouchableOpacityExample = () => (
    <View style={styles.section}>
      <Text style={styles.sectionTitle}>TouchableOpacity (Legacy)</Text>
      <TouchableOpacity
        activeOpacity={0.6}
        onPress={() => Alert.alert('TouchableOpacity', 'Simple press')}
        style={styles.touchableOpacity}
      >
        <Text style={styles.buttonText}>TouchableOpacity (fades to 0.6)</Text>
      </TouchableOpacity>
    </View>
  );

  // MARK: - TouchableHighlight (for background highlight)
  const TouchableHighlightExample = () => (
    <View style={styles.section}>
      <Text style={styles.sectionTitle}>TouchableHighlight</Text>
      <TouchableHighlight
        underlayColor="#dddddd"
        onPress={() => Alert.alert('Highlight', 'Background changes color')}
        style={styles.touchableHighlight}
      >
        <Text style={styles.buttonText}>Highlight (background changes)</Text>
      </TouchableHighlight>
    </View>
  );

  // MARK: - Button (native, limited styling)
  const ButtonExample = () => (
    <View style={styles.section}>
      <Text style={styles.sectionTitle}>Native Button</Text>
      <Button
        title="Native Button"
        onPress={() => Alert.alert('Button', 'Native iOS/Android button')}
        color="#007AFF"
        disabled={false}
      />
      <Text style={styles.noteText}>
        Note: Button has limited styling. Use Pressable for custom designs.
      </Text>
    </View>
  );

  return (
    <ScrollView style={styles.container}>
      <PressableExamples />
      <TouchableOpacityExample />
      <TouchableHighlightExample />
      <ButtonExample />
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  },
  section: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0'
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 12
  },
  pressableBasic: {
    backgroundColor: '#007AFF',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 12
  },
  pressableState: {
    backgroundColor: '#34C759',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 8
  },
  pressablePressed: {
    backgroundColor: '#2BA04E',
    transform: [{ scale: 0.98 }]
  },
  pressableHovered: {
    backgroundColor: '#3DD96A'
  },
  pressableRipple: {
    backgroundColor: '#FF3B30',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 12
  },
  pressState: {
    fontSize: 12,
    color: '#666',
    textAlign: 'center',
    marginBottom: 12
  },
  touchableOpacity: {
    backgroundColor: '#5856D6',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center'
  },
  touchableHighlight: {
    backgroundColor: '#FF9500',
    padding: 12,
    borderRadius: 8
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600'
  },
  disabled: {
    backgroundColor: '#c7c7c7'
  },
  disabledText: {
    color: '#8e8e8e'
  },
  noteText: {
    fontSize: 12,
    color: '#666',
    marginTop: 8,
    fontStyle: 'italic'
  }
});

export default TouchableComponents;

  1. Image Component

The Image component handles both local assets and remote images. Use ImageBackground for placing content on top of images. For performance, specify dimensions and use caching strategies.

React JSXRead-only
1
import React, { useState } from 'react';
import {
  View,
  Text,
  Image,
  ImageBackground,
  TouchableOpacity,
  ActivityIndicator,
  StyleSheet,
  Dimensions
} from 'react-native';

const { width: screenWidth } = Dimensions.get('window');

const ImageComponents = () => {
  const [imageLoading, setImageLoading] = useState(true);
  const [imageError, setImageError] = useState(false);

  // Local image (requires bundler configuration)
  const LocalImage = () => (
    <Image
      source={require('../assets/logo.png')} // Local asset
      style={styles.localImage}
      resizeMode="contain" // cover, contain, stretch, repeat, center
    />
  );

  // Remote image with loading/error states
  const RemoteImage = () => (
    <View style={styles.remoteContainer}>
      {imageLoading && (
        <View style={styles.loaderOverlay}>
          <ActivityIndicator size="large" color="#007AFF" />
        </View>
      )}
      {imageError && (
        <View style={styles.errorOverlay}>
          <Text style={styles.errorText}>Failed to load image</Text>
        </View>
      )}
      <Image
        source={{ uri: 'https://picsum.photos/400/300' }}
        style={styles.remoteImage}
        onLoadStart={() => {
          setImageLoading(true);
          setImageError(false);
        }}
        onLoadEnd={() => setImageLoading(false)}
        onError={() => {
          setImageLoading(false);
          setImageError(true);
        }}
        // Caching strategy
        // iOS: NSURLCache, Android: Disk cache
        // Cache control: 'default', 'reload', 'force-cache', 'only-if-cached'
        cache="force-cache"
      />
    </View>
  );

  // ImageBackground (for overlaying text/buttons)
  const ImageBackgroundExample = () => (
    <ImageBackground
      source={{ uri: 'https://picsum.photos/400/200' }}
      style={styles.backgroundImage}
      imageStyle={{ borderRadius: 12 }}
      resizeMode="cover"
    >
      <View style={styles.overlay}>
        <Text style={styles.overlayText}>Text Overlay on Image</Text>
        <TouchableOpacity style={styles.overlayButton}>
          <Text style={styles.overlayButtonText}>Action</Text>
        </TouchableOpacity>
      </View>
    </ImageBackground>
  );

  // Network image with authentication headers
  const AuthenticatedImage = () => {
    const [authToken] = useState('your-jwt-token');

    return (
      <Image
        source={{
          uri: 'https://api.example.com/images/1',
          headers: {
            Authorization: `Bearer ${authToken}`
          }
        }}
        style={styles.remoteImage}
      />
    );
  };

  // Multiple sources for different screen densities
  const MultipleSourcesImage = () => (
    <Image
      source={{
        uri: 'https://example.com/image.jpg',
        width: 400,
        height: 300,
        scale: 2 // For pixel density
      }}
      style={styles.remoteImage}
    />
  );

  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>Image Component Examples</Text>
      
      <Text style={styles.subtitle}>Local Image:</Text>
      <LocalImage />
      
      <Text style={styles.subtitle}>Remote Image with Loading:</Text>
      <RemoteImage />
      
      <Text style={styles.subtitle}>ImageBackground:</Text>
      <ImageBackgroundExample />
      
      <Text style={styles.note}>
        Performance tips:• Always set width/height for remote images
        • Use resizeMode appropriately
        • Consider using react-native-fast-image for advanced caching
        • Use thumbnail URIs for previews
      </Text>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#fff'
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20
  },
  subtitle: {
    fontSize: 18,
    fontWeight: '600',
    marginTop: 16,
    marginBottom: 8
  },
  localImage: {
    width: 150,
    height: 150,
    alignSelf: 'center',
    marginVertical: 10
  },
  remoteContainer: {
    position: 'relative',
    alignItems: 'center',
    justifyContent: 'center'
  },
  remoteImage: {
    width: screenWidth - 32,
    height: 200,
    borderRadius: 8,
    marginVertical: 10
  },
  loaderOverlay: {
    position: 'absolute',
    width: '100%',
    height: '100%',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(0,0,0,0.1)',
    borderRadius: 8,
    zIndex: 1
  },
  errorOverlay: {
    position: 'absolute',
    width: '100%',
    height: '100%',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(0,0,0,0.5)',
    borderRadius: 8,
    zIndex: 1
  },
  errorText: {
    color: '#fff',
    fontSize: 14
  },
  backgroundImage: {
    width: screenWidth - 32,
    height: 200,
    borderRadius: 12,
    overflow: 'hidden',
    marginVertical: 10
  },
  overlay: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.4)',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20
  },
  overlayText: {
    color: '#fff',
    fontSize: 20,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 12
  },
  overlayButton: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 20,
    paddingVertical: 10,
    borderRadius: 8
  },
  overlayButtonText: {
    color: '#fff',
    fontWeight: '600'
  },
  note: {
    fontSize: 12,
    color: '#666',
    marginTop: 20,
    padding: 12,
    backgroundColor: '#f5f5f5',
    borderRadius: 8
  }
});

export default ImageComponents;

  1. Custom Component Creation & Composition

Creating reusable custom components is essential for maintaining large React Native apps. Use props for customization, children for composition, and TypeScript for type safety.

React TSXRead-only
1
import React from 'react';
import {
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
  ViewStyle,
  TextStyle,
  ActivityIndicator
} from 'react-native';

// MARK: - Typed Props Interface (TypeScript)
interface ButtonProps {
  title: string;
  onPress: () => void;
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  loading?: boolean;
  disabled?: boolean;
  style?: ViewStyle;
  textStyle?: TextStyle;
  leftIcon?: React.ReactNode;
  rightIcon?: React.ReactNode;
}

// MARK: - Custom Button Component
const CustomButton: React.FC<ButtonProps> = ({
  title,
  onPress,
  variant = 'primary',
  size = 'medium',
  loading = false,
  disabled = false,
  style,
  textStyle,
  leftIcon,
  rightIcon
}) => {
  const getButtonStyles = (): ViewStyle[] => {
    const baseStyles = [styles.button];
    
    // Variant styles
    if (variant === 'primary') baseStyles.push(styles.primary);
    if (variant === 'secondary') baseStyles.push(styles.secondary);
    if (variant === 'danger') baseStyles.push(styles.danger);
    
    // Size styles
    if (size === 'small') baseStyles.push(styles.small);
    if (size === 'medium') baseStyles.push(styles.medium);
    if (size === 'large') baseStyles.push(styles.large);
    
    // Disabled style
    if (disabled) baseStyles.push(styles.disabled);
    
    if (style) baseStyles.push(style);
    
    return baseStyles;
  };

  const getTextStyles = (): TextStyle[] => {
    const baseStyles = [styles.text];
    if (size === 'small') baseStyles.push(styles.textSmall);
    if (size === 'large') baseStyles.push(styles.textLarge);
    if (disabled) baseStyles.push(styles.textDisabled);
    if (textStyle) baseStyles.push(textStyle);
    return baseStyles;
  };

  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={disabled || loading}
      activeOpacity={0.7}
      style={getButtonStyles()}
    >
      {loading ? (
        <ActivityIndicator color="#fff" size="small" />
      ) : (
        <View style={styles.buttonContent}>
          {leftIcon && <View style={styles.leftIcon}>{leftIcon}</View>}
          <Text style={getTextStyles()}>{title}</Text>
          {rightIcon && <View style={styles.rightIcon}>{rightIcon}</View>}
        </View>
      )}
    </TouchableOpacity>
  );
};

// MARK: - Card Component with Children Composition
interface CardProps {
  title?: string;
  subtitle?: string;
  footer?: React.ReactNode;
  children: React.ReactNode;
  style?: ViewStyle;
  onPress?: () => void;
}

const Card: React.FC<CardProps> = ({
  title,
  subtitle,
  footer,
  children,
  style,
  onPress
}) => {
  const Content = () => (
    <View style={[styles.card, style]}>
      {(title || subtitle) && (
        <View style={styles.cardHeader}>
          {title && <Text style={styles.cardTitle}>{title}</Text>}
          {subtitle && <Text style={styles.cardSubtitle}>{subtitle}</Text>}
        </View>
      )}
      
      <View style={styles.cardContent}>
        {children}
      </View>
      
      {footer && (
        <View style={styles.cardFooter}>
          {footer}
        </View>
      )}
    </View>
  );

  if (onPress) {
    return (
      <TouchableOpacity onPress={onPress} activeOpacity={0.8}>
        <Content />
      </TouchableOpacity>
    );
  }
  
  return <Content />;
};

// MARK: - List Item Component (optimized for FlatList)
interface ListItemProps {
  id: string;
  title: string;
  description?: string;
  avatar?: React.ReactNode;
  onPress?: (id: string) => void;
  rightElement?: React.ReactNode;
}

const ListItem = React.memo(({ id, title, description, avatar, onPress, rightElement }: ListItemProps) => {
  const handlePress = () => {
    onPress?.(id);
  };

  return (
    <TouchableOpacity onPress={handlePress} style={styles.listItem}>
      {avatar && <View style={styles.listItemAvatar}>{avatar}</View>}
      
      <View style={styles.listItemContent}>
        <Text style={styles.listItemTitle}>{title}</Text>
        {description && (
          <Text style={styles.listItemDescription} numberOfLines={2}>
            {description}
          </Text>
        )}
      </View>
      
      {rightElement && (
        <View style={styles.listItemRight}>{rightElement}</View>
      )}
    </TouchableOpacity>
  );
});

// MARK: - Usage Example
const CustomComponentsExample = () => {
  return (
    <ScrollView style={styles.container} contentContainerStyle={styles.content}>
      <CustomButton
        title="Primary Button"
        onPress={() => console.log('Pressed')}
        variant="primary"
        size="large"
      />
      
      <CustomButton
        title="Loading"
        onPress={() => {}}
        loading={true}
      />
      
      <CustomButton
        title="Disabled"
        onPress={() => {}}
        disabled={true}
      />
      
      <Card
        title="Card Title"
        subtitle="Card subtitle goes here"
        footer={<CustomButton title="Action" onPress={() => {}} size="small" />}
        onPress={() => console.log('Card pressed')}
      >
        <Text>This is the card content. Children can be any React Native component.</Text>
      </Card>
      
      <ListItem
        id="1"
        title="List Item Title"
        description="This is a longer description that will be truncated after two lines if it exceeds the maximum allowed space."
        onPress={(id) => console.log(`Pressed item ${id}`)}
        rightElement={<CustomButton title="Action" onPress={() => {}} size="small" />}
      />
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5'
  },
  content: {
    padding: 16,
    gap: 12
  },
  // Button styles
  button: {
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center'
  },
  buttonContent: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center'
  },
  primary: { backgroundColor: '#007AFF' },
  secondary: { backgroundColor: '#5856D6' },
  danger: { backgroundColor: '#FF3B30' },
  small: { paddingHorizontal: 12, paddingVertical: 6 },
  medium: { paddingHorizontal: 16, paddingVertical: 10 },
  large: { paddingHorizontal: 24, paddingVertical: 14 },
  disabled: { backgroundColor: '#c7c7c7' },
  text: { color: '#fff', fontWeight: '600' },
  textSmall: { fontSize: 12 },
  textLarge: { fontSize: 18 },
  textDisabled: { color: '#8e8e8e' },
  leftIcon: { marginRight: 8 },
  rightIcon: { marginLeft: 8 },
  // Card styles
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    overflow: 'hidden',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3
  },
  cardHeader: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0'
  },
  cardTitle: {
    fontSize: 18,
    fontWeight: 'bold'
  },
  cardSubtitle: {
    fontSize: 14,
    color: '#666',
    marginTop: 4
  },
  cardContent: {
    padding: 16
  },
  cardFooter: {
    padding: 12,
    borderTopWidth: 1,
    borderTopColor: '#e0e0e0',
    alignItems: 'flex-end'
  },
  // List item styles
  listItem: {
    flexDirection: 'row',
    backgroundColor: '#fff',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center'
  },
  listItemAvatar: {
    marginRight: 12
  },
  listItemContent: {
    flex: 1
  },
  listItemTitle: {
    fontSize: 16,
    fontWeight: '600'
  },
  listItemDescription: {
    fontSize: 14,
    color: '#666',
    marginTop: 4
  },
  listItemRight: {
    marginLeft: 12
  }
});

export { CustomButton, Card, ListItem, CustomComponentsExample };

  1. Performance Optimization for Components

Key optimization techniques for React Native components:

  1. useMemo & useCallback – Memoize expensive calculations and functions
  2. React.memo – Prevent unnecessary re-renders of pure components
  3. FlatList optimizations – initialNumToRender, maxToRenderPerBatch, windowSize
  4. Avoid inline functions in render (creates new references)
  5. Use InteractionManager for post-animation tasks
  6. Image optimization – Proper dimensions, caching, lazy loading
  7. Avoid anonymous objects in style props (creates new style objects on each render)
React JSXRead-only
1
import React, { useState, useCallback, useMemo, memo } from 'react';
import {
  View,
  Text,
  FlatList,
  TouchableOpacity,
  StyleSheet,
  InteractionManager
} from 'react-native';

// MARK: - Optimized Component with React.memo
const OptimizedListItem = memo(({ item, onPress, isSelected }) => {
  // Log to check re-renders (remove in production)
  console.log(`Rendering item ${item.id}`);
  
  return (
    <TouchableOpacity
      onPress={() => onPress(item.id)}
      style={[styles.item, isSelected && styles.selectedItem]}
    >
      <Text style={styles.itemTitle}>{item.title}</Text>
      <Text style={styles.itemSubtitle}>{item.subtitle}</Text>
    </TouchableOpacity>
  );
});

// MARK: - Parent Component with Optimizations
const OptimizedList = () => {
  const [items, setItems] = useState(
    Array.from({ length: 1000 }).map((_, i) => ({
      id: i,
      title: `Item ${i}`,
      subtitle: `Subtitle for item ${i}`
    }))
  );
  const [selectedId, setSelectedId] = useState(null);
  const [isReady, setIsReady] = useState(false);

  // useCallback prevents function recreation on each render
  const handlePress = useCallback((id) => {
    setSelectedId(id);
    console.log(`Selected item ${id}`);
  }, []);

  // useMemo for expensive computations
  const expensiveComputation = useMemo(() => {
    console.log('Computing expensive value...');
    return items.length * 2;
  }, [items.length]);

  // Defer non-critical work after animations
  React.useEffect(() => {
    InteractionManager.runAfterInteractions(() => {
      // Load analytics, pre-fetch images, etc.
      setIsReady(true);
    });
  }, []);

  // Optimized renderItem function (defined outside FlatList or useCallback)
  const renderItem = useCallback(({ item }) => (
    <OptimizedListItem
      item={item}
      onPress={handlePress}
      isSelected={selectedId === item.id}
    />
  ), [selectedId, handlePress]);

  // Key extractor (stable, unique)
  const keyExtractor = useCallback((item) => item.id.toString(), []);

  // Get item layout for performance
  const getItemLayout = useCallback((data, index) => ({
    length: 70, // Item height
    offset: 70 * index,
    index
  }), []);

  if (!isReady) {
    return (
      <View style={styles.loadingContainer}>
        <Text>Loading...</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <Text style={styles.header}>Optimized FlatList</Text>
      <Text>Expensive value: {expensiveComputation}</Text>
      
      <FlatList
        data={items}
        renderItem={renderItem}
        keyExtractor={keyExtractor}
        getItemLayout={getItemLayout}
        initialNumToRender={10}
        maxToRenderPerBatch={10}
        windowSize={21}
        removeClippedSubviews={true}
        updateCellsBatchingPeriod={50}
        // Disable virtualization for small lists
        // virtualized={items.length > 100}
      />
    </View>
  );
};

// MARK: - Avoid Inline Styles
// BAD: Creates new style object on each render
// <View style={{ padding: 10, margin: 5 }} />

// GOOD: Define styles outside component
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 16
  },
  item: {
    padding: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0'
  },
  selectedItem: {
    backgroundColor: '#e3f2fd'
  },
  itemTitle: {
    fontSize: 16,
    fontWeight: '600'
  },
  itemSubtitle: {
    fontSize: 12,
    color: '#666',
    marginTop: 4
  }
});

export default OptimizedList;

  1. Platform-Specific Components

Use Platform.select() and platform-specific file extensions (.ios.js / .android.js) to customize components per platform.

React JSXRead-only
1
import React from 'react';
import {
  View,
  Text,
  StyleSheet,
  Platform,
  ActionSheetIOS,
  Alert,
  ToastAndroid,
  Switch,
  ScrollView
} from 'react-native';

const PlatformSpecificComponents = () => {
  // Platform-specific styles
  const styles = StyleSheet.create({
    container: {
      flex: 1,
      padding: 16,
      // Platform-specific padding
      paddingTop: Platform.OS === 'ios' ? 40 : 20,
      // Platform-specific shadow
      ...Platform.select({
        ios: {
          shadowColor: '#000',
          shadowOffset: { width: 0, height: 2 },
          shadowOpacity: 0.1
        },
        android: {
          elevation: 4
        }
      })
    },
    button: {
      ...Platform.select({
        ios: {
          backgroundColor: '#007AFF',
          borderRadius: 8
        },
        android: {
          backgroundColor: '#6200EE',
          borderRadius: 4,
          elevation: 2
        }
      }),
      padding: 12,
      alignItems: 'center',
      marginVertical: 8
    },
    buttonText: {
      color: '#fff',
      fontWeight: '600'
    }
  });

  // Platform-specific alerts
  const showAlert = () => {
    if (Platform.OS === 'ios') {
      ActionSheetIOS.showActionSheetWithOptions(
        {
          options: ['Cancel', 'Option 1', 'Option 2'],
          cancelButtonIndex: 0,
          destructiveButtonIndex: 2,
          title: 'Choose an option',
          message: 'Select from the list below'
        },
        (buttonIndex) => {
          console.log('Selected index:', buttonIndex);
        }
      );
    } else {
      Alert.alert(
        'Choose an option',
        'Select from the list below',
        [
          { text: 'Cancel', style: 'cancel' },
          { text: 'Option 1', onPress: () => console.log('Option 1') },
          { text: 'Option 2', style: 'destructive', onPress: () => console.log('Option 2') }
        ]
      );
    }
  };

  // Platform-specific toast
  const showToast = () => {
    if (Platform.OS === 'android') {
      ToastAndroid.show('This is a toast message', ToastAndroid.SHORT);
    } else {
      Alert.alert('Notification', 'iOS uses alerts instead of toasts');
    }
  };

  // Platform-specific component rendering
  const renderSwitch = () => {
    if (Platform.OS === 'ios') {
      return (
        <View style={styles.button}>
          <Text style={styles.buttonText}>iOS Native Switch Below</Text>
          <Switch value={true} onValueChange={() => {}} />
        </View>
      );
    } else {
      return (
        <View style={styles.button}>
          <Text style={styles.buttonText}>Android Switch</Text>
          <Switch value={true} onValueChange={() => {}} />
        </View>
      );
    }
  };

  return (
    <ScrollView style={styles.container}>
      <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 16 }}>
        Platform: {Platform.OS}
      </Text>
      <Text style={{ marginBottom: 16 }}>
        Version: {Platform.Version}
      </Text>
      
      <TouchableOpacity style={styles.button} onPress={showAlert}>
        <Text style={styles.buttonText}>Show Alert/ActionSheet</Text>
      </TouchableOpacity>
      
      <TouchableOpacity style={styles.button} onPress={showToast}>
        <Text style={styles.buttonText}>Show Toast/Notification</Text>
      </TouchableOpacity>
      
      {renderSwitch()}
      
      <Text style={{ marginTop: 20, fontSize: 12, color: '#666' }}>
        Tip: Use .ios.js and .android.js file extensions for complete platform separation
      </Text>
    </ScrollView>
  );
};

// Platform-specific file structure:
// Button.ios.js – iOS implementation
// Button.android.js – Android implementation
// Then import: import Button from './Button';

export default PlatformSpecificComponents;

Test Your Knowledge

Q1
of 4

Which component is recommended for large, scrollable lists with many items?

A
ScrollView
B
FlatList
C
View
D
SectionList
Q2
of 4

Which touchable component is the modern recommended approach?

A
TouchableOpacity
B
TouchableHighlight
C
Pressable
D
Button
Q3
of 4

What must all text in React Native be wrapped in?

A
<View>
B
<Text>
C
<div>
D
<span>
Q4
of 4

Which optimization technique prevents unnecessary re-renders of functional components?

A
useEffect
B
React.memo
C
useState
D
createRef

Frequently Asked Questions

When should I use ScrollView vs FlatList?

Use ScrollView when you have a small, fixed number of items (under 20) that all need to render at once. Use FlatList for any dynamic or large list (20+ items). FlatList renders only visible items, saving memory and improving performance significantly.

Why is Pressable preferred over TouchableOpacity?

Pressable is more flexible, accessible, and performant. It provides granular control over press states (pressed, hovered, focused), supports Android ripple effects natively, and has better VoiceOver/accessibility support. TouchableOpacity is still supported but Pressable is the modern standard.

How do I optimize FlatList performance for 1000+ items?

Use these optimizations: initialNumToRender={10}, maxToRenderPerBatch={10}, windowSize={21}, removeClippedSubviews={true}, memoize renderItem with useCallback, provide getItemLayout, and avoid inline functions in props.

What's the difference between StyleSheet.create and inline styles?

StyleSheet.create validates styles at compile time, creates optimized native style objects (sent once to native side), and improves performance. Inline style objects are recreated on every render, causing unnecessary work. Always use StyleSheet.create for static styles.

Previous

react native project structure

Next

react native styling

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.