Back to skills
SKILL.md
React Patterns
ASecurityReact 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.
- 13 stars
- 0 votes
- 0 copies
- 0 views
- Added September 5, 2026
Works with
Security analysis
100/100Pro scans all 3 files and shows the line behind each finding
npx -y skills add Everyone-Needs-A-Copilot/claude-copilot --skill react-patterns --agent claude-codeAre you the author of React Patterns?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/everyone-needs-a-copilot-react-patterns)---
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.
Files in this skill
- SKILL.md
- scripts/react_patterns.py
- scripts/test_react_patterns.py
Attribution
Comments
Loading comments…