S Single Responsibility Principle SRP · Quick recall Q&A
3 min readQuick recall Q&A
When a component handles data fetching, validation, form state, analytics, and rendering all in one place. High line count (>200 lines), multiple useEffect hooks with different purposes, and mixing UI with business logic are red flags.
Focused modules let teams modify one area without risk to others—changing a validation rule doesn't require retesting data fetching logic. Reduces merge conflicts and enables independent feature deployments.
Yes, if its sole responsibility is composition/coordination. For example, a CheckoutFlow component can orchestrate cart, payment, and confirmation components; its responsibility is orchestration, not implementing checkout logic itself.
Group files by feature/domain, not by type. For example:
features/
user-profile/
UserProfile.tsx
useUser.ts
userService.ts
userValidators.ts
This keeps responsibilities cohesive and discoverable.
Custom hooks extract reusable logic with focused responsibilities (useUser, useAuth, useAnalytics), ensuring components stay focused on UI rendering and composition.
Services encapsulate single responsibilities (UserService for API calls, AuthService for authentication, LoggingService for logging), keeping components focused on UI coordination via dependency injection.
- Move data fetching to custom hooks
- Extract validation to utility functions
- Move API calls to service modules
- Extract analytics/logging to dedicated hooks
Extract logic incrementally:
Add unit tests for each extracted piece.
Absolutely. A utility module that mixes date formatting, API calls, and validation also violates SRP. Break into focused modules: dateUtils.ts, apiClient.ts, validators.ts.
Focused modules enable better tree-shaking—unused validation logic or analytics code can be eliminated if components don't import them. Mixing responsibilities makes dead code elimination harder.
- Pure validators can be unit tested in isolation
- Data hooks can be tested with mock APIs
- Components can be tested with mock dependencies
- No need to set up entire component trees to test validation logic
Testing becomes easier:
ESLint rules (max-lines, complexity), code review checklists, and architecture tests. Tools like dependency-cruiser can enforce module boundaries.
Describe it as separating concerns like a restaurant: the waiter doesn't cook (UI doesn't handle business logic), the chef doesn't serve (business logic doesn't render), the host doesn't wash dishes (coordination doesn't include implementation details).
Separate concerns: actions for events, reducers for state transitions, selectors for derived data, middleware for side effects. Each piece has one clear responsibility, making the state machine predictable and testable.