Review React hook usage for React 19 compliance and best practices
Reviews React hooks for React 19 compliance, flagging deprecated APIs like forwardRef and validating new hooks (use(), useActionState, useOptimistic) against best practices and security requirements.
/plugin marketplace add djankies/claude-configs/plugin install react-19@claude-configsThis skill is limited to using the following tools:
Reviews React hook patterns for React 19 compliance, deprecated APIs, and best practices.
Activates: Code reviews, compliance validation, React 19 migration checks, hook-related PRs
Scope: New hooks (use(), useActionState, useOptimistic), deprecated patterns (forwardRef, propTypes, defaultProps), hook rules, best practices, TypeScript compliance
Focus: Correctness, security (Server Actions), performance (re-renders), React 19 compliance
Use Grep (output_mode: "content"):
forwardRef\.propTypes\s*= (removed in React 19)\.defaultProps\s*= (deprecated on function components)use() API
useActionState
useOptimistic
Standard Hooks (useState, useEffect, etc.)
useRef requires initial value:
// ✅ Correct
const ref = useRef<HTMLDivElement>(null);
// ❌ Incorrect (React 19)
const ref = useRef<HTMLDivElement>();
Ref as prop typed correctly:
// ✅ Correct
interface Props {
ref?: Ref<HTMLButtonElement>;
}
// ❌ Incorrect (using forwardRef)
const Comp = forwardRef<HTMLButtonElement, Props>(...);
Array index as key:
// ❌ Bad
{
items.map((item, index) => <div key={index}>{item}</div>);
}
// ✅ Good
{
items.map((item) => <div key={item.id}>{item}</div>);
}
Direct state mutation:
// ❌ Bad
const [items, setItems] = useState([]);
items.push(newItem);
setItems(items);
// ✅ Good
setItems([...items, newItem]);
Missing dependencies:
// ❌ Bad
useEffect(() => {
fetchData(userId);
}, []);
// ✅ Good
useEffect(() => {
fetchData(userId);
}, [userId]);
Missing cleanup:
// ❌ Bad
useEffect(() => {
const timer = setInterval(() => {}, 1000);
}, []);
// ✅ Good
useEffect(() => {
const timer = setInterval(() => {}, 1000);
return () => clearInterval(timer);
}, []);
List correct React 19 usage, good practices, proper hooks.
Deprecated APIs still functional but require migration: forwardRef, manual memoization (when React Compiler available), patterns with better React 19 alternatives.
Migration paths, performance improvements, best practices, relevant skill references.
Code:
import { useState } from 'react';
function ContactForm() {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
async function handleSubmit(e) {
e.preventDefault();
await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify({ email, message }),
});
}
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<textarea value={message} onChange={(e) => setMessage(e.target.value)} />
<button type="submit">Send</button>
</form>
);
}
useActionState with server validation instead of client-side fetchisPending for UX feedback'use client';
import { useActionState } from 'react';
import { submitContact } from './actions';
function ContactForm() {
const [state, formAction, isPending] = useActionState(submitContact, null);
return (
<form action={formAction}>
<input name="email" type="email" required />
<textarea name="message" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Sending...' : 'Send'}
</button>
{state?.error && <p className="error">{state.error}</p>}
{state?.success && <p>Message sent!</p>}
</form>
);
}
Server Action (actions.js):
'use server';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
message: z.string().min(10),
});
export async function submitContact(previousState, formData) {
const data = {
email: formData.get('email'),
message: formData.get('message'),
};
const result = schema.safeParse(data);
if (!result.success) return { error: 'Invalid input' };
try {
await db.contacts.create({ data: result.data });
return { success: true };
} catch (error) {
return { error: 'Failed to send message' };
}
}
Code:
import { forwardRef } from 'react';
const Button = forwardRef((props, ref) => (
<button ref={ref} {...props}>
{props.children}
</button>
));
Button.displayName = 'Button';
Deprecated forwardRef Usage — Still functional in React 19 but deprecated. Migrate to ref-as-prop pattern.
function Button({ children, ref, ...props }) {
return (
<button ref={ref} {...props}>
{children}
</button>
);
}
Benefits: Simpler API, better TypeScript inference, follows React 19 patterns, less boilerplate.
MUST Flag:
use() with PromisesSHOULD Flag:
MAY Suggest:
NEVER:
use() + Promises: Suspense + Error Boundaryuse() + Context: appropriate usageuseActionState: Server Actions validate inputsuseOptimistic: paired with startTransitionuseFormStatus: inside form componentsuseRef has initial valueRef<HTMLElement>dangerouslySetInnerHTML sanitized| Issue | Search Pattern | Fix |
|---|---|---|
| forwardRef usage | forwardRef | Convert to ref-as-prop |
| propTypes | \.propTypes\s*= | Remove (use TypeScript) |
| defaultProps | \.defaultProps\s*= | Use ES6 defaults |
| Missing dependencies | Manual review | Add to dependency array |
| Array index keys | key={.*index} | Use stable ID |
| Direct mutation | Manual review | Use immutable updates |
| use() without Suspense | Manual review | Add Suspense boundary |
| Server Action no validation | Manual review | Add zod/yup validation |
For comprehensive React 19 patterns and migration guides, see: research/react-19-comprehensive.md