Applying the Single Responsibility Principle in React: Refactor Your Components
This article explains how the Single Responsibility Principle applies to React components, shows a common anti‑pattern with a monolithic component, and demonstrates a cleaner architecture using custom hooks, presentation components, and container components for better maintainability.
This article is translated from "Single Responsibility Principle in React: The Art of Component Focus" and discusses how to apply SRP in React.
The Single Responsibility Principle states that a class should have only one reason to change.
Problems of Multiple Responsibilities
Here is a common anti‑pattern:
<code>// Don't do this
const UserProfile = () => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetchUser();
}, []);
const fetchUser = async () => {
try {
const response = await fetch("/api/user");
const data = await response.json();
setUser(data);
} catch (e) {
setError(e as Error);
} finally {
setLoading(false);
}
};
const handleUpdateProfile = async (data: Partial<User>) => {
try {
await fetch("/api/user", {
method: "PUT",
body: JSON.stringify(data),
});
fetchUser(); // refresh data
} catch (e) {
setError(e as Error);
}
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>No user found</div>;
return (
<div>
<h1>{user.name}</h1>
<form onSubmit={/* form logic */}>
{/* complex form fields */}
</form>
<UserStats userId={user.id} />
<UserPosts userId={user.id} />
</div>
);
};</code>This component violates SRP because it handles:
Data fetching
Error handling
Loading state
Form handling
Layout and presentation
Better Approach: Separation of Concerns
Let's split it into focused components:
<code>// Data fetching hook
const useUser = (userId: string) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetchUser();
}, [userId]);
const fetchUser = async () => {
try {
const response = await fetch(`/api/user/${userId}`);
const data = await response.json();
setUser(data);
} catch (e) {
setError(e as Error);
} finally {
setLoading(false);
}
};
return { user, loading, error, refetch: fetchUser };
};
// Presentation component
const UserProfileView = ({
user,
onUpdate,
}: {
user: User;
onUpdate: (data: Partial<User>) => void;
}) => (
<div>
<h1>{user.name}</h1>
<UserProfileForm user={user} onSubmit={onUpdate} />
<UserStats userId={user.id} />
<UserPosts userId={user.id} />
</div>
);
// Container component
const UserProfileContainer = ({ userId }: { userId: string }) => {
const { user, loading, error, refetch } = useUser(userId);
const handleUpdate = async (data: Partial<User>) => {
try {
await fetch(`/api/user/${userId}`, {
method: "PUT",
body: JSON.stringify(data),
});
refetch();
} catch (e) {
// error handling
}
};
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <NotFound message="User not found" />;
return <UserProfileView user={user} onUpdate={handleUpdate} />;
};</code>Key Takeaways
Separate data and presentation – use hooks for data, components for UI.
Create focused components – each component should do one thing well.
Use composition – build complex features from simple parts.
Reuse logic – extract reusable logic into custom hooks.
Think in layers – data layer, business logic layer, presentation layer.
Conclusion
When every component has a clear, single responsibility, the application becomes easier to maintain, test, and extend.
As emphasized by Bob Uncle in "Clean Architecture," the key is that there is only one reason for change. This subtle distinction is crucial: a component may do several related tasks if they change for the same reason, but should be split when different reasons cause changes.
If you find yourself describing a component's functionality with "and," it likely violates SRP – split it, while still considering why each part changes and who requests those changes.
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.