home / skills / thebushidocollective / han / react-native-components

This skill helps you build reusable React Native UI components with core and custom patterns, improving consistency and performance.

npx playbooks add skill thebushidocollective/han --skill react-native-components

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
11.2 KB
---
name: react-native-components
user-invocable: false
description: Use when building React Native UI components with core components, custom components, and component patterns. Covers View, Text, Image, ScrollView, FlatList, and component composition.
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash
  - Grep
  - Glob
---

# React Native Components

Use this skill when building user interfaces with React Native's core components and creating custom reusable components.

## Key Concepts

### Core Components

React Native provides platform-agnostic components that map to native views:

```tsx
import React from 'react';
import {
  View,
  Text,
  Image,
  ScrollView,
  TextInput,
  TouchableOpacity,
  SafeAreaView,
} from 'react-native';

export default function App() {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <ScrollView>
        <View>
          <Text>Hello, React Native!</Text>
          <Image
            source={{ uri: 'https://example.com/image.jpg' }}
            style={{ width: 200, height: 200 }}
          />
          <TextInput
            placeholder="Enter text"
            style={{ borderWidth: 1, padding: 10 }}
          />
          <TouchableOpacity onPress={() => console.log('Pressed')}>
            <Text>Press Me</Text>
          </TouchableOpacity>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}
```

### View Component

The fundamental building block:

```tsx
import { View } from 'react-native';

function Container({ children }: { children: React.ReactNode }) {
  return (
    <View style={{
      flex: 1,
      padding: 16,
      backgroundColor: '#fff',
    }}>
      {children}
    </View>
  );
}
```

### Text Component

All text must be wrapped in `<Text>`:

```tsx
import { Text } from 'react-native';

function Heading({ children }: { children: string }) {
  return (
    <Text style={{
      fontSize: 24,
      fontWeight: 'bold',
      color: '#333',
    }}>
      {children}
    </Text>
  );
}

function Body({ children }: { children: string }) {
  return (
    <Text style={{
      fontSize: 16,
      lineHeight: 24,
      color: '#666',
    }}>
      {children}
    </Text>
  );
}
```

### Image Component

Display images from various sources:

```tsx
import { Image } from 'react-native';

// Remote image
<Image
  source={{ uri: 'https://example.com/image.jpg' }}
  style={{ width: 200, height: 200 }}
/>

// Local image
<Image
  source={require('./assets/logo.png')}
  style={{ width: 100, height: 100 }}
/>

// With resize mode
<Image
  source={{ uri: 'https://example.com/image.jpg' }}
  style={{ width: 200, height: 200 }}
  resizeMode="cover"
/>
```

## Best Practices

### Use SafeAreaView for iOS Notch

Always use SafeAreaView to handle safe areas:

```tsx
import { SafeAreaView } from 'react-native';

export default function App() {
  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
      {/* Your content */}
    </SafeAreaView>
  );
}
```

### FlatList for Long Lists

Use FlatList instead of ScrollView for performance:

```tsx
import { FlatList, Text, View } from 'react-native';

interface Item {
  id: string;
  title: string;
}

function ItemList({ items }: { items: Item[] }) {
  return (
    <FlatList
      data={items}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={{ padding: 16 }}>
          <Text>{item.title}</Text>
        </View>
      )}
      // Performance optimizations
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      initialNumToRender={10}
      windowSize={10}
    />
  );
}
```

### Touchable Components

Use appropriate touchables for platform:

```tsx
import { TouchableOpacity, TouchableHighlight, Pressable } from 'react-native';

// Modern approach - Pressable (recommended)
<Pressable
  onPress={() => console.log('Pressed')}
  style={({ pressed }) => [
    { padding: 12, backgroundColor: pressed ? '#ddd' : '#fff' }
  ]}
>
  {({ pressed }) => (
    <Text style={{ color: pressed ? '#000' : '#333' }}>Press Me</Text>
  )}
</Pressable>

// TouchableOpacity - simple fade effect
<TouchableOpacity
  onPress={() => console.log('Pressed')}
  activeOpacity={0.7}
>
  <Text>Press Me</Text>
</TouchableOpacity>
```

### Component Composition

Build complex UIs from simple components:

```tsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

interface CardProps {
  title: string;
  subtitle?: string;
  children?: React.ReactNode;
}

function Card({ title, subtitle, children }: CardProps) {
  return (
    <View style={styles.card}>
      <View style={styles.header}>
        <Text style={styles.title}>{title}</Text>
        {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
      </View>
      {children && <View style={styles.content}>{children}</View>}
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',
    borderRadius: 8,
    padding: 16,
    marginVertical: 8,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  header: {
    marginBottom: 12,
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#333',
  },
  subtitle: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
  content: {
    marginTop: 8,
  },
});

export default Card;
```

## Common Patterns

### List with Pull-to-Refresh

```tsx
import React, { useState, useCallback } from 'react';
import { FlatList, RefreshControl, Text, View } from 'react-native';

interface Item {
  id: string;
  title: string;
}

function RefreshableList({ items, onRefresh }: {
  items: Item[];
  onRefresh: () => Promise<void>;
}) {
  const [refreshing, setRefreshing] = useState(false);

  const handleRefresh = useCallback(async () => {
    setRefreshing(true);
    await onRefresh();
    setRefreshing(false);
  }, [onRefresh]);

  return (
    <FlatList
      data={items}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={{ padding: 16 }}>
          <Text>{item.title}</Text>
        </View>
      )}
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
      }
    />
  );
}
```

### Infinite Scroll List

```tsx
import React from 'react';
import { FlatList, ActivityIndicator, View } from 'react-native';

interface Item {
  id: string;
  title: string;
}

function InfiniteList({
  items,
  loading,
  onEndReached
}: {
  items: Item[];
  loading: boolean;
  onEndReached: () => void;
}) {
  return (
    <FlatList
      data={items}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={{ padding: 16 }}>
          <Text>{item.title}</Text>
        </View>
      )}
      onEndReached={onEndReached}
      onEndReachedThreshold={0.5}
      ListFooterComponent={
        loading ? (
          <View style={{ padding: 16 }}>
            <ActivityIndicator size="large" />
          </View>
        ) : null
      }
    />
  );
}
```

### Modal Component

```tsx
import React from 'react';
import {
  Modal,
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
} from 'react-native';

interface CustomModalProps {
  visible: boolean;
  title: string;
  children: React.ReactNode;
  onClose: () => void;
}

function CustomModal({ visible, title, children, onClose }: CustomModalProps) {
  return (
    <Modal
      visible={visible}
      animationType="slide"
      transparent={true}
      onRequestClose={onClose}
    >
      <View style={styles.overlay}>
        <View style={styles.modal}>
          <View style={styles.header}>
            <Text style={styles.title}>{title}</Text>
            <TouchableOpacity onPress={onClose}>
              <Text style={styles.closeButton}>āœ•</Text>
            </TouchableOpacity>
          </View>
          <View style={styles.content}>{children}</View>
        </View>
      </View>
    </Modal>
  );
}

const styles = StyleSheet.create({
  overlay: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modal: {
    width: '80%',
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 20,
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 16,
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
  },
  closeButton: {
    fontSize: 24,
    color: '#666',
  },
  content: {
    marginTop: 8,
  },
});

export default CustomModal;
```

### Form Input Component

```tsx
import React, { useState } from 'react';
import {
  View,
  TextInput,
  Text,
  StyleSheet,
  TextInputProps,
} from 'react-native';

interface FormInputProps extends TextInputProps {
  label: string;
  error?: string;
}

function FormInput({ label, error, ...props }: FormInputProps) {
  const [isFocused, setIsFocused] = useState(false);

  return (
    <View style={styles.container}>
      <Text style={styles.label}>{label}</Text>
      <TextInput
        {...props}
        style={[
          styles.input,
          isFocused && styles.inputFocused,
          error && styles.inputError,
        ]}
        onFocus={() => setIsFocused(true)}
        onBlur={() => setIsFocused(false)}
      />
      {error && <Text style={styles.error}>{error}</Text>}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    marginVertical: 8,
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginBottom: 4,
    color: '#333',
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
  },
  inputFocused: {
    borderColor: '#007AFF',
  },
  inputError: {
    borderColor: '#FF3B30',
  },
  error: {
    color: '#FF3B30',
    fontSize: 12,
    marginTop: 4,
  },
});

export default FormInput;
```

## Anti-Patterns

### Don't Nest ScrollViews

```tsx
// Bad - Nested ScrollViews cause issues
<ScrollView>
  <ScrollView>
    <Text>Content</Text>
  </ScrollView>
</ScrollView>

// Good - Use single ScrollView
<ScrollView>
  <View>
    <Text>Content</Text>
  </View>
</ScrollView>
```

### Don't Use Inline Styles for Static Values

```tsx
// Bad - Creates new object on every render
<View style={{ padding: 16, backgroundColor: '#fff' }}>
  <Text>Content</Text>
</View>

// Good - Use StyleSheet
const styles = StyleSheet.create({
  container: {
    padding: 16,
    backgroundColor: '#fff',
  },
});

<View style={styles.container}>
  <Text>Content</Text>
</View>
```

### Don't Forget to Set keyExtractor

```tsx
// Bad - May cause rendering issues
<FlatList
  data={items}
  renderItem={({ item }) => <Text>{item.title}</Text>}
/>

// Good - Provide unique key
<FlatList
  data={items}
  keyExtractor={(item) => item.id}
  renderItem={({ item }) => <Text>{item.title}</Text>}
/>
```

### Don't Use Index as Key

```tsx
// Bad - Index as key causes issues with reordering
<FlatList
  data={items}
  keyExtractor={(item, index) => index.toString()}
  renderItem={({ item }) => <Text>{item.title}</Text>}
/>

// Good - Use unique identifier
<FlatList
  data={items}
  keyExtractor={(item) => item.id}
  renderItem={({ item }) => <Text>{item.title}</Text>}
/>
```

## Related Skills

- **react-native-styling**: Styling components with StyleSheet
- **react-native-navigation**: Navigation between screens
- **react-native-performance**: Optimizing component performance

Overview

This skill helps you build React Native UI components using core building blocks and common component patterns. It covers View, Text, Image, ScrollView, FlatList, touchables, form inputs, modals, and composition techniques. The goal is reusable, performant, and cross-platform UI code in TypeScript.

How this skill works

The skill inspects component usage patterns and provides concrete examples for core components (View, Text, Image) and higher-level patterns (lists, modals, forms). It explains performance-focused choices like FlatList and windowing, safe-area handling, and proper touchable components. Examples include code snippets, prop suggestions, and common anti-patterns to avoid.

When to use it

  • Creating screen layouts with platform-agnostic primitives
  • Building lists, infinite scroll, or pull-to-refresh UIs
  • Composing reusable components (cards, modals, form inputs)
  • Handling images from remote or local sources
  • Optimizing rendering and touch interactions for mobile

Best practices

  • Wrap textual content in <Text> and keep styles in StyleSheet to avoid inline allocations
  • Use SafeAreaView for safe-area spacing on iOS devices with notches
  • Prefer FlatList for long lists; tune initialNumToRender, windowSize, and removeClippedSubviews
  • Use Pressable for modern touch feedback; fall back to TouchableOpacity/TouchableHighlight when simpler effects are needed
  • Avoid nested ScrollViews and never use array index as FlatList keys

Example use cases

  • A feed screen using FlatList with pull-to-refresh and infinite scroll
  • A reusable Card component that accepts title, subtitle, and children for composition
  • An image gallery that loads remote images with proper resizeMode and placeholders
  • A form screen with labeled inputs, focus styles, and validation error UI
  • A modal dialog component with overlay, header, and close action

FAQ

When should I choose ScrollView vs FlatList?

Use ScrollView for small, static content. Use FlatList for long or dynamic lists for better memory and rendering performance.

How do I handle safe areas on iOS?

Wrap top-level screens in SafeAreaView to respect notches and home indicators. Combine with padding or container styles as needed.