Single Responsibility Principle (SRP).
Content: A component should have only one reason to change, meaning it should have only one job.
Should: Try splitting code into smaller component to easy to control and debug.
Shouldn't: Combine display logic, api fetching and business logic into one component.
// Should
const ResumeBuilderShould = ({ data }) => {
return (
<div>
<Info data={data.info}/>
<Project data={data.project}/>
<Education data={data.education} />
</div>
)
}
const Info = ({ data }) => (
<div>
<div>Name: {data.name}</div>
<div>Address: {data.address}</div>
</div>
)
const Project = ({ data }) => (
<div>Project Info....</div>
)
const Education = () => (
<div>Education Info ...</div>
)
// Shouldn't
const ResumeBuilderShouldNot = ({ data }) => {
return (
<div>
<div>Name: {data.info.name}</div>
<div>Address: {data.info.address}</div>
<div>Project Info....</div>
<div>Education Info....</div>
</div>
)
}
Open/Closed Principle (OCP)
Content: Software entities should be open for extension, but closed for modification.
Should: Use props to extend component functionalities without modifying the original component.
Shouldn't: Modify the original component code to add style or new behaviors directly.
// Should
const Input = ({ placeholder, onChange, className, ...props }) => {
return (
<input
{...props}
onChange={onChange}
placeholder={placeholder}
className={className}
/>
)
}
// Usage with Custom Hook or HOC
const withActiveStyle = (Component) => ({ isActive, ...props }) => {
const activeClass = isActive ? 'color-active' : 'color-primary';
return <Component {...props} className={`${props.className} ${activeClass}`} />;
};
const CustomInput = withActiveStyle(Input);
// Shouldn't
const InputShouldNot = ({ placeholder, onChange, className, ...props }) => {
const isActiveStyle = props.isActive ? 'color-active' : 'color-primary';
return (
<input
{...props}
onChange={onChange}
placeholder={placeholder}
className={`${className} ${isActiveStyle}`}
/>
);
};
Liskov Substitution Principle (LSP)
Content: Objects of a superclass shall be replaceable with objects of its subclasses without breaking the application.
Should: Allow subclass component can replace superclass components.
Shouldn't: Write subclass-specific properties that break functionality when substituting.
// Should
const Label = ({ htmlFor, children, className }) => {
return <label htmlFor={htmlFor} className={className}>{children}</label>
}
// Usage Label with Icon
const LabelIcon = ({ htmlFor, children, className, icon, iconClassName }) => {
return (
<label htmlFor={htmlFor} className={className}>
{icon && <img src={icon} alt="icon" className={iconClassName} />}
{children}
</label>
);
};
//Shouldn't
const LabelIconShouldNot = ({ htmlFor, className, icon, iconClassName }) => {
if (!icon) return
return (
<label htmlFor={htmlFor} className={className}>
<img src={icon} alt="icon" className={iconClassName} />
</label>
)
}
Interface Segregation Principle (ISP)
Content: No client should be forced to depend on methods it does not use
Should: Provide specific interfaces for different uses.
Shouldn't: Clutter a component with unnecessary properties.
// Should
// Base Button Component
const Button = ({ type, children, onClick, className }) => {
let buttonClass = "btn";
if (type === 'success') {
buttonClass += " btn-success";
} else if (type === 'primary') {
buttonClass += " btn-primary";
}
return (
<button type="button" className={`${buttonClass} ${className}`} onClick={onClick}>
{children}
</button>
);
};
// Disabled Button from base Button Component
const DisabledButton = ({ children, className }) => {
return <div className={`btn-disabled ${className}`}>{children}</div>;
};
// Shouldn't
const ButtonShouldNot = ({ type, children, onClick, isDisable }) => {
if (isDisable && !onClick) {
return <div className="btn-disable">{children}</div>;
}
return <button type={type} onClick={onClick}>{children}</button>;
};
Dependency Inversion Principle (DIP)
Content: High-level modules should not depend on low-level modules. Both should depend on abstractions
Should: Use hook, context, or similar patterns to abstract data fetching or state management.
Shouldn't: Hard-code data fetching inside component.
// Custom Hook for Layout
const useLayout = () => {
const [layout, setLayout] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const handleGetLayout = async () => {
try {
// Simulating data fetching
const response = await fetch('/api/layout'); // replace with actual data fetching
const data = await response.json();
setLayout(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
handleGetLayout();
}, []);
return { layout, loading, error };
};
// Layout Component
const Layout = () => {
const { layout, loading, error } = useLayout();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{layout.name}</div>;
};
// Shouldn't
const LayoutShouldNot = () => {
const [layout, setLayout] = useState(null);
useEffect(() => {
const handleGetLayout = async () => {
// data fetching....
};
const updateLayout = handleGetLayout();
setLayout(updateLayout);
}, []);
if (!layout) return <div>Loading...</div>;
return <div>{layout.name}</div>;
};