---
name: separation-of-concerns
description: Use when component does too many things. Use when mixing data fetching, logic, and presentation. Use when code is hard to test.
---
# Separation of Concerns
## Overview
**Each piece of code should do one thing. Data, logic, and presentation should be separate.**
Mixed concerns create untestable, unreusable, unmaintainable code. Separation enables testing, reuse, and clarity.
## When to Use
- Component fetches, transforms, and displays data
- Business logic mixed with UI code
- Database queries in controllers
- Hard to test a piece of code in isolation
## The Iron Rule
```
NEVER mix data fetching, business logic, and presentation in one place.
```
**No exceptions:**
- Not for "it's a small component"
- Not for "it's simpler this way"
- Not for "only used once"
- Not for "it works"
## Detection: Mixed Concerns Smell
If one file does fetch + transform + display, STOP:
```typescript
// ❌ VIOLATION: Component does everything
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Data fetching
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
// Business logic / transformation
const fullName = `${data.firstName} ${data.lastName}`;
const memberSince = new Date(data.createdAt).toLocaleDateString();
const isVIP = data.orderCount > 100;
setUser({ ...data, fullName, memberSince, isVIP });
setLoading(false);
});
}, [userId]);
// Presentation
if (loading) return
Loading...
;
return (
{user.fullName}
{user.isVIP &&
VIP}
Member since: {user.memberSince}
);
}
```
Problems:
- Can't test formatting without fetching
- Can't reuse fetch logic
- Can't reuse presentation
- Component does 3 jobs
## The Correct Pattern: Separated Layers
```typescript
// ✅ CORRECT: Separated concerns
// 1. Data fetching (hook)
// hooks/useUser.ts
function useUser(userId: string) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => {
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
})
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
return { user, loading, error };
}
// 2. Business logic (pure functions)
// utils/userFormatters.ts
interface FormattedUser {
fullName: string;
memberSince: string;
isVIP: boolean;
}
function formatUser(user: User): FormattedUser {
return {
fullName: `${user.firstName} ${user.lastName}`,
memberSince: new Date(user.createdAt).toLocaleDateString(),
isVIP: user.orderCount > 100,
};
}
// 3. Presentation (dumb component)
// components/UserCard.tsx
interface UserCardProps {
fullName: string;
memberSince: string;
isVIP: boolean;
}
function UserCard({ fullName, memberSince, isVIP }: UserCardProps) {
return (
{fullName}
{isVIP &&
VIP}
Member since: {memberSince}
);
}
// 4. Composition (container component)
// pages/UserProfile.tsx
function UserProfile({ userId }) {
const { user, loading, error } = useUser(userId);
if (loading) return ;
if (error) return ;
if (!user) return ;
const formatted = formatUser(user);
return ;
}
```
## Benefits of Separation
| Mixed | Separated |
|-------|-----------|
| Can't test formatting | `formatUser()` tested in isolation |
| Can't reuse fetch | `useUser()` reusable anywhere |
| Can't reuse UI | `UserCard` reusable with any data |
| 1 complex component | 4 simple pieces |
## The Layers
### 1. Data Layer (Hooks, Services)
- Fetching data
- API calls
- State management
- No business logic
### 2. Logic Layer (Pure Functions)
- Transformations
- Calculations
- Validations
- Business rules
### 3. Presentation Layer (Components)
- Rendering UI
- Styling
- User interactions
- No data fetching
### 4. Composition Layer (Containers)
- Connects layers
- Handles loading/error states
- Passes data down
## Pressure Resistance Protocol
### 1. "It's a Small Component"
**Pressure:** "For simple cases, separation is overkill"
**Response:** Small becomes large. Start clean, stay clean.
**Action:** Separate even for small components. It costs little.
### 2. "It's Simpler This Way"
**Pressure:** "Everything in one place is easier to understand"
**Response:** Mixed concerns seem simple but are hard to test, debug, and modify.
**Action:** Separation is simpler in the long run.
### 3. "Only Used Once"
**Pressure:** "This component is unique, won't be reused"
**Response:** Testability matters even for unique components.
**Action:** Separate for testability, not just reuse.
## Red Flags - STOP and Reconsider
- `useEffect` with fetch + transform + setState
- Business logic in render functions
- Database queries in route handlers
- API calls in utility functions
- Components with 100+ lines
**All of these mean: Separate the concerns.**
## Quick Reference
| Concern | Where It Belongs |
|---------|------------------|
| API calls | Hooks / Services |
| Data transformation | Pure functions |
| Business rules | Pure functions |
| UI rendering | Presentation components |
| Connecting pieces | Container components |
## Common Rationalizations (All Invalid)
| Excuse | Reality |
|--------|---------|
| "Small component" | Small grows. Separate now. |
| "Simpler together" | Separated is simpler to test/modify. |
| "Only used once" | Testability matters. |
| "It works" | Working ≠ maintainable. |
| "Over-engineering" | This is just engineering. |
## The Bottom Line
**Data fetching in hooks. Logic in pure functions. UI in components.**
Separation enables testing, reuse, and maintainability. A component should either fetch data, transform it, or display it - never all three.