WHAT: React Native component patterns with variant-based architecture and repository pattern. WHEN: creating components, implementing variants, separating data logic from UI. KEYWORDS: component, variant, discriminated union, repository, FlatList, useCallback, memo, JSX, props.
Pro scans all 2 files and shows the line behind each finding
Scanned 9/27/2026
npx -y skills add David-Li0406/meta-skill-evloving --skill component-patterns-3 --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Component Patterns 3?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/david-li0406-component-patterns-3)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: component-patterns
description: "WHAT: React Native component patterns with variant-based architecture and repository pattern. WHEN: creating components, implementing variants, separating data logic from UI. KEYWORDS: component, variant, discriminated union, repository, FlatList, useCallback, memo, JSX, props."
tracked_repositories:
- url: https://github.com/facebook/react-native-website
type: external
paths:
- docs/**/*.md
last_synced: 2024-01-01T00:00:00Z
sync_frequency: monthly
---
# Component Patterns for React Native
## Core Principles
**Use TypeScript strict mode, arrow functions, and named exports only.** Structure components with variant-based architecture using discriminated unions and repository pattern for data logic.
**Why**: Consistent patterns improve code maintainability, type safety catches bugs at compile time, and established architectures make components predictable and testable.
## When to Use This Skill
Use these patterns when:
- Creating any React Native component
- Implementing variant-based components (different modes/states)
- Separating data logic from UI rendering
- Formatting JSX for readability
- Rendering conditional UI elements
- Displaying lists of data efficiently
- Handling user interactions and events
- Optimizing component performance
## Component Structure
### Basic Component Pattern
```typescript
import { View, TouchableOpacity } from 'react-native';
import { Text, useZestStyles } from '@zest/react-native';
import type { ComponentProps } from './types';
import { stylesConfig } from './styles';
export const ExampleComponent = ({
title,
onPress,
variant = 'primary',
}: ComponentProps) => {
const styles = useZestStyles(stylesConfig);
return (
<View style={styles.container}>
<Text type="headline-lg">{title}</Text>
<TouchableOpacity style={styles.button} onPress={onPress}>
<Text type="body-md-bold">Press Me</Text>
</TouchableOpacity>
</View>
);
};
```
**Structure order:**
1. Imports (React/RN → External → Zest → Types → Internal → Relative)
2. Props destructuring with defaults
3. Hooks (styles, state, effects)
4. Event handlers
5. Helper functions
6. Render logic
**Why**: Consistent structure makes components easy to navigate and understand.
## Variant-Based Component Architecture
Use discriminated unions for components with different modes or states:
```typescript
// types.ts
export type EditCardVariant = {
variant: 'EDIT';
product: Product;
quantity: number;
maxQuantity: number;
onQuantityChange: (qty: number) => void;
};
export type SkippedCardVariant = {
variant: 'SKIPPED';
product: Product;
reason: string;
onUnskip: () => void;
};
export type LoadingCardVariant = {
variant: 'LOADING';
productId: string;
};
export type CardVariant =
| EditCardVariant
| SkippedCardVariant
| LoadingCardVariant;
export type ProductCardProps = {
data: CardVariant;
brandCategory: string;
};
```
**Component implementation with exhaustive checking:**
```typescript
import { assertNever } from '@libs/utils';
export const ProductCard = ({ data, brandCategory }: ProductCardProps) => {
const renderCardVariant = () => {
switch (data.variant) {
case 'EDIT':
return (
<EditCard
product={data.product}
quantity={data.quantity}
maxQuantity={data.maxQuantity}
onQuantityChange={data.onQuantityChange}
brandCategory={brandCategory}
/>
);
case 'SKIPPED':
return (
<SkippedCard
product={data.product}
reason={data.reason}
onUnskip={data.onUnskip}
brandCategory={brandCategory}
/>
);
case 'LOADING':
return <LoadingCard productId={data.productId} />;
default:
return assertNever(data);
}
};
return <View>{renderCardVariant()}</View>;
};
```
**Why**:
- **Type safety**: TypeScript ensures all variants are handled
- **Exhaustive checking**: `assertNever` catches missing cases at compile time
- **Maintainability**: Adding new variants requires updating all switch statements
- **Clear intent**: Each variant has specific, type-safe props
**Usage**: 50+ instances in YourCompany codebase for cards, screens, and modal states.
## Repository Pattern for Data Logic
Separate data fetching and transformation from UI components:
```typescript
// hooks/useDeliveryFeedItemRepository.tsx
import { useQuery } from '@tanstack/react-query';
export const useDeliveryFeedItemRepository = ({ delivery }: { delivery: Delivery }) => {
const { data: benefitUnits } = useBenefitUnitsQuery(delivery.id);
const { data: addOns } = useAddOnsQuery(delivery.id);
// Computed properties
const hasBenefitUnits = benefitUnits && benefitUnits.length > 0;
const isSkippedStatus = delivery.state === 'SKIPPED';
const deliverySizeMeals = delivery.config?.numberOfRecipes || 0;
// Complex data transformation
const meals = delivery.items
.filter((item) => item.type === 'MEAL')
.map((item) => ({
id: item.id,
name: item.product.name,
imageUrl: item.product.imageUrl,
servings: item.quantity,
}));
const totalItems = meals.length + (addOns?.length || 0);
return {
hasBenefitUnits,
isSkippedStatus,
deliverySizeMeals,
meals,
addOns,
totalItems,
};
};
```
**Component usage:**
```typescript
export const DeliveryFeedItem = ({ delivery }: Props) => {
const repository = useDeliveryFeedItemRepository({ delivery });
const styles = useZestStyles(stylesConfig);
return (
<View style={styles.container}>
<Text type="headline-md">
{repository.totalItems} items
</Text>
{repository.hasBenefitUnits && <BenefitBadge />}
{repository.isSkippedStatus && <SkippedBanner />}
<MealList meals={repository.meals} />
</View>
);
};
```
**Why**:
- **Separation of concerns**: Data logic separate from UI
- **Reusability**: Multiple components can use same repository
- **Testability**: Repository hooks can be tested independently
- **Maintainability**: Data changes don't affect UI structure
**Usage**: 30+ repository hooks in YourCompany codebase for complex data operations.
## JSX Formatting
### Multiple Props
Put each prop on a separate line:
```typescript
// ✅ Good - Readable
<Button
variant="primary"
appearance="brand"
size="lg"
disabled={isLoading}
accessibilityLabel="Submit form"
onPress={handleSubmit}
>
Submit
</Button>
// ❌ Bad - Hard to read
<Button variant="primary" appearance="brand" size="lg" disabled={isLoading} onPress={handleSubmit}>Submit</Button>
```
### Single Prop
Keep on same line:
```typescript
// ✅ Good
<Text type="headline-lg">Title</Text>
<Icon icon="CloseOutline24" altText="Close" />
// ❌ Bad - Unnecessary line breaks for single prop
<Text
type="headline-lg"
>
Title
</Text>
```
### Boolean Props
Always explicit:
```typescript
// ✅ Good - Explicit
<Button enabled={true} loading={false} />
<Input required={true} disabled={false} />
// ❌ Bad - Implicit (avoid)
<Button enabled loading />
```
**Why**: Explicit formatting improves readability and prevents ambiguity.
## Conditional Rendering
### Ternary for Inline Conditions
```typescript
// ✅ Good - Clear ternary
{isLoading ? <LoadingSpinner /> : <Content data={data} />}
// ✅ Good - With null
{hasData ? <DataDisplay data={data} /> : null}
```
### Logical AND for Show/Hide
```typescript
// ✅ Good - Simple show/hide
{hasData && <DataDisplay data={data} />}
{isError && <ErrorMessage error={error} />}
{items.length > 0 && <ItemList items={items} />}
```
### Early Returns for Complex Conditions
```typescript
export const UserProfile = ({ user }: Props) => {
// ✅ Good - Early returns
if (!user) {
return <EmptyState message="No user found" />;
}
if (user.isBlocked) {
return <BlockedUserMessage />;
}
if (!user.profile) {
return <IncompleteProfileWarning />;
}
// Main render
return (
<View>
<ProfileHeader user={user} />
<ProfileContent profile={user.profile} />
</View>
);
};
```
### Avoid Nested Ternaries
```typescript
// ❌ Bad - Nested ternaries
{isLoading ? (
<LoadingSpinner />
) : isError ? (
<ErrorMessage />
) : hasData ? (
<Content />
) : (
<EmptyState />
)}
// ✅ Good - Extract to function
const renderContent = () => {
if (isLoading) return <LoadingSpinner />;
if (isError) return <ErrorMessage />;
if (hasData) return <Content />;
return <EmptyState />;
};
return <View>{renderContent()}</View>;
```
**Why**: Simple patterns are easier to read, maintain, and debug.
## List Rendering
### Use FlatList for Performance
```typescript
import { FlatList } from 'react-native';
const ITEM_HEIGHT = 100;
export const RecipeList = ({ recipes }: Props) => {
return (
<FlatList
data={recipes}
keyExtractor={(item) => item.id}
renderItem={({ item, index }) => (
<RecipeCard
recipe={item}
index={index}
testID={`recipe-card-${item.id}`}
/>
)}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={5}
/>
);
};
```
**Performance optimizations:**
- `keyExtractor`: Stable unique keys (use IDs, never index)
- `getItemLayout`: Skip height measurements for fixed-height items
- `initialNumToRender`: Render minimum items on mount
- `maxToRenderPerBatch`: Render fewer items per scroll batch
- `windowSize`: Keep smaller render window
**Why**: FlatList virtualizes items, only rendering what's visible for optimal performance with large lists.
### Never Use Index as Key
```typescript
// ❌ Bad - Index as key
{items.map((item, index) => (
<ItemCard key={index} item={item} />
))}
// ✅ Good - Unique ID as key
{items.map((item) => (
<ItemCard key={item.id} item={item} />
))}
// ✅ Good - Composite key if no ID
{items.map((item) => (
<ItemCard key={`${item.name}-${item.timestamp}`} item={item} />
))}
```
**Why**: Index keys cause React to incorrectly reuse components when list order changes, leading to bugs with component state and animations.
### Memoize renderItem
```typescript
import { useCallback } from 'react';
export const RecipeList = ({ recipes, onSelect }: Props) => {
const renderItem = useCallback(
({ item }: { item: Recipe }) => (
<RecipeCard recipe={item} onSelect={onSelect} />
),
[onSelect]
);
return (
<FlatList
data={recipes}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
);
};
```
**Why**: Memoized `renderItem` prevents FlatList from re-rendering all items when parent re-renders.
## Event Handling
### useCallback for Event Handlers
```typescript
import { useCallback } from 'react';
export const ProductCard = ({ product, onSelect }: Props) => {
const handlePress = useCallback(() => {
onSelect(product.id);
}, [onSelect, product.id]);
const handleAddToCart = useCallback(() => {
addToCart(product);
trackEvent('add_to_cart', { productId: product.id });
}, [product]);
return (
<View>
<TouchableOpacity onPress={handlePress}>
<ProductImage source={product.imageUrl} />
</TouchableOpacity>
<Button onPress={handleAddToCart}>Add to Cart</Button>
</View>
);
};
```
**Why**: `useCallback` prevents creating new function instances on every render, avoiding unnecessary child re-renders.
### Inline Handlers for Simple Cases
```typescript
// ✅ Good - Simple inline handler
<Button onPress={() => setIsVisible(false)}>Close</Button>
<TouchableOpacity onPress={() => navigation.goBack()}>
<Icon icon="ChevronLeftOutline24" altText="Back" />
</TouchableOpacity>
// ❌ Bad - Complex inline handler
<Button onPress={() => {
validateInput();
processData();
updateState();
trackEvent();
}}>
Process
</Button>
// ✅ Good - Extract complex handler
const handleProcess = useCallback(() => {
validateInput();
processData();
updateState();
trackEvent();
}, [validateInput, processData, updateState, trackEvent]);
<Button onPress={handleProcess}>Process</Button>
```
**Rule**: Use inline handlers for single-line actions, `useCallback` for multi-line or repeated handlers.
### Action Handlers with Discriminated Unions
```typescript
import { assertNever } from '@libs/utils';
type ProductAction =
| { type: 'ADD'; productId: string; isAlcoholic?: boolean }
| { type: 'DECREASE_QUANTITY'; productId: string }
| { type: 'INCREASE_QUANTITY'; productId: string }
| { type: 'CARD_CLICKED'; productId: string; position?: number };
export const createHandleAction = ({
onAddProduct,
onDecreaseProduct,
onIncreaseProduct,
navigateToProductDetails,
}: Handlers) => {
return (action: ProductAction) => {
switch (action.type) {
case 'ADD':
onAddProduct(action.productId, action.isAlcoholic);
break;
case 'DECREASE_QUANTITY':
onDecreaseProduct(action.productId);
break;
case 'INCREASE_QUANTITY':
onIncreaseProduct(action.productId);
break;
case 'CARD_CLICKED':
navigateToProductDetails(action.productId, action.position);
break;
default:
assertNever(action);
}
};
};
```
**Why**: Action handlers with discriminated unions provide type-safe event handling with exhaustive case checking.
## Performance Patterns
### Component Memoization
```typescript
import { memo } from 'react';
// ✅ Memo for expensive components
export const RecipeCard = memo(({ recipe, onSelect }: RecipeCardProps) => {
return (
<View>
<Image source={{ uri: recipe.imageUrl }} />
<Text>{recipe.name}</Text>
<Button onPress={() => onSelect(recipe.id)}>Select</Button>
</View>
);
});
RecipeCard.displayName = 'RecipeCard';
```
**When to use memo:**
- Component in a large list
- Component with expensive rendering logic
- Component receives same props frequently
- Parent re-renders often but props rarely change
**Why**: `memo` prevents re-renders when props haven't changed, improving performance for lists and frequently updated parents.
### useMemo for Expensive Calculations
```typescript
import { useMemo } from 'react';
export const RecipeList = ({ recipes, filters }: Props) => {
const filteredRecipes = useMemo(() => {
return recipes
.filter((recipe) => matchesFilters(recipe, filters))
.sort((a, b) => a.rating - b.rating)
.slice(0, 20);
}, [recipes, filters]);
return (
<FlatList
data={filteredRecipes}
renderItem={renderRecipe}
/>
);
};
```
**Why**: `useMemo` caches expensive calculations, only recomputing when dependencies change.
### Cleanup & Memory Management
```typescript
import { useEffect } from 'react';
export const RealTimeUpdates = () => {
useEffect(() => {
// ✅ Good - Setup subscription
const subscription = subscribeToUpdates((data) => {
handleUpdate(data);
});
// ✅ Good - Cleanup function
return () => {
subscription.unsubscribe();
};
}, []);
useEffect(() => {
// ✅ Good - Setup interval
const interval = setInterval(() => {
fetchLatestData();
}, 30000);
// ✅ Good - Cleanup interval
return () => {
clearInterval(interval);
};
}, []);
return <View>{/* ... */}</View>;
};
```
**Why**: Proper cleanup prevents memory leaks, event listener buildup, and subscription issues.
## Common Mistakes to Avoid
❌ **Don't use HTML elements**:
```typescript
// ❌ Bad - HTML elements don't work in React Native
<div>Content</div>
<span>Text</span>
<img src="..." />
// ✅ Good - React Native components
<View>Content</View>
<Text>Text</Text>
<Image source={{ uri: '...' }} />
```
❌ **Don't use default exports**:
```typescript
// ❌ Bad
export default UserProfile;
// ✅ Good
export const UserProfile = ({ user }: Props) => {};
```
❌ **Don't use function declarations**:
```typescript
// ❌ Bad
function calculateTotal(items: Item[]) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// ✅ Good
const calculateTotal = (items: Item[]): number => {
return items.reduce((sum, item) => sum + item.price, 0);
};
```
❌ **Don't use var**:
```typescript
// ❌ Bad
var count = 0;
var config = {};
// ✅ Good
let count = 0;
const config = {};
```
❌ **Don't create new objects/functions in render**:
```typescript
// ❌ Bad - Creates new style object every render
<View style={{ padding: 16, backgroundColor: 'white' }} />
// ✅ Good - Use extracted styles
const styles = useZestStyles(stylesConfig);
<View style={styles.container} />
// ❌ Bad - Creates new function every render
<Button onPress={() => handlePress(item.id)} />
// ✅ Good - Use useCallback
const handlePress = useCallback(() => {
onSelect(item.id);
}, [item.id, onSelect]);
<Button onPress={handlePress} />
```
✅ **Do use TypeScript strict mode**:
```typescript
// ✅ Good - Explicit types
const processUser = (user: User): ProcessedUser => {
return { ...user, processed: true };
};
// ❌ Bad - any types
const processUser = (user: any): any => {
return { ...user, processed: true };
};
```
✅ **Do use repository pattern for complex data**:
```typescript
// ✅ Good - Separated concerns
const repository = useDataRepository({ id });
return <Component data={repository.transformedData} />;
// ❌ Bad - Data logic in component
const Component = ({ id }) => {
const { data } = useQuery(...);
const transformed = complexTransformation(data); // Move to repository
return <View>{transformed}</View>;
};
```
✅ **Do use discriminated unions for variants**:
```typescript
// ✅ Good - Type-safe variants
type CardVariant =
| { variant: 'EDIT'; quantity: number }
| { variant: 'LOADING' };
const renderCard = (data: CardVariant) => {
switch (data.variant) {
case 'EDIT': return <EditCard quantity={data.quantity} />;
case 'LOADING': return <LoadingCard />;
default: return assertNever(data);
}
};
```
## Quick Reference
**Component Structure:**
1. Imports (React/RN → External → Zest → Types → Internal → Relative)
2. Props destructuring with defaults
3. Hooks
4. Event handlers
5. Helper functions
6. Render logic
**Variant Architecture:**
- Use discriminated unions for different component modes
- Use `assertNever` for exhaustive case checking
- TypeScript ensures all variants are handled
**Repository Pattern:**
- Separate data fetching/transformation from UI
- Create custom hooks for complex data operations
- Return transformed, ready-to-use data
**JSX Formatting:**
- Multiple props: Each on separate line
- Single prop: Same line
- Boolean props: Always explicit
**Conditional Rendering:**
- Ternary: Inline conditions
- Logical AND: Show/hide
- Early returns: Complex conditions
- Avoid: Nested ternaries
**List Rendering:**
- Use `FlatList` for performance
- Unique ID as `key`, never index
- Memoize `renderItem`
- Use `getItemLayout` for fixed heights
**Event Handling:**
- `useCallback` for complex/repeated handlers
- Inline for simple single-line actions
- Action handlers with discriminated unions
**Performance:**
- `memo` for expensive components
- `useMemo` for expensive calculations
- Always cleanup in `useEffect`
**Key Rules:**
- ✅ Arrow functions only
- ✅ Named exports only
- ✅ TypeScript strict mode
- ✅ const/let, never var
- ❌ Never use HTML elements
- ❌ Never use default exports
- ❌ Never create objects/functions in render
For production examples, see [references/examples.md](references/examples.md).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!