-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
The react-dev plugin enforces opinionated React conventions: functional components only, TypeScript throughout, Vitest + Testing Library for tests, and Tailwind CSS for styling. It provides component/hook scaffolding that follows these patterns consistently. This is valuable because Claude Code generates inconsistent React code without explicit guidance — sometimes class components, sometimes inline styles, sometimes different test frameworks.
Original Intent
Plugin to guide React web development, preferred dependencies and conventions.
Commands
/react-dev:component
Purpose: Generate a new React component following project conventions.
Behavior:
- Ask user for:
- Component name (PascalCase)
- Component type: page, layout, UI component, form, modal/dialog
- Location hint (or auto-detect from existing component paths)
- Detect project conventions by scanning existing components:
- Directory structure:
components/<Name>/(directory) vscomponents/<Name>.tsx(flat) - Styling: Tailwind, CSS Modules, styled-components, or plain CSS
- State management: React context, Zustand, Jotai, Redux
- Export style: named vs default
- Directory structure:
- Determine target path:
- If
src/components/exists → use it - If
app/(Next.js App Router) → useapp/for pages,components/for UI - If
pages/(Next.js Pages Router) → usepages/for pages,components/for UI
- If
- Generate component file:
Conventions enforced:
// src/components/UserProfile/UserProfile.tsx interface UserProfileProps { userId: string; onEdit?: () => void; } export function UserProfile({ userId, onEdit }: UserProfileProps) { return ( <div className="..."> {/* component content */} </div> ); }
- Always functional components (never class)
- Always TypeScript with explicit Props interface
- Always named export (not default, unless Next.js page)
- Props destructured in parameter
- No
React.FC— use plain function with typed props
- Generate associated files based on project patterns:
- Test file:
UserProfile.test.tsxwith Testing Library - Story file:
UserProfile.stories.tsx(if Storybook detected) - Index file:
index.tsbarrel export (if directory-based structure)
- Test file:
- Test file template:
import { render, screen } from "@testing-library/react"; import { describe, test, expect } from "vitest"; import { UserProfile } from "./UserProfile"; describe("UserProfile", () => { test("renders without crashing", () => { render(<UserProfile userId="123" />); // Add assertions based on component content }); });
- Output: list of created files
Edge cases:
- Component already exists → warn and offer to overwrite or pick new name
- No React detected in project → error with suggestion to install React first
- Next.js vs plain React vs Remix → adjust conventions accordingly
/react-dev:hook
Purpose: Generate a custom React hook following best practices.
Behavior:
- Ask user for:
- Hook name (must start with
use) - Purpose description
- What it returns (data, loading state, error, functions)
- Hook name (must start with
- Detect where hooks live:
src/hooks/(most common)src/lib/hooks/(Next.js convention)- Colocated with feature (
src/features/<feature>/hooks/)
- Generate hook file:
Conventions enforced:
// src/hooks/useDebounce.ts import { useState, useEffect } from "react"; interface UseDebounceOptions { delay?: number; } export function useDebounce<T>(value: T, options: UseDebounceOptions = {}) { const { delay = 300 } = options; const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebouncedValue(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debouncedValue; }
- Name starts with
use - Typed with generics where appropriate
- Options object pattern for configuration (not positional args)
- JSDoc with
@exampleusage
- Name starts with
- Generate test file:
import { renderHook, act } from "@testing-library/react"; import { describe, test, expect, vi } from "vitest"; import { useDebounce } from "./useDebounce"; describe("useDebounce", () => { test("returns initial value immediately", () => { const { result } = renderHook(() => useDebounce("hello")); expect(result.current).toBe("hello"); }); test("debounces value changes", async () => { vi.useFakeTimers(); // ... }); });
- Output: list of created files
Common hook patterns to suggest (based on purpose):
| Purpose | Pattern | Key imports |
|---|---|---|
| API data fetching | useQuery wrapper |
useState, useEffect |
| Form state | useForm |
useState, useCallback |
| Local storage | useLocalStorage |
useState, useEffect |
| Media query | useMediaQuery |
useState, useEffect |
| Intersection observer | useInView |
useRef, useEffect |
| Keyboard shortcut | useHotkey |
useEffect |
| Previous value | usePrevious |
useRef, useEffect |
Edge cases:
- Hook name doesn't start with
use→ auto-prefix and inform user - Complex return type → suggest returning an object (not tuple) for readability
/react-dev:test
Purpose: Run React component tests and identify coverage gaps.
Behavior:
- Detect test framework:
vitestin devDependencies →bunx vitest runornpx vitest runjestin devDependencies →bunx jestornpx jest- Neither → suggest installing vitest
- Run tests:
bunx vitest run --reporter=verbose
- Analyze results:
- Show pass/fail summary
- For failures: show component name, test name, assertion error, file:line
- Run coverage:
bunx vitest run --coverage
- Identify untested components:
- Scan
src/components/**/*.tsxandsrc/hooks/**/*.ts - Check for corresponding
*.test.tsx/*.test.ts - List untested files:
Untested Components: - src/components/Header/Header.tsx - src/components/Sidebar/Sidebar.tsx Untested Hooks: - src/hooks/useAuth.ts
- Scan
- Offer to generate test stubs for untested components
Testing best practices to include in command doc:
- Query by role, label, or text (not test-id) — accessible selectors
userEventoverfireEventfor realistic interactions- Test behavior, not implementation details
- Don't test implementation details (internal state, hooks directly)
Edge cases:
- No tests exist → offer to scaffold test files for all components
- Jest + Enzyme → suggest migration to Vitest + Testing Library
- Tests reference DOM elements that don't exist → suggest checking component render
Hooks
None — this plugin operates through commands only.
File Manifest
| File | Est. Lines | Purpose |
|---|---|---|
commands/component.md |
100-120 | Generate React component |
commands/hook.md |
90-110 | Generate React hook |
commands/test.md |
80-100 | Run tests and check coverage |
README.md |
170-210 | Full plugin documentation |
.claude-plugin/plugin.json |
15-20 | Plugin manifest |
README Outline
-
Overview — Opinionated React: functional, TypeScript, Vitest, Tailwind
-
Quick Start — Installation + generating first component
-
Commands — Table with all 3 commands
-
Conventions
Aspect Convention Rationale Components Functional only Hooks supersede class lifecycle Language TypeScript Type safety, better DX Styling Tailwind CSS Utility-first, no CSS file sprawl Testing Vitest + Testing Library Fast, accessible queries Exports Named (not default) Better refactoring, grep-able Props Interface, not type Extensible, better error messages State Zustand or Context Simple, no boilerplate -
Component Patterns — Page, Layout, UI, Form, Modal templates
-
Hook Patterns — Common custom hook patterns with examples
-
Testing Guide — Query priority, userEvent, what to test
-
Recommended Libraries
Need Package Notes Routing react-router/ Next.jsFramework-dependent Forms react-hook-formUncontrolled, performant Validation zodRuntime + TypeScript inference Data fetching @tanstack/react-queryCaching, dedup, background refresh Animation framer-motionDeclarative, layout animations Icons lucide-reactTree-shakeable, consistent UI components shadcn/uiCopy-paste, not dependency
Prerequisites
- React 18+ project with TypeScript
vitest+@testing-library/react(plugin will suggest installation if missing)
Quality Checklist
- Each command .md is 60+ lines with concrete steps
- README is 100+ lines with examples and reference tables
- Convention table explains rationale for each choice
- Component and hook templates are production-ready
- Plugin provides clear value beyond Claude's defaults (consistent conventions + testing)