Claude
Skills
Sign in
Back

component-designing

Included with Lifetime
$97 forever

Component and type design for TypeScript + React code. Use when planning new features, designing components and custom hooks, preventing primitive obsession, or when refactoring reveals need for new abstractions. Supports layer-based and hybrid architecture patterns with type safety.

Design

What this skill does


# Component Designing

Component and type design for TypeScript + React applications.
Use when planning new features or identifying need for new abstractions during refactoring.

## When to Use
- Planning a new feature (before writing code)
- Refactoring reveals need for new components/hooks
- Linter failures suggest better abstractions
- When you need to think through component architecture
- Designing state management approach

## Purpose
Design clean, well-composed components and types that:
- Prevent primitive obsession (use branded types, Zod schemas)
- Ensure type safety with TypeScript
- Follow component composition patterns
- Implement feature-based architecture
- Create reusable custom hooks

## Workflow

### 0. Architecture Pattern Analysis (FIRST STEP)

**Default: Match existing codebase architecture** (consistency is key).

Scan codebase structure:
- **Layer-based**: `src/{components,hooks,contexts,types}/...` - Group by technical layer
- **Hybrid**: `src/{components,hooks}/...` + `src/pages/...` - Shared layers + page-specific code
- **Feature-based**: `src/features/[feature]/{components,hooks,types}` - Group by feature

**Decision Flow**:
1. **Pure layer-based** → Continue pattern, place code in appropriate technical layers
2. **Pure feature-based** → Continue pattern, implement as `src/features/[new-feature]/`
3. **Hybrid** → Follow existing conventions (e.g., shared in layers, page-specific co-located)

**Layer-Based Structure** (Recommended for most codebases):
```
src/
  components/        # Reusable components only
    Button.tsx
    Input.tsx
    Modal.tsx
    Card.tsx
  pages/            # Top-level views/pages (use components)
    LoginPage.tsx
    DashboardPage.tsx
    UserProfilePage.tsx
  hooks/            # Reusable hooks
    useAuth.ts
    useDebounce.ts
    useLocalStorage.ts
  contexts/         # Shared context providers
    AuthContext.tsx
    ThemeContext.tsx
  types/            # Shared type definitions
    auth.ts
    user.ts
  api/              # API client
    authApi.ts
    userApi.ts
```

**Key Distinction**:
- `components/` = **Reusable** UI components (Button, Input, Modal)
- `pages/` or `views/` = **Top-level** page components that compose reusable components

**Hybrid Structure** (Common in practice):
```
src/
  components/        # Truly shared UI components
    Button.tsx
    Input.tsx
  hooks/            # Truly shared hooks
    useDebounce.ts
  pages/            # Pages with co-located feature-specific code
    auth/
      LoginPage.tsx
      components/LoginForm.tsx
      hooks/useLoginForm.ts
    dashboard/
      DashboardPage.tsx
      components/StatsWidget.tsx
```

**Key Principle**: Consistency over dogma. Match the existing structure unless there's a compelling reason to change.

See reference.md section #2 for detailed patterns.

---

### 1. Understand Domain

- What is the problem domain?
- What are the main UI concepts/interactions?
- What state needs to be managed?
- What are the user flows?
- How does this fit into existing architecture?

### 2. Identify Core Abstractions

Ask for each concept:
- Is this currently a primitive (string, number, boolean)?
- Does it have validation rules?
- Is it a UI concept (component)?
- Is it reusable logic (custom hook)?
- Is it shared state (context)?
- Does it need type safety (branded type)?

### 3. Design Self-Validating Types

For primitives with validation (Email, UserId, Port):

**Option A: Zod Schemas (Recommended)**
```typescript
import { z } from 'zod'

// Schema definition with validation
export const EmailSchema = z.string().email().min(1)
export const UserIdSchema = z.string().uuid()

// Extract type from schema
export type Email = z.infer<typeof EmailSchema>
export type UserId = z.infer<typeof UserIdSchema>

// Validation function
export function validateEmail(value: unknown): Email {
  return EmailSchema.parse(value) // Throws on invalid
}
```

**Option B: Branded Types (TypeScript)**
```typescript
// Brand for nominal typing
declare const __brand: unique symbol
type Brand<T, TBrand> = T & { [__brand]: TBrand }

export type Email = Brand<string, 'Email'>
export type UserId = Brand<string, 'UserId'>

// Validating constructor
export function createEmail(value: string): Email {
  if (!value.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
    throw new Error('Invalid email format')
  }
  return value as Email
}

export function createUserId(value: string): UserId {
  if (!value || value.length === 0) {
    throw new Error('UserId cannot be empty')
  }
  return value as UserId
}
```

**When to use which:**
- Zod: Form validation, API parsing, runtime validation
- Branded types: Type safety without runtime overhead

**Composed types trust their parts** — never re-validate validated types:
```typescript
// ❌ Re-validates after Zod parse
function createUser(email: Email, id: UserId) {
  if (!email.includes('@')) { ... }  // EmailSchema already validated this
}

// ✅ Trusts validated types — only adds own concerns
function createUser(email: Email, id: UserId) {
  // Email and UserId are already validated — no re-validation needed
  return { email, id, createdAt: new Date() }
}
```

### 4. Design Component Structure

**Component Types:**

**A. Presentational Components (Pure UI)**
- No state management
- Props-driven
- Reusable across features
- 100% testable

```typescript
interface ButtonProps {
  readonly label: string
  readonly onClick: () => void
  readonly disabled?: boolean
  readonly variant?: 'primary' | 'secondary'
}

export function Button({
  label,
  onClick,
  variant = 'primary',
  disabled = false
}: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  )
}
```

**B. Container Components (Logic + State)**
- Manage state
- Handle side effects
- Coordinate data fetching
- Compose presentational components

```typescript
import { EMPTY_STRING } from 'consts'

export function LoginContainer() {
  const { login, isLoading, error } = useAuth()
  const [email, setEmail] = useState(EMPTY_STRING)
  const [password, setPassword] = useState(EMPTY_STRING)

  const handleSubmit = async () => {
    try {
      const validEmail = EmailSchema.parse(email)
      await login(validEmail, password)
    } catch (error) {
      // Handle error
    }
  }

  return (
    <LoginForm
      email={email}
      error={error}
      isLoading={isLoading}
      password={password}
      onEmailChange={setEmail}
      onPasswordChange={setPassword}
      onSubmit={handleSubmit}
    />
  )
}
```

### 5. Design Custom Hooks

Extract reusable logic into custom hooks:

```typescript
// Single responsibility: Form state management
export function useFormState<T>(initialValues: T) {
  const [values, setValues] = useState<T>(initialValues)
  const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({})

  const setValue = <K extends keyof T>(key: K, value: T[K]) => {
    setValues(prev => ({ ...prev, [key]: value }))
    setErrors(prev => ({ ...prev, [key]: undefined }))
  }

  const reset = () => {
    setValues(initialValues)
    setErrors({})
  }

  return { values, errors, setValue, setErrors, reset }
}

// Single responsibility: Data fetching
export function useUsers() {
  const [users, setUsers] = useState<User[]>([])
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    const fetchUsers = async () => {
      setIsLoading(true)
      try {
        const data = await api.getUsers()
        setUsers(data)
      } catch (err) {
        setError(err as Error)
      } finally {
        setIsLoading(false)
      }
    }
    fetchUsers()
  }, [])

  return { users, isLoading, error }
}
```

### 6. Design Context for Shared State

When state is needed across 3+ component levels:

```typescript
interface AuthContextValue {
  user: User | null
  login: (email: Email, password: string) =>

Related in Design