Install
$ agentstack add skill-everyone-needs-a-copilot-claude-copilot-react-patterns ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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
// 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 (
{label}
);
}
// BAD: Class component (outdated)
class Button extends React.Component { }
Composition Over Props Drilling
// GOOD: Compound components
function Card({ children }: { children: React.ReactNode }) {
return {children};
}
Card.Header = function CardHeader({ children }: { children: React.ReactNode }) {
return {children};
};
Card.Body = function CardBody({ children }: { children: React.ReactNode }) {
return {children};
};
// Usage
Title
Content
Render Props / Children as Function
// GOOD: Flexible render pattern
interface DataFetcherProps {
url: string;
children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode;
}
function DataFetcher({ url, children }: DataFetcherProps) {
const { data, loading, error } = useFetch(url);
return <>{children(data, loading, error)};
}
// Usage
url="/api/users">
{(users, loading, error) => {
if (loading) return ;
if (error) return ;
return ;
}}
Hooks Best Practices
useState
// GOOD: Typed state
const [user, setUser] = useState(null);
const [items, setItems] = useState([]);
// 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
// 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
// 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
// GOOD: Extract reusable logic
function useLocalStorage(key: string, initialValue: T) {
const [value, setValue] = useState(() => {
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
// BAD: Index key causes render issues
{items.map((item, index) => (
// Re-renders all on reorder!
))}
// GOOD: Stable unique key
{items.map(item => (
))}
Props in State
// 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
// 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
// BAD: New object/function on every render
handleClick(id)} // New function every render!
/>
// GOOD: Stable references
const config = useMemo(() => ({ theme: 'dark' }), []);
const handleChildClick = useCallback(() => handleClick(id), [id]);
State Management
Context for Global State
// GOOD: Typed context with provider
interface AuthContextType {
user: User | null;
login: (credentials: Credentials) => Promise;
logout: () => void;
}
const AuthContext = createContext(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
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:
key={index}(index used as key in map)- Missing
keyprop in.map(JSX render (JSX element withoutkey=on same or following lines) - 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):
python .claude/skills/code/react-patterns/scripts/react_patterns.py path/to/Component.tsx
Run via Bash (stdin — paste code or pipe):
cat path/to/Component.jsx | python .claude/skills/code/react-patterns/scripts/react_patterns.py -
The script outputs:
- A JSON object with
findings(list of anti-patterns with rule, severity, line, message) and asummaryof counts by severity. - A human-readable markdown table sorted by severity descending.
Detected rules:
INDEX_AS_KEYHIGH —key={index}orkey={i}in map callback (causes incorrect reconciliation on reorder)MISSING_KEYMEDIUM —.map(call with JSX element that has nokey=prop within 5 linesHOOK_IN_CONDITIONALHIGH — Hook call (use...()) inside anifstatement 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:
- HIGH findings (INDEXASKEY, HOOKINCONDITIONAL) must be fixed before merge.
- MISSING_KEY findings may be false positives for static lists — verify that the list is truly static before dismissing.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Everyone-Needs-A-Copilot
- Source: Everyone-Needs-A-Copilot/claude-copilot
- License: MIT
- Homepage: https://ineedacopilot.com
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.