React component patterns, hooks, anti-patterns, and quality rules — with deterministic regex-based React anti-pattern detection for JSX, TSX, useState, useEffect, conditional hooks, key-prop anti-patterns, and context usage. Use proactively when reviewing React component files (.jsx, .tsx), enforcing hooks rules (no conditional hooks), catching key-prop anti-patterns in list rendering, or running a pre-review scan of React codebases. Run the checker for deterministic scanning.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add Everyone-Needs-A-Copilot/claude-copilot --skill react-patterns --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of React Patterns?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/everyone-needs-a-copilot-react-patterns)More formats (shields.io, HTML) on the badges page.
---
name: react-patterns
skill_category: code
description: >-
React component patterns, hooks, anti-patterns, and quality rules — with
deterministic regex-based React anti-pattern detection for JSX, TSX, useState,
useEffect, conditional hooks, key-prop anti-patterns, and context usage. Use
proactively when reviewing React component files (.jsx, .tsx), enforcing hooks
rules (no conditional hooks), catching key-prop anti-patterns in list
rendering, or running a pre-review scan of React codebases. Run the checker
for deterministic scanning.
version: 2.0.0
source: converted from .claude/skills/code/react-patterns.md (2026-05-20); L3 checker added
when_to_use:
- Reviewing React component files (.jsx, .tsx)
- Enforcing hooks rules (no conditional hooks)
- Catching key-prop anti-patterns in list rendering
- Pre-review scan of React codebases
allowed-tools:
- Read
- Grep
- Glob
- Bash
tags: [react, hooks, components, jsx, frontend, ui, patterns]
related_skills: [javascript-patterns, testing-patterns]
trigger_files: ["*.tsx", "*.jsx", "**/components/**", "**/hooks/**"]
trigger_keywords: [react, hooks, useState, useEffect, component, jsx, props, context]
---
# React Patterns
Modern React patterns, hooks best practices, anti-patterns, and quality rules.
## Core Principles
| Principle | Description |
|-----------|-------------|
| **Composition** | Small, focused components over inheritance |
| **Unidirectional** | Data flows down, events flow up |
| **Declarative** | Describe what, not how |
| **Hooks** | Functional components with hooks over class components |
## Component Patterns
### Functional Components
```tsx
// GOOD: Typed functional component
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
export function Button({
label,
onClick,
variant = 'primary',
disabled = false,
}: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{label}
</button>
);
}
// BAD: Class component (outdated)
class Button extends React.Component { }
```
### Composition Over Props Drilling
```tsx
// GOOD: Compound components
function Card({ children }: { children: React.ReactNode }) {
return <div className="card">{children}</div>;
}
Card.Header = function CardHeader({ children }: { children: React.ReactNode }) {
return <div className="card-header">{children}</div>;
};
Card.Body = function CardBody({ children }: { children: React.ReactNode }) {
return <div className="card-body">{children}</div>;
};
// Usage
<Card>
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
</Card>
```
### Render Props / Children as Function
```tsx
// GOOD: Flexible render pattern
interface DataFetcherProps<T> {
url: string;
children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode;
}
function DataFetcher<T>({ url, children }: DataFetcherProps<T>) {
const { data, loading, error } = useFetch<T>(url);
return <>{children(data, loading, error)}</>;
}
// Usage
<DataFetcher<User[]> url="/api/users">
{(users, loading, error) => {
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <UserList users={users!} />;
}}
</DataFetcher>
```
## Hooks Best Practices
### useState
```tsx
// GOOD: Typed state
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<Item[]>([]);
// GOOD: Functional updates for derived state
setCount(prev => prev + 1);
setItems(prev => [...prev, newItem]);
// BAD: Object mutation
setUser({ ...user, name: 'New' }); // Works but...
user.name = 'New'; // NEVER mutate directly!
setUser(user);
```
### useEffect
```tsx
// GOOD: Proper dependency array
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
const response = await fetch(url, { signal: controller.signal });
setData(await response.json());
} catch (error) {
if (!controller.signal.aborted) {
setError(error as Error);
}
}
}
fetchData();
return () => controller.abort();
}, [url]); // Re-run when url changes
// BAD: Missing dependencies
useEffect(() => {
fetchData(userId); // userId not in deps - stale closure!
}, []);
// BAD: Object/array in deps (always new reference)
useEffect(() => {
doSomething(options);
}, [options]); // Infinite loop if options = {} inline
```
### useMemo / useCallback
```tsx
// GOOD: Expensive computation
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// GOOD: Stable callback for child props
const handleClick = useCallback(
(id: string) => {
onSelect(id);
},
[onSelect]
);
// BAD: Premature optimization
const double = useMemo(() => value * 2, [value]); // Too simple
// BAD: No deps for callback
const handleClick = useCallback(() => {
onSelect(selectedId); // selectedId is stale!
}, []);
```
### Custom Hooks
```tsx
// GOOD: Extract reusable logic
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
// Usage
const [theme, setTheme] = useLocalStorage('theme', 'light');
```
## Anti-Patterns to Avoid
### Index as Key
```tsx
// BAD: Index key causes render issues
{items.map((item, index) => (
<Item key={index} data={item} /> // Re-renders all on reorder!
))}
// GOOD: Stable unique key
{items.map(item => (
<Item key={item.id} data={item} />
))}
```
### Props in State
```tsx
// BAD: Copying props to state
function UserProfile({ user }: { user: User }) {
const [userData, setUserData] = useState(user); // Stale!
// ...
}
// GOOD: Derive from props or use effect to sync
function UserProfile({ user }: { user: User }) {
const displayName = user.name.toUpperCase(); // Derive directly
// ...
}
```
### useEffect for Transforms
```tsx
// BAD: Effect for derived data
const [items, setItems] = useState([]);
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(items.filter(i => i.active));
}, [items]);
// GOOD: Compute directly or useMemo
const filtered = useMemo(
() => items.filter(i => i.active),
[items]
);
```
### Excessive Re-renders
```tsx
// BAD: New object/function on every render
<Child
config={{ theme: 'dark' }} // New object every render!
onClick={() => handleClick(id)} // New function every render!
/>
// GOOD: Stable references
const config = useMemo(() => ({ theme: 'dark' }), []);
const handleChildClick = useCallback(() => handleClick(id), [id]);
<Child config={config} onClick={handleChildClick} />
```
## State Management
### Context for Global State
```tsx
// GOOD: Typed context with provider
interface AuthContextType {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | null>(null);
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
```
### Reducer for Complex State
```tsx
type Action =
| { type: 'SET_LOADING' }
| { type: 'SET_DATA'; payload: Data }
| { type: 'SET_ERROR'; payload: Error };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'SET_LOADING':
return { ...state, loading: true, error: null };
case 'SET_DATA':
return { ...state, loading: false, data: action.payload };
case 'SET_ERROR':
return { ...state, loading: false, error: action.payload };
}
}
```
## Quality Checklist
| Check | Rule |
|-------|------|
| Unique keys | Never use index as key for dynamic lists |
| Hook deps | All dependencies listed, no lint suppressions |
| No props in state | Derive from props directly |
| Stable callbacks | useCallback for event handlers passed to children |
| Error boundaries | Wrap feature sections |
| Typed props | Interface for all component props |
| Controlled inputs | value + onChange, not defaultValue |
| Cleanup effects | Return cleanup function for subscriptions |
| Memoize expensive | useMemo for costly computations only |
| Custom hooks | Extract reusable stateful logic |
## Invocation — React Anti-Pattern Checker (L3 Script)
Run the checker on any JSX or TSX file. Consume its **output only** — the script source never enters context.
**Scope note:** This is a regex-based structural checker, not a full JSX/AST parser. It reliably catches three closed-set patterns:
1. `key={index}` (index used as key in map)
2. Missing `key` prop in `.map(` JSX render (JSX element without `key=` on same or following lines)
3. Hooks called inside conditional blocks (`if`/`&&`/ternary body)
It does NOT detect prop-drilling depth (requires component tree analysis), missing useEffect dependencies (requires full scope analysis), or render performance issues. Use eslint-plugin-react-hooks for those.
**Run via Bash (file argument):**
```bash
python .claude/skills/code/react-patterns/scripts/react_patterns.py path/to/Component.tsx
```
**Run via Bash (stdin — paste code or pipe):**
```bash
cat path/to/Component.jsx | python .claude/skills/code/react-patterns/scripts/react_patterns.py -
```
**The script outputs:**
1. A JSON object with `findings` (list of anti-patterns with rule, severity, line, message) and a `summary` of counts by severity.
2. A human-readable markdown table sorted by severity descending.
**Detected rules:**
- `INDEX_AS_KEY` HIGH — `key={index}` or `key={i}` in map callback (causes incorrect reconciliation on reorder)
- `MISSING_KEY` MEDIUM — `.map(` call with JSX element that has no `key=` prop within 5 lines
- `HOOK_IN_CONDITIONAL` HIGH — Hook call (`use...()`) inside an `if` statement or `&&` expression (violates Rules of Hooks)
**Error handling:** Script exits 1 on unreadable file. Exits 0 even if findings are present.
**What the agent does with the output:**
1. HIGH findings (INDEX_AS_KEY, HOOK_IN_CONDITIONAL) must be fixed before merge.
2. MISSING_KEY findings may be false positives for static lists — verify that the list is truly static before dismissing.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!