
Introduction
Reusable components are the building blocks of scalable and maintainable React Native apps. If you find yourself copy-pasting UI code between screens, it’s time to refactor. Creating clean, reusable components not only saves time but also improves consistency and reduces bugs. In this guide, you will learn best practices for building reusable components in React Native, with comprehensive code examples and patterns that scale with your application. These techniques will help you build a component library that accelerates development across your entire team.
Why Reusable Components Matter
Building reusable components provides significant benefits as your application grows. A cleaner codebase becomes easier to navigate and understand. Components are easier to test in isolation and maintain over time. Consistent design across screens improves user experience. Faster development and prototyping accelerate feature delivery. Bug fixes in one component propagate everywhere it’s used.
Teams that invest in reusable components report spending less time on UI development and more time on business logic and features that differentiate their applications.
Atomic Design Principles
Follow a structured hierarchy when breaking your UI into components. This approach provides clear guidelines for component granularity.
Atoms are the smallest building blocks: buttons, inputs, text, icons, and avatars. Molecules combine atoms into functional units: input with label, card with image, button with icon. Organisms are complex components built from molecules: forms, lists, navigation headers, and complex layouts.
// Atom: Basic Text component
interface TextProps {
variant: 'h1' | 'h2' | 'body' | 'caption';
children: React.ReactNode;
style?: StyleProp;
}
const AppText: React.FC = ({ variant, children, style }) => {
const textStyle = styles[variant];
return {children} ;
};
// Molecule: Card with title and content
interface CardProps {
title: string;
children: React.ReactNode;
onPress?: () => void;
}
const Card: React.FC = ({ title, children, onPress }) => {
const Container = onPress ? TouchableOpacity : View;
return (
{title}
{children}
);
};
// Organism: User profile section
const UserProfileSection: React.FC<{ user: User }> = ({ user }) => (
{user.bio}
{user.email}
);
The rule is simple: if a component is used more than once, make it reusable.
Keep Components Pure
Make components stateless unless absolutely necessary. Accept props and avoid internal side effects. Pure components are predictable, testable, and easy to reason about.
import React, { memo } from 'react';
import { TouchableOpacity, Text, StyleSheet, ViewStyle } from 'react-native';
interface PrimaryButtonProps {
title: string;
onPress: () => void;
disabled?: boolean;
loading?: boolean;
style?: ViewStyle;
}
const PrimaryButton: React.FC = memo(({
title,
onPress,
disabled = false,
loading = false,
style
}) => (
{loading ? (
) : (
{title}
)}
));
const styles = StyleSheet.create({
button: {
backgroundColor: '#007AFF',
paddingVertical: 14,
paddingHorizontal: 24,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
minHeight: 48,
},
disabled: {
backgroundColor: '#C7C7CC',
},
text: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
});
Using memo prevents unnecessary re-renders when parent components update but the props haven’t changed.
Use TypeScript for Strong Typing
Strong typing improves developer experience and catches bugs before runtime. TypeScript makes component APIs self-documenting.
import React from 'react';
import { View, TextInput, Text, StyleSheet } from 'react-native';
interface InputFieldProps {
label: string;
value: string;
onChangeText: (text: string) => void;
placeholder?: string;
secureTextEntry?: boolean;
error?: string;
keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad';
autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
}
const InputField: React.FC = ({
label,
value,
onChangeText,
placeholder,
secureTextEntry = false,
error,
keyboardType = 'default',
autoCapitalize = 'none',
}) => (
{label}
{error && {error} }
);
const styles = StyleSheet.create({
container: {
marginBottom: 16,
},
label: {
fontSize: 14,
fontWeight: '500',
marginBottom: 6,
color: '#333',
},
input: {
borderWidth: 1,
borderColor: '#E5E5E5',
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
fontSize: 16,
},
inputError: {
borderColor: '#FF3B30',
},
error: {
color: '#FF3B30',
fontSize: 12,
marginTop: 4,
},
});
Style with Flexibility
Allow style overrides via style props so components adapt to different contexts without modification.
import React from 'react';
import { Image, StyleSheet, ViewStyle, ImageStyle } from 'react-native';
interface AvatarProps {
uri: string;
size?: 'small' | 'medium' | 'large' | number;
style?: ViewStyle;
}
const sizeMap = {
small: 32,
medium: 48,
large: 80,
};
const Avatar: React.FC = ({ uri, size = 'medium', style }) => {
const dimension = typeof size === 'number' ? size : sizeMap[size];
return (
);
};
const styles = StyleSheet.create({
avatar: {
backgroundColor: '#E5E5E5',
},
});
// Usage examples
Separate Logic and Presentation
Keep business logic in custom hooks or container components, and UI in presentational components. This separation makes components more reusable and easier to test.
// Custom hook for user data
import { useState, useEffect } from 'react';
interface User {
id: string;
name: string;
email: string;
avatarUrl: string;
}
const useUser = (userId: string) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
const response = await api.getUser(userId);
setUser(response.data);
} catch (err) {
setError('Failed to load user');
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]);
return { user, loading, error };
};
// Pure presentational component
const UserCard: React.FC<{ user: User }> = ({ user }) => (
{user.name}
{user.email}
);
// Container component combining logic and presentation
const UserCardContainer: React.FC<{ userId: string }> = ({ userId }) => {
const { user, loading, error } = useUser(userId);
if (loading) return ;
if (error) return ;
if (!user) return null;
return ;
};
Organize Your Component Library
Create a well-structured component directory that scales with your application.
src/
components/
ui/ # Reusable UI components
Button/
Button.tsx
Button.test.tsx
index.ts
Input/
InputField.tsx
index.ts
Avatar/
Card/
index.ts # Barrel export
features/ # Feature-specific components
auth/
LoginForm.tsx
profile/
UserProfile.tsx
layout/ # Layout components
Container.tsx
Header.tsx
TabBar.tsx
// components/ui/index.ts - Barrel export
export { Button } from './Button';
export { InputField } from './Input';
export { Avatar } from './Avatar';
export { Card } from './Card';
// Usage in screens
import { Button, InputField, Avatar } from '@/components/ui';
Build Accessible Components
Accessibility should be built into components from the start, not added as an afterthought.
interface IconButtonProps {
icon: string;
onPress: () => void;
accessibilityLabel: string;
accessibilityHint?: string;
}
const IconButton: React.FC = ({
icon,
onPress,
accessibilityLabel,
accessibilityHint,
}) => (
);
// Usage
Real-World Component Example
Here’s a complete example of a reusable list item component that handles common patterns.
import React, { memo } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
interface ListItemProps {
title: string;
subtitle?: string;
leftElement?: React.ReactNode;
rightElement?: React.ReactNode;
onPress?: () => void;
showDivider?: boolean;
}
const ListItem: React.FC = memo(({
title,
subtitle,
leftElement,
rightElement,
onPress,
showDivider = true,
}) => {
const Container = onPress ? TouchableOpacity : View;
return (
<>
{leftElement && {leftElement} }
{title}
{subtitle && {subtitle} }
{rightElement && {rightElement} }
{showDivider && }
>
);
});
export default ListItem;
Common Mistakes to Avoid
Hardcoding styles or layout sizes makes components inflexible. Always use props for customization.
Repeating component logic across screens defeats the purpose of reusability. Extract shared logic into hooks.
Overusing context in shared components creates tight coupling. Pass data through props instead.
Ignoring accessibility excludes users and violates app store guidelines. Include accessibility props from the start.
Conclusion
Building reusable components in React Native is all about clarity, consistency, and modularity. Whether you’re building a simple button or an entire form, investing in reusability pays off as your app grows. Start with atomic design principles, keep components pure, use TypeScript for type safety, and organize your component library thoughtfully.
If you want to learn more about TypeScript integration, read “TypeScript vs JavaScript: Key Differences You Should Know.” For project organization patterns, see “Feature-First Folder Structure for Flutter and React Native.” You can also explore the React Native Components documentation for built-in component APIs. With well-designed reusable components, your team will build features faster and maintain code more easily.