Loading...
Loading...
Review React/TypeScript code for bugs, security vulnerabilities, performance issues, accessibility gaps, and CLAUDE.md workflow compliance. Enforces TypeScript strict mode, GPU-accelerated animations, WCAG AA accessibility, bundle size limits, and surgical simplicity. Use when completing features, before commits, or reviewing pull requests.
npx skill4agent add travisjneuman/.claude generic-react-code-reviewernpm run test # Unit tests
npm run type-check # TypeScript strict mode
npm run lint # ESLint/Prettier
npm run build # Production build// Required tsconfig.json settings
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}anyunknownany| Pattern | Check |
|---|---|
| Props | Interface defined, no |
| State | Typed with Zustand/useState |
| Events | Typed event handlers |
| Refs | |
// P1 Issue: Missing dependencies
useEffect(() => {
fetchData(userId); // userId missing from deps
}, []); // ❌
// Correct
useEffect(() => {
fetchData(userId);
}, [userId]); // ✓react-hooks/exhaustive-deps// Proper typing
const { data } = useQuery<User>({
queryKey: ["user", id],
queryFn: () => fetchUser(id),
});
// Check: staleTime, cacheTime configured
// Check: error boundaries for query failures| Target | Threshold |
|---|---|
| Initial bundle | < 100KB gzipped |
| Lazy-loaded chunks | < 50KB each |
| Total JS | < 300KB |
// Heavy components (>20KB) MUST be lazy loaded
const HeavyChart = lazy(() => import('./HeavyChart'));
const RichTextEditor = lazy(() => import('./RichTextEditor'));
// Wrap in Suspense
<Suspense fallback={<Skeleton />}>
<HeavyChart />
</Suspense>// Expensive calculations
const sortedItems = useMemo(() =>
items.sort((a, b) => a.date - b.date),
[items]
);
// Callback stability for child components
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
// Component memoization (when props are stable)
export const Item = memo(function Item({ data }: Props) {
return <div>{data.name}</div>;
});/* ✓ DO animate */
transform: translateY(-4px);
opacity: 0.5;
/* ❌ NEVER animate */
width, height, margin, padding, top, left// Modal focus trapping
useEffect(() => {
if (isOpen) {
const firstFocusable = modalRef.current?.querySelector("button, input");
firstFocusable?.focus();
}
}, [isOpen]);
// Escape to close
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", handleEscape);
return () => window.removeEventListener("keydown", handleEscape);
}, [onClose]);// Icon-only buttons require labels
<button aria-label="Close modal">
<X className="w-5 h-5" />
</button>
// Dialogs
<div role="dialog" aria-modal="true" aria-labelledby="title">
<h2 id="title">Confirm Action</h2>
</div>