React hooks, composition patterns, state management, and performance optimization reference. Use when reviewing, scaffolding, or refactoring React components.
From frontend-devnpx claudepluginhub bailejl/dev-plugins --plugin frontend-devThis skill uses the workspace's default tool permissions.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Orchestrates subagents to execute phased plans: deploys for implementation, verification, anti-pattern checks, code quality review, and commits only after passing checks.
Reference guide for React patterns, hooks, composition strategies, state management, and performance optimization. Use this knowledge when reviewing, scaffolding, or refactoring React components.
Manages local component state.
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
Guidelines:
useState<string[]>([]) not useState([])setCount(c => c + 1)useMemouseReducer over 4+ related useState callsSynchronizes a component with an external system (APIs, subscriptions, DOM).
useEffect(() => {
const controller = new AbortController();
fetchData(id, { signal: controller.signal }).then(setData);
return () => controller.abort();
}, [id]);
Guidelines:
useState(() => computeInitial(props))Memoizes a function reference to prevent unnecessary re-renders of children.
const handleClick = useCallback((id: string) => {
setSelected(id);
}, []);
Guidelines:
React.memo)useCallback over inline arrow functions in JSX only when it matters for performanceMemoizes an expensive computation.
const sortedItems = useMemo(
() => items.slice().sort(compareFn),
[items, compareFn]
);
Guidelines:
Holds a mutable value that doesn't trigger re-renders, or references a DOM element.
const inputRef = useRef<HTMLInputElement>(null);
const timerRef = useRef<number | null>(null);
Guidelines:
.current during rendering (except for initialization)useRef<T>(null) for DOM refs, useRef<T>(initialValue) for mutable valuesManages complex state with explicit state transitions.
type Action =
| { type: 'FETCH_START' }
| { type: 'FETCH_SUCCESS'; data: Item[] }
| { type: 'FETCH_ERROR'; error: string };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_SUCCESS':
return { ...state, loading: false, data: action.data };
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.error };
}
}
Guidelines:
Extract reusable stateful logic into custom hooks.
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
Guidelines:
use to follow rules of hooks{ data, error, loading }renderHook from Testing LibraryPass a function as a prop (or children) to share stateful logic.
<DataFetcher url="/api/users">
{({ data, loading, error }) => (
loading ? <Spinner /> : <UserList users={data} />
)}
</DataFetcher>
When to use: When the consumer needs full control over rendering. Less common now that hooks exist — prefer custom hooks for most cases.
Wrap a component to add behavior.
function withAuth<P>(Component: React.ComponentType<P>) {
return function AuthWrapper(props: P) {
const { user } = useAuth();
if (!user) return <Redirect to="/login" />;
return <Component {...props} />;
};
}
When to use: Cross-cutting concerns (auth, logging, error boundaries) that apply to many components. Prefer hooks for most cases — HOCs add wrapper elements and can obscure component identity.
Related components that share implicit state.
<Tabs defaultValue="tab1">
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab1">Content 1</Tabs.Content>
<Tabs.Content value="tab2">Content 2</Tabs.Content>
</Tabs>
When to use: Component families that share state (tabs, accordions, menus, select). Implement with React Context to share state between compound parts.
Accept named render areas via props.
<Card
header={<CardHeader title="Title" />}
footer={<CardFooter actions={actions} />}
>
Card body content
</Card>
When to use: Layout components with well-defined regions. Clearer than deeply nested children.
// Controlled — parent owns the state
<Input value={name} onChange={setName} />
// Uncontrolled — component owns its state
<Input defaultValue="initial" ref={inputRef} />
Guidelines:
value+onChange and defaultValueUse when: State is used by one component or its immediate children. This is the default — start here.
Use when: Sibling components need the same state. Move state to the nearest common ancestor and pass down via props.
Use when: State needs to be accessed by deeply nested components (theme, auth, locale). Not a replacement for prop passing in shallow trees.
const ThemeContext = createContext<Theme>(defaultTheme);
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(defaultTheme);
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
Guidelines:
<ThemeProvider> doesn't need to include user and cartChoose based on the type of state:
Prevents re-renders when props haven't changed.
const ExpensiveList = React.memo(function ExpensiveList({ items }: Props) {
return items.map(item => <ExpensiveItem key={item.id} item={item} />);
});
Guidelines:
useMemo/useCallback)Split code at route boundaries or for heavy components.
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Dashboard />
</Suspense>
);
}
Render only visible items in long lists.
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
// render only virtualizer.getVirtualItems()
}
Use when: Lists have 100+ items. Don't virtualize short lists — the overhead isn't worth it.
// Force remount with key change (reset component state)
<UserProfile key={userId} userId={userId} />
// Stable keys for lists (never use array index for reorderable lists)
{items.map(item => <Item key={item.id} item={item} />)}
Catch rendering errors in a subtree.
class ErrorBoundary extends React.Component<Props, State> {
state = { hasError: false, error: null };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
logErrorToService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <DefaultErrorUI error={this.state.error} />;
}
return this.props.children;
}
}
Guidelines:
'use client')// Server Component (default)
async function UserProfile({ userId }: { userId: string }) {
const user = await db.users.find(userId); // direct DB access
return (
<div>
<h1>{user.name}</h1>
<LikeButton userId={userId} /> {/* Client component */}
</div>
);
}
// Client Component
'use client';
function LikeButton({ userId }: { userId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>Like</button>;
}
Guidelines:
'use client' boundaries as low in the tree as possible'use server' for server actions (form mutations)