From the author of the Telegram channel REACT NATIVE HUB
Join a growing community of React Native Devs! 👆
When building scalable React Native apps, clean architecture matters. One of the key principles of clean code is the Liskov Substitution Principle (LSP)— often overlooked, but incredibly powerful for designing reusable, maintainable components.
Let’s break it down and see how LSP applies in React Native, especially when creating buttons, inputs, or any shared UI components.
What Is the Single Responsibility Principle?
- Definition: Objects or components should be replaceable with instances of their subtypes without altering the correctness of the program.
- In React Native: When creating components or extending them, ensure they can be used interchangeably without breaking the application.
Bad Example: Violating LSP in React Native
In this example, incorrect use of interface methods and not following the base interface for the same type of component violates the LSP principle.
// ❌ BAD EXAMPLE - Violating LSP
interface BadBaseButton {
onPress: () => void;
text: string; // Restricts to string only
}
const BadBaseButton: React.FC<BadBaseButton> = ({ onPress, text }) => (
return (
<TouchableOpacity onPress={onPress}>
<Text>{text}</Text>
</TouchableOpacity>
);
);
// Violates LSP by changing prop structure and behavior
interface BadPrimaryButton {
label: string; // Different prop name
onAction: () => Promise<void>; // Different type
}
const BadPrimaryButton: React.FC<BadPrimaryButton> = ({ label, onAction }) => {
return (
<TouchableOpacity onPress={onAction}>
<Text>{label}</Text>
</TouchableOpacity>
);
};
// Usage shows incompatibility
const BadExample: React.FC = () => {
const handleClick = () => console.log('clicked');
return (
<View>
<BadBaseButton
onClick={handleClick}
text="Click me"
/>
{/* Won't work! Different props and behavior */}
<BadPrimaryButton
label="Click me"
onAction={async () => console.log('clicked')}
/>
</View>
);
};
Following LSP
It helps to ensure base props are extended consistently, the same onClick type throughout, components are interchangeable & add features without breaking the base contract.
// ✅ GOOD EXAMPLE - Following LSP
interface GoodBaseButtonProps {
onPress: () => void;
children: React.ReactNode;
}
const GoodBaseButton: React.FC<GoodBaseButtonProps> = ({ onPress, children }) => (
<TouchableOpacity onPress={onPress}>{children}</TouchableOpacity>
);
// Properly extends base props
interface GoodPrimaryButtonProps extends GoodBaseButtonProps {
variant?: 'solid' | 'outline'; // Optional additional prop
}
const GoodPrimaryButton: React.FC<GoodPrimaryButtonProps> = ({
onPress,
children,
variant = 'solid'
}) => {
const bg = variant === 'solid' ? 'bg-blue-500' : 'border-blue-500';
return (
<TouchableOpacity onPress={onPress} style={{backgroundColor: bg}}>
{children}
</TouchableOpacity>
);
};
// Usage shows compatibility
const GoodExample: React.FC = () => {
const handleClick = () => console.log('clicked');
return (
<View>
{/* Both components can use the same handler */}
<GoodBaseButton onPress={handleClick}>
Click me
</GoodBaseButton>
<GoodPrimaryButton onPress={handleClick} variant="solid">
Click me
</GoodPrimaryButton>
</View>
);
};
Key Takeaways
- LSP ensures that your components remain interchangeable.
- In React Native, always extend base props when creating variants.
- Avoid re-inventing props or behavior for components that serve the same role.
- Following LSP leads to better code reuse, less duplication, and easier maintenance.
About me: My name is Arsen, and I am a react native developer and owner of the TG channel 👇
🔗 Join TG community for React Native Devs: REACT NATIVE HUB
Top comments (0)