This skill should be used when writing, reviewing, or refactoring React code. It covers React 19 patterns (server/client components, Actions, use() hook), component composition, custom hooks, state management with Zustand and urql, and performance optimization.
From mnpx claudepluginhub molcajeteai/plugin --plugin mThis skill uses the workspace's default tool permissions.
references/component-patterns.mdreferences/hooks.mdreferences/performance.mdreferences/react-19.mdreferences/state-management.mdSearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
Quick reference for writing production-quality React code. Each section summarizes the key rules — reference files provide full examples and edge cases.
Components are server components by default in frameworks that support them. Use "use client" only when the component needs browser APIs, hooks, or event handlers.
// Server Component — no directive needed
async function DoctorList() {
const doctors = await getDoctors();
return <ul>{doctors.map((d) => <DoctorCard key={d.id} doctor={d} />)}</ul>;
}
// Client Component — needs interactivity
"use client";
function BookButton({ doctorId }: { doctorId: string }) {
const [isPending, setIsPending] = useState(false);
return <button onClick={() => book(doctorId)}>Agendar</button>;
}
Rules:
"use client" as far down the tree as possiblechildren"use client" just because a child is a client component"use client";
import { useActionState } from "react";
function LoginForm() {
const [state, formAction, isPending] = useActionState(loginAction, { error: null });
return (
<form action={formAction}>
<input name="email" type="email" />
{state.error && <p className="text-destructive">{state.error}</p>}
<button disabled={isPending}>{isPending ? "..." : "Iniciar sesión"}</button>
</form>
);
}
React 19 passes ref as a regular prop — forwardRef is no longer needed:
function Input({ ref, className, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} className={cn("...", className)} {...props} />;
}
See references/react-19.md for use() hook, Suspense, error boundaries, optimistic updates, and metadata hoisting.
Prefer composition over configuration. Build components from smaller pieces rather than passing many props.
// ❌ Configuration-heavy
<Card title="Dr. García" subtitle="Cardiología" badge="Disponible" footer={<BookButton />} />
// ✅ Composition
<Card>
<Card.Header>
<Card.Title>Dr. García</Card.Title>
<Badge>Disponible</Badge>
</Card.Header>
<Card.Footer><BookButton /></Card.Footer>
</Card>
| Pattern | Use When |
|---|---|
| Compound components | Related components share implicit state (Tabs, Accordion, Dropdown) |
| Render props | Consumer controls rendering, component controls logic |
Polymorphic as prop | Element type varies (Text as <p>, <span>, <label>) |
| Controlled + Uncontrolled | Support both modes via optional value prop |
| Children as ReactNode | Content is flexible (strings, elements, fragments) |
if (loading) return <Spinner /> is clearer than nested ternariesSee references/component-patterns.md for compound components, render props, polymorphic components, and controlled/uncontrolled patterns.
// Always prefix with `use`
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;
}
useEffect for listeners, timers, abort controllers// ❌ Wrong — useEffect for derived state
const [filtered, setFiltered] = useState(items);
useEffect(() => {
setFiltered(items.filter((i) => i.name.includes(search)));
}, [items, search]);
// ✅ Correct — compute during render
const filtered = items.filter((i) => i.name.includes(search));
See references/hooks.md for cleanup patterns, useLocalStorage, useMediaQuery, useClickOutside, and anti-patterns.
| State Type | Tool | Don't Use |
|---|---|---|
| Local UI (toggle, form input) | useState | Zustand |
| Shared client state (auth, theme) | Zustand | Context for frequently changing values |
| Server data (API responses) | urql | Zustand — don't duplicate server state |
| URL-driven (search, pagination) | React Router useSearchParams | Zustand or useState |
const useAuthStore = create<AuthState>((set) => ({
accessToken: null,
user: null,
setAuth: (accessToken, user) => set({ accessToken, user }),
clearAuth: () => set({ accessToken: null, user: null }),
}));
// ✅ Always use selectors — prevents unnecessary re-renders
const user = useAuthStore((state) => state.user);
const isAuthenticated = useAuthStore((state) => state.accessToken !== null);
// ❌ Never destructure the entire store
const { user } = useAuthStore(); // Re-renders on ANY state change
const [result] = useQuery({ query: VIEWER_QUERY });
if (result.fetching) return <Skeleton />;
if (result.error) return <ErrorMessage error={result.error} />;
return <Profile user={result.data.viewer.me} />;
See references/state-management.md for Zustand middleware, slices, testing stores, urql auth exchange, and when NOT to use global state.
Never optimize without profiling data. Use React DevTools Profiler to identify actual bottlenecks.
lazy() + Suspense at route boundaries. Biggest impact, lowest effort.memo.@tanstack/react-virtual for lists with 100+ items.// ✅ Route-based code splitting
const Settings = lazy(() => import("./pages/Settings"));
// ✅ Stable callback reference (only when needed for memoized children)
const handleDelete = useCallback((id: string) => {
setItems((prev) => prev.filter((item) => item.id !== id));
}, []);
// ❌ Don't memoize everything "just in case"
const MemoHeader = memo(Header); // Only if profiling proves it's needed
useMemo.useEffect + fetch. urql handles caching, races, and cleanup.See references/performance.md for React.memo, code splitting, virtualization, image optimization, and profiling techniques.
After every React code change, run the TypeScript verification protocol from the typescript-writing-code skill:
pnpm --filter <app> validate
# or: pnpm run type-check && pnpm run lint && pnpm run format && pnpm run test
All 4 steps must pass. See typescript-writing-code skill for details.
| File | Description |
|---|---|
| references/react-19.md | Server/client components, Actions, use() hook, Suspense, error boundaries |
| references/hooks.md | Custom hooks, dependency management, cleanup patterns, common hooks |
| references/component-patterns.md | Composition, compound components, render props, polymorphic, controlled/uncontrolled |
| references/state-management.md | Zustand patterns, selectors, middleware, slices, urql, testing stores |
| references/performance.md | React.memo, code splitting, lazy loading, virtualization, profiling |