Linter-driven refactoring patterns to reduce complexity and improve code quality in React/TypeScript. Use when ESLint fails with SonarJS complexity issues (cognitive, cyclomatic, expression) or when code feels hard to read/maintain. Applies component extraction, hook extraction, and simplification patterns.
Automatically refactors React/TypeScript code to fix ESLint/SonarJS complexity issues. Applies component extraction, hook extraction, and simplification patterns when linter fails or code is hard to maintain.
/plugin marketplace add buzzdan/ai-coding-rules/plugin install ts-react-linter-driven-development@ai-coding-rulesThis skill inherits all available tools. When active, it can use any tool Claude has access to.
reference.mdLinter-driven refactoring patterns to reduce complexity and improve React code quality.
Run npm run lintcheck and analyze failures:
src/features/auth/LoginForm.tsx:45:1: Cognitive Complexity of 18 exceeds max of 15
src/features/users/UserList.tsx:120:5: Cyclomatic Complexity of 12 exceeds max of 10
src/components/DataTable.tsx:89:1: Function has 250 lines, max is 200
For each failure, ask:
Choose appropriate pattern:
npm run lintchecknpm testSignal: Component mixing UI with complex logic
// ❌ Before - Complex logic in component (Cognitive Complexity: 18)
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
const fetchUser = async () => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) {
throw new Error('Failed to fetch user')
}
const data = await response.json()
setUser(data)
} catch (err) {
setError(err as Error)
} finally {
setIsLoading(false)
}
}
fetchUser()
}, [userId])
if (isLoading) return <Spinner />
if (error) return <ErrorMessage error={error} />
if (!user) return <NotFound />
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
// ✅ After - Logic extracted to hook (Component Complexity: 4)
function useUser(userId: string) {
const [user, setUser] = useState<User | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
const fetchUser = async () => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) throw new Error('Failed to fetch user')
setUser(await response.json())
} catch (err) {
setError(err as Error)
} finally {
setIsLoading(false)
}
}
fetchUser()
}, [userId])
return { user, isLoading, error }
}
function UserProfile({ userId }: { userId: string }) {
const { user, isLoading, error } = useUser(userId)
if (isLoading) return <Spinner />
if (error) return <ErrorMessage error={error} />
if (!user) return <NotFound />
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
Signal: Component > 200 lines, doing too much
// ❌ Before - Large component (250 lines, Cognitive Complexity: 22)
function UserDashboard() {
const [users, setUsers] = useState([])
const [selectedUser, setSelectedUser] = useState(null)
const [isEditing, setIsEditing] = useState(false)
const [searchTerm, setSearchTerm] = useState('')
// ... 200+ lines of logic and JSX
return (
<div>
{/* Search bar */}
<input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
{/* User list */}
<ul>
{users.filter(u => u.name.includes(searchTerm)).map(user => (
<li key={user.id} onClick={() => setSelectedUser(user)}>
{user.name} - {user.email}
<button onClick={() => setIsEditing(true)}>Edit</button>
<button onClick={() => deleteUser(user.id)}>Delete</button>
</li>
))}
</ul>
{/* User detail */}
{selectedUser && (
<div>
{isEditing ? (
<form>...</form>
) : (
<div>...</div>
)}
</div>
)}
</div>
)
}
// ✅ After - Broken into focused components
function UserDashboard() {
const [selectedUser, setSelectedUser] = useState<User | null>(null)
return (
<div>
<UserSearch />
<UserList onSelectUser={setSelectedUser} />
{selectedUser && <UserDetail user={selectedUser} />}
</div>
)
}
function UserSearch() {
const [searchTerm, setSearchTerm] = useState('')
// Search logic
return <input value={searchTerm} onChange={...} />
}
function UserList({ onSelectUser }: { onSelectUser: (user: User) => void }) {
const { users } = useUsers()
return (
<ul>
{users.map(user => (
<UserListItem key={user.id} user={user} onSelect={onSelectUser} />
))}
</ul>
)
}
function UserListItem({ user, onSelect }: UserListItemProps) {
return (
<li onClick={() => onSelect(user)}>
<span>{user.name}</span>
<UserActions user={user} />
</li>
)
}
Signal: Deeply nested conditionals, cyclomatic complexity high
// ❌ Before - Deep nesting (Cyclomatic Complexity: 12, Nesting: 5)
function validateAndSubmit(data: FormData) {
if (data) {
if (data.email) {
if (isValidEmail(data.email)) {
if (data.password) {
if (data.password.length >= 8) {
if (data.terms) {
return submitForm(data)
} else {
return { error: 'Must accept terms' }
}
} else {
return { error: 'Password too short' }
}
} else {
return { error: 'Password required' }
}
} else {
return { error: 'Invalid email' }
}
} else {
return { error: 'Email required' }
}
}
return { error: 'No data' }
}
// ✅ After - Early returns (Cyclomatic Complexity: 7, Nesting: 1)
function validateAndSubmit(data: FormData) {
if (!data) return { error: 'No data' }
if (!data.email) return { error: 'Email required' }
if (!isValidEmail(data.email)) return { error: 'Invalid email' }
if (!data.password) return { error: 'Password required' }
if (data.password.length < 8) return { error: 'Password too short' }
if (!data.terms) return { error: 'Must accept terms' }
return submitForm(data)
}
// ✅ Even better - Use Zod schema
const FormDataSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
terms: z.boolean().refine(val => val === true, 'Must accept terms')
})
function validateAndSubmit(data: unknown) {
const result = FormDataSchema.safeParse(data)
if (!result.success) {
return { error: result.error.errors[0].message }
}
return submitForm(result.data)
}
Signal: Complex boolean expressions, expression complexity > 5
// ❌ Before - Complex condition (Expression Complexity: 8)
if (
user &&
user.isActive &&
!user.isBanned &&
user.subscription &&
user.subscription.status === 'active' &&
user.subscription.expiresAt > Date.now() &&
(user.roles.includes('admin') || user.roles.includes('moderator'))
) {
// Allow access
}
// ✅ After - Extracted to helper functions
function hasActiveSubscription(user: User): boolean {
return (
user.subscription?.status === 'active' &&
user.subscription.expiresAt > Date.now()
)
}
function hasModeratorAccess(user: User): boolean {
return user.roles.includes('admin') || user.roles.includes('moderator')
}
function canAccessFeature(user: User): boolean {
return (
user.isActive &&
!user.isBanned &&
hasActiveSubscription(user) &&
hasModeratorAccess(user)
)
}
if (user && canAccessFeature(user)) {
// Allow access
}
// ✅ Or extract to variables
const isUserValid = user.isActive && !user.isBanned
const hasSubscription = hasActiveSubscription(user)
const isModerator = hasModeratorAccess(user)
if (user && isUserValid && hasSubscription && isModerator) {
// Allow access
}
Signal: react/no-unstable-nested-components
// ❌ Before - Component defined inside component
function UserList() {
const users = useUsers()
// ❌ Recreated on every render
const UserCard = ({ user }: { user: User }) => (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)
return (
<div>
{users.map(user => <UserCard key={user.id} user={user} />)}
</div>
)
}
// ✅ After - Component extracted
function UserCard({ user }: { user: User }) {
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)
}
function UserList() {
const users = useUsers()
return (
<div>
{users.map(user => <UserCard key={user.id} user={user} />)}
</div>
)
}
Signal: react-hooks/exhaustive-deps warnings, complex useEffect
// ❌ Before - Complex dependencies
function SearchResults({ initialQuery, filters, sortBy }: Props) {
const [results, setResults] = useState([])
useEffect(() => {
const fetchResults = async () => {
const response = await api.search({
query: initialQuery,
filters: filters,
sort: sortBy,
page: 1
})
setResults(response.data)
}
fetchResults()
}, [initialQuery, filters, sortBy, filters.category, filters.price]) // ❌ Duplicates, object deps
}
// ✅ After - Simplified with custom hook
function useSearchResults(query: string, filters: Filters, sortBy: string) {
const [results, setResults] = useState([])
// Stable object reference
const searchParams = useMemo(
() => ({ query, filters, sort: sortBy, page: 1 }),
[query, filters, sortBy]
)
useEffect(() => {
api.search(searchParams).then(response => setResults(response.data))
}, [searchParams])
return results
}
function SearchResults({ initialQuery, filters, sortBy }: Props) {
const results = useSearchResults(initialQuery, filters, sortBy)
return <ResultsList results={results} />
}
Signal: Complex validation in components
// ❌ Before - Validation scattered in component
function LoginForm() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [errors, setErrors] = useState({})
const handleSubmit = () => {
const newErrors = {}
if (!email) {
newErrors.email = 'Email required'
} else if (!/\S+@\S+\.\S+/.test(email)) {
newErrors.email = 'Invalid email'
}
if (!password) {
newErrors.password = 'Password required'
} else if (password.length < 8) {
newErrors.password = 'Password too short'
}
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors)
return
}
submitLogin(email, password)
}
// ...JSX
}
// ✅ After - Validation with Zod
import { z } from 'zod'
const LoginSchema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be at least 8 characters')
})
function LoginForm() {
const { values, errors, setValue, handleSubmit } = useFormValidation(
LoginSchema,
{ email: '', password: '' },
submitLogin
)
return (
<form onSubmit={handleSubmit}>
<Input
label='Email'
value={values.email}
onChange={e => setValue('email', e.target.value)}
error={errors.email}
/>
<Input
label='Password'
type='password'
value={values.password}
onChange={e => setValue('password', e.target.value)}
error={errors.password}
/>
<button type='submit'>Login</button>
</form>
)
}
When linter fails, follow this decision tree:
Linter Failure
├─ Cognitive Complexity > 15
│ ├─ Mixed abstractions? → Extract custom hooks
│ ├─ Complex conditions? → Extract to helper functions
│ └─ Deep nesting? → Early returns, guard clauses
│
├─ Cyclomatic Complexity > 10
│ ├─ Many branches? → Early returns
│ ├─ Complex switch? → Use object mapping or extract functions
│ └─ Multiple &&/|| chains? → Extract conditions to variables
│
├─ Expression Complexity > 5
│ ├─ Long boolean expressions? → Extract to variables
│ └─ Nested ternaries? → Extract to function or if statements
│
├─ Max Lines Per Function > 200
│ ├─ Large component? → Extract smaller components
│ ├─ Complex logic? → Extract custom hooks
│ └─ Mixed concerns? → Separate UI from business logic
│
├─ Nested Control Flow > 4
│ └─ Deep nesting? → Early returns, guard clauses
│
└─ React-specific
├─ no-unstable-nested-components → Extract component definition
├─ no-multi-comp → Split into separate files
└─ exhaustive-deps → Simplify dependencies, extract logic
See reference.md for detailed principles:
npm run lintchecknpm testSee reference.md for complete refactoring patterns and decision trees.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.