Custom Components
Learn to create reusable custom components in React Native with proper styling, props, and state management. This is a foundational concept in cross-platform mobile development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React Native experience. Take your time with each section and practice the examples
Component Architecture
Design reusable components with clear prop interfaces, proper TypeScript types, and consistent styling patterns.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Component Composition
Use composition patterns to build complex UI elements from smaller, focused components.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Custom Button Component
import React from 'react';
import { TouchableOpacity, Text, StyleSheet, ViewStyle, TextStyle, ActivityIndicator } from 'react-native';
interface CustomButtonProps {
title: string;
onPress: () => void;
variant?: 'primary' | 'secondary' | 'outline' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
loading?: boolean;
icon?: React.ReactNode;
style?: ViewStyle;
textStyle?: TextStyle;
}
const CustomButton: React.FC<CustomButtonProps> = ({
title,
onPress,
variant = 'primary',
size = 'medium',
disabled = false,
loading = false,
icon,
style,
textStyle,
}) => {
const getButtonStyle = (): ViewStyle => {
const baseStyle = [styles.button, styles[`button${size.charAt(0).toUpperCase() + size.slice(1)}`]];
switch (variant) {
case 'primary':
baseStyle.push(styles.primaryButton);
break;
case 'secondary':
baseStyle.push(styles.secondaryButton);
break;
case 'outline':
baseStyle.push(styles.outlineButton);
break;
case 'danger':
baseStyle.push(styles.dangerButton);
break;
}
if (disabled) {
baseStyle.push(styles.disabledButton);
}
return StyleSheet.flatten([baseStyle, style]);
};
const getTextStyle = (): TextStyle => {
const baseStyle = [styles.text, styles[`text${size.charAt(0).toUpperCase() + size.slice(1)}`]];
switch (variant) {
case 'primary':
baseStyle.push(styles.primaryText);
break;
case 'secondary':
baseStyle.push(styles.secondaryText);
break;
case 'outline':
baseStyle.push(styles.outlineText);
break;
case 'danger':
baseStyle.push(styles.dangerText);
break;
}
if (disabled) {
baseStyle.push(styles.disabledText);
}
return StyleSheet.flatten([baseStyle, textStyle]);
};
return (
<TouchableOpacity
style={getButtonStyle()}
onPress={onPress}
disabled={disabled || loading}
activeOpacity={0.7}
>
{loading ? (
<ActivityIndicator
size="small"
color={variant === 'outline' ? '#2196F3' : 'white'}
/>
) : (
<>
{icon && <>{icon}</>}
<Text style={getTextStyle()}>{title}</Text>
</>
)}
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
paddingHorizontal: 16,
paddingVertical: 12,
},
buttonSmall: {
paddingHorizontal: 12,
paddingVertical: 8,
},
buttonMedium: {
paddingHorizontal: 16,
paddingVertical: 12,
},
buttonLarge: {
paddingHorizontal: 24,
paddingVertical: 16,
},
primaryButton: {
backgroundColor: '#2196F3',
},
secondaryButton: {
backgroundColor: '#6c757d',
},
outlineButton: {
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: '#2196F3',
},
dangerButton: {
backgroundColor: '#dc3545',
},
disabledButton: {
backgroundColor: '#e9ecef',
borderColor: '#e9ecef',
},
text: {
fontWeight: '600',
textAlign: 'center',
},
textSmall: {
fontSize: 14,
},
textMedium: {
fontSize: 16,
},
textLarge: {
fontSize: 18,
},
primaryText: {
color: 'white',
},
secondaryText: {
color: 'white',
},
outlineText: {
color: '#2196F3',
},
dangerText: {
color: 'white',
},
disabledText: {
color: '#6c757d',
},
});
export default CustomButton;Custom Card Component
import React from 'react';
import { View, Text, StyleSheet, ViewStyle, TouchableOpacity } from 'react-native';
interface CustomCardProps {
title?: string;
subtitle?: string;
children?: React.ReactNode;
onPress?: () => void;
style?: ViewStyle;
elevation?: number;
padding?: number;
margin?: number;
borderRadius?: number;
backgroundColor?: string;
}
const CustomCard: React.FC<CustomCardProps> = ({
title,
subtitle,
children,
onPress,
style,
elevation = 2,
padding = 16,
margin = 8,
borderRadius = 8,
backgroundColor = 'white',
}) => {
const cardStyle = {
padding,
margin,
borderRadius,
backgroundColor,
shadowColor: '#000',
shadowOffset: { width: 0, height: elevation },
shadowOpacity: 0.1,
shadowRadius: elevation * 2,
elevation,
};
const CardContent = () => (
<View style={[cardStyle, style]}>
{title && <Text style={styles.title}>{title}</Text>}
{subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
{children}
</View>
);
if (onPress) {
return (
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
<CardContent />
</TouchableOpacity>
);
}
return <CardContent />;
};
const styles = StyleSheet.create({
title: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
marginBottom: 8,
},
subtitle: {
fontSize: 14,
color: '#666',
marginBottom: 12,
},
});
export default CustomCard;