• Home
  • About
  • Agent Skills
  • Projects
  • Blog
  • Contact
Resume
Agent Skills/architecture/Review SOLID Principles in React

Review SOLID Principles in React

Review React and TypeScript code for practical SOLID violations and apply the smallest useful refactor to responsibilities, dependencies, and component contracts.

reacttypescriptsolidclean-codearchitecturerefactoring

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.md

Claude Code

.claude/skills/react-solid-principles-review/SKILL.md

Cursor

.cursor/skills/react-solid-principles-review/SKILL.md

GitHub 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.
Source file: react-solid-principles-review/SKILL.md
---
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:

  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:

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:

  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.

Skill details

Version
1.0.0
Updated
2026-09-07
Category
architecture
Difficulty
advanced
License
MIT
Author
Naser Rasouli

Capabilities

Executes scriptsNot required
Network accessNot required

Source article

Read the original article behind this skill for deeper explanations, context, and examples.