Install for your agent
Download the file and place it at the path for your coding agent. Review the instructions before enabling a third-party skill.
Codex
~/.codex/skills/react-solid-principles-review/SKILL.mdClaude Code
.claude/skills/react-solid-principles-review/SKILL.mdCursor
.cursor/skills/react-solid-principles-review/SKILL.mdGitHub Copilot
.github/skills/react-solid-principles-review/SKILL.md</>View raw SKILL.mdInspect only the executable instructions your coding agent will receive, without catalog metadata.
---
name: "react-solid-principles-review"
description: "Review React and TypeScript code for practical SOLID violations and apply the smallest useful refactor to responsibilities, dependencies, and component contracts."
---
# Review SOLID Principles in React
Use this skill when the user wants a React/TypeScript feature, component, custom hook, or service reviewed for responsibility boundaries, coupling, dependency direction, and changeability.
Do not apply SOLID as a mechanical checklist. Refactor only violations that have real evidence of maintenance cost, risky change propagation, poor testability, or confusing contracts.
## Trigger / Usage Context
Use this workflow for requests involving:
- reviewing or refactoring a large component;
- multi-purpose custom hooks;
- services coupled to a concrete transport or SDK;
- boolean-prop explosions or oversized interfaces;
- inverted or circular dependencies;
- adding new variants without expanding a central conditional;
- simplifying code that has been over-abstracted.
For a purely educational question about SOLID definitions, explain the concepts instead of running a repository refactor workflow unless code is provided.
## Goals
- Find responsibilities with independent reasons to change.
- Reduce coupling that makes change or testing harder.
- Introduce abstractions only at boundaries with real policy or implementation variation.
- Keep component APIs small, composable, and understandable.
- Preserve current behavior unless the user explicitly requests a behavior change.
- Choose the smallest refactor that solves the observed problem.
## Architectural Context
Before recommending a SOLID change, inspect the actual project:
1. feature ownership and module boundaries;
2. state and side-effect ownership;
3. API clients and external dependencies;
4. component contracts and consumers;
5. tests, types, and public exports;
6. existing repository conventions.
If the project already has a healthy equivalent pattern, adapt to it. Do not import a new architecture just to demonstrate a principle.
## Workflow
### 1. Define the scope
List the files and direct dependencies in scope.
For each file capture:
```text
File:
Primary responsibility:
Reasons to change:
Dependencies:
Consumers:
```
A file becomes an SRP candidate when it has independent reasons to change, not merely because it is long.
### 2. Record findings with evidence
Every finding should include:
```text
Principle:
Evidence:
Maintenance cost:
Smallest useful change:
Risk:
```
Do not report a principle name without showing the concrete code pressure behind it.
### 3. Apply SRP to reasons for change
Smell:
```tsx
function CheckoutPage() {
// fetch pricing
// validate coupon
// calculate totals
// persist cart
// render layout
}
```
First identify which responsibilities actually evolve independently.
A possible refactor:
```text
CheckoutPage
├── useCheckoutPricing
├── checkout domain calculations
└── CheckoutView
```
If extracting a pure function solves the problem, do not create another architectural layer.
### 4. Prefer composition for OCP
If every new variant requires another branch in a central component:
```tsx
if (type === "card") ...
if (type === "paypal") ...
if (type === "crypto") ...
```
check whether a small composition or strategy boundary localizes the change:
```ts
const paymentRenderers = {
card: CardPayment,
paypal: PayPalPayment,
}
```
Only introduce a registry when variants are a real extension point. Two stable branches may still be clearer as ordinary conditionals.
### 5. Review LSP through component contracts
In React, LSP usually appears through substitutable contracts rather than class inheritance.
Smell:
```tsx
<Button variant="link" disabled />
```
If one variant silently ignores props or changes semantics, the contract may be too broad.
A discriminated union can prevent invalid states:
```ts
type ButtonProps =
| { kind: "button"; disabled?: boolean }
| { kind: "link"; href: string }
```
Use stronger types only when they make the contract easier to understand.
### 6. Apply ISP to props and service interfaces
If a component receives an entire domain object but only uses two fields:
```ts
function UserAvatar({ user }: { user: User }) {
return <img src={user.avatarUrl} alt={user.name} />
}
```
consider a smaller contract:
```ts
type UserAvatarProps = {
name: string
avatarUrl: string
}
```
Do not split stable domain contracts mechanically.
### 7. Apply DIP only at meaningful boundaries
Smell:
```ts
import axios from "axios"
export async function submitOrder(input: OrderInput) {
return axios.post("/orders", input)
}
```
If the use case should be testable or reusable without the transport, define a small port:
```ts
export interface OrderRepository {
create(input: OrderInput): Promise<Order>
}
```
and an infrastructure adapter:
```ts
export class HttpOrderRepository implements OrderRepository {
async create(input: OrderInput) {
return postOrder(input)
}
}
```
For a small feature with one straightforward request, an interface and class may add more ceremony than value.
### 8. Refactor incrementally
Prefer this order:
```text
1. Rename or extract pure logic
2. Reduce contract surface
3. Move responsibility to the correct owner
4. Introduce a boundary only when justified
5. Update imports
6. Run available checks
```
### 9. Validate
Run or recommend the checks available in the project:
```text
typecheck
lint
unit tests
integration tests
build
```
If execution is unavailable, state exactly what remains unverified.
## Rules / Constraints
- Do not measure SOLID by file count.
- Do not create classes or interfaces just to show a pattern.
- Preserve behavior unless a behavior change is requested.
- Inspect consumers before changing a public API.
- Do not force a shared abstraction before at least two concrete needs justify it.
- Do not add a dependency-injection container when a function parameter solves the boundary.
- Prefer React composition over inheritance.
- Keep domain logic separate from rendering and transport where that separation has real value.
- Report code smells with evidence, not taste.
## Anti-patterns
### SRP means one function per five lines
No. SRP is about reasons to change, not size.
### OCP means everything becomes a plugin system
No. Extension points are useful only when extension is a real product requirement.
### DIP means every API needs an interface
No. Create a boundary only when dependency direction, testing, or multiple implementations justify it.
### ISP means splitting every type
No. A smaller contract should reduce consumer coupling.
### SOLID should make the design more sophisticated
If the refactor requires more diagrams but does not reduce the cost of change, it is probably worse.
## Examples / Patterns
### Before
```tsx
export function ProfilePanel({ user, api, analytics, theme }: Props) {
// fetch
// transform
// track
// permissions
// rendering
}
```
### After review
The appropriate result may simply be:
```text
- Read data from the existing query hook.
- Move a pure transform to a small function.
- Keep analytics close to the interaction handler.
- Reuse the existing permission helper.
- Leave ProfilePanel responsible for composition and rendering.
```
A repository, service, and use-case class are not automatically required.
## Output Expectations
Return results in this order:
1. **Scope reviewed**
2. **Prioritized findings**
3. **SOLID principle + evidence**
4. **Minimal refactor plan**
5. **Files changed**
6. **Behavior preserved / changed**
7. **Validation performed**
8. **Remaining risks**
Prioritize findings with approximate `high / medium / low` severity.
## Safety / Limits
- Never copy secrets, credentials, or tokens into output.
- Do not add external dependencies without a concrete need.
- Do not infer a repository-wide migration from one local finding.
- Separate high-risk public-contract or data-model changes from routine refactors.
- When tests are weak, keep refactors small and reversible.
Use this skill when the user wants a React/TypeScript feature, component, custom hook, or service reviewed for responsibility boundaries, coupling, dependency direction, and changeability.
Do not apply SOLID as a mechanical checklist. Refactor only violations that have real evidence of maintenance cost, risky change propagation, poor testability, or confusing contracts.
Trigger / Usage Context
Use this workflow for requests involving:
- reviewing or refactoring a large component;
- multi-purpose custom hooks;
- services coupled to a concrete transport or SDK;
- boolean-prop explosions or oversized interfaces;
- inverted or circular dependencies;
- adding new variants without expanding a central conditional;
- simplifying code that has been over-abstracted.
For a purely educational question about SOLID definitions, explain the concepts instead of running a repository refactor workflow unless code is provided.
Goals
- Find responsibilities with independent reasons to change.
- Reduce coupling that makes change or testing harder.
- Introduce abstractions only at boundaries with real policy or implementation variation.
- Keep component APIs small, composable, and understandable.
- Preserve current behavior unless the user explicitly requests a behavior change.
- Choose the smallest refactor that solves the observed problem.
Architectural Context
Before recommending a SOLID change, inspect the actual project:
- feature ownership and module boundaries;
- state and side-effect ownership;
- API clients and external dependencies;
- component contracts and consumers;
- tests, types, and public exports;
- existing repository conventions.
If the project already has a healthy equivalent pattern, adapt to it. Do not import a new architecture just to demonstrate a principle.
Workflow
1. Define the scope
List the files and direct dependencies in scope.
For each file capture:
File:
Primary responsibility:
Reasons to change:
Dependencies:
Consumers:
A file becomes an SRP candidate when it has independent reasons to change, not merely because it is long.
2. Record findings with evidence
Every finding should include:
Principle:
Evidence:
Maintenance cost:
Smallest useful change:
Risk:
Do not report a principle name without showing the concrete code pressure behind it.
3. Apply SRP to reasons for change
Smell:
function CheckoutPage() {
// fetch pricing
// validate coupon
// calculate totals
// persist cart
// render layout
}
First identify which responsibilities actually evolve independently.
A possible refactor:
CheckoutPage
├── useCheckoutPricing
├── checkout domain calculations
└── CheckoutView
If extracting a pure function solves the problem, do not create another architectural layer.
4. Prefer composition for OCP
If every new variant requires another branch in a central component:
if (type === "card") ...
if (type === "paypal") ...
if (type === "crypto") ...
check whether a small composition or strategy boundary localizes the change:
const paymentRenderers = {
card: CardPayment,
paypal: PayPalPayment,
}
Only introduce a registry when variants are a real extension point. Two stable branches may still be clearer as ordinary conditionals.
5. Review LSP through component contracts
In React, LSP usually appears through substitutable contracts rather than class inheritance.
Smell:
<Button variant="link" disabled />
If one variant silently ignores props or changes semantics, the contract may be too broad.
A discriminated union can prevent invalid states:
type ButtonProps =
| { kind: "button"; disabled?: boolean }
| { kind: "link"; href: string }
Use stronger types only when they make the contract easier to understand.
6. Apply ISP to props and service interfaces
If a component receives an entire domain object but only uses two fields:
function UserAvatar({ user }: { user: User }) {
return <img src={user.avatarUrl} alt={user.name} />
}
consider a smaller contract:
type UserAvatarProps = {
name: string
avatarUrl: string
}
Do not split stable domain contracts mechanically.
7. Apply DIP only at meaningful boundaries
Smell:
import axios from "axios"
export async function submitOrder(input: OrderInput) {
return axios.post("/orders", input)
}
If the use case should be testable or reusable without the transport, define a small port:
export interface OrderRepository {
create(input: OrderInput): Promise<Order>
}
and an infrastructure adapter:
export class HttpOrderRepository implements OrderRepository {
async create(input: OrderInput) {
return postOrder(input)
}
}
For a small feature with one straightforward request, an interface and class may add more ceremony than value.
8. Refactor incrementally
Prefer this order:
1. Rename or extract pure logic
2. Reduce contract surface
3. Move responsibility to the correct owner
4. Introduce a boundary only when justified
5. Update imports
6. Run available checks
9. Validate
Run or recommend the checks available in the project:
typecheck
lint
unit tests
integration tests
build
If execution is unavailable, state exactly what remains unverified.
Rules / Constraints
- Do not measure SOLID by file count.
- Do not create classes or interfaces just to show a pattern.
- Preserve behavior unless a behavior change is requested.
- Inspect consumers before changing a public API.
- Do not force a shared abstraction before at least two concrete needs justify it.
- Do not add a dependency-injection container when a function parameter solves the boundary.
- Prefer React composition over inheritance.
- Keep domain logic separate from rendering and transport where that separation has real value.
- Report code smells with evidence, not taste.
Anti-patterns
SRP means one function per five lines
No. SRP is about reasons to change, not size.
OCP means everything becomes a plugin system
No. Extension points are useful only when extension is a real product requirement.
DIP means every API needs an interface
No. Create a boundary only when dependency direction, testing, or multiple implementations justify it.
ISP means splitting every type
No. A smaller contract should reduce consumer coupling.
SOLID should make the design more sophisticated
If the refactor requires more diagrams but does not reduce the cost of change, it is probably worse.
Examples / Patterns
Before
export function ProfilePanel({ user, api, analytics, theme }: Props) {
// fetch
// transform
// track
// permissions
// rendering
}
After review
The appropriate result may simply be:
- Read data from the existing query hook.
- Move a pure transform to a small function.
- Keep analytics close to the interaction handler.
- Reuse the existing permission helper.
- Leave ProfilePanel responsible for composition and rendering.
A repository, service, and use-case class are not automatically required.
Output Expectations
Return results in this order:
- Scope reviewed
- Prioritized findings
- SOLID principle + evidence
- Minimal refactor plan
- Files changed
- Behavior preserved / changed
- Validation performed
- Remaining risks
Prioritize findings with approximate high / medium / low severity.
Safety / Limits
- Never copy secrets, credentials, or tokens into output.
- Do not add external dependencies without a concrete need.
- Do not infer a repository-wide migration from one local finding.
- Separate high-risk public-contract or data-model changes from routine refactors.
- When tests are weak, keep refactors small and reversible.