react-dev
Install this skill
npx skills add softaworks/agent-toolkitWorks across Claude Code, Cursor, Codex, Copilot & Antigravity
The react-dev skill enables agents to generate production-grade, type-safe React code using modern TypeScript patterns. It prioritizes strict type safety for component interfaces, event handlers, and complex state management, ensuring compile-time validation. The skill adheres to current standards, including React 19 conventions such as the replacement of forwardRef with standard prop passing, the use() hook for promise unwrapping, and improved form handling via useActionState. By enforcing explicit typing for refs, children, and custom hooks, the agent minimizes runtime errors and creates predictable codebases. It is built to assist in scaling component libraries, maintaining state consistency through discriminated unions, and implementing type-safe generic patterns for reusable UI elements. This ensures that the generated React code follows structural best practices that facilitate easier long-term maintenance and refactoring across large-scale frontend applications.
When to Use This Skill
- •Building reusable component libraries with strict TypeScript interfaces
- •Migrating legacy forwardRef patterns to standard React 19 prop passing
- •Refactoring complex form logic using useActionState for better state tracking
- •Creating type-safe hooks for global state management with context providers
How to Invoke This Skill
Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:
- “Create a type-safe React component with variant props
- “How do I type a form event handler in React 19?
- “Generate a generic table component using TypeScript
- “Refactor this hook to use const assertion return types
- “Show me the React 19 pattern for handling refs as props
Pro Tips
- 💡Always enable `strict` mode in your `tsconfig.json` for maximum type safety benefits across your React projects.
- 💡Familiarize yourself with React 19's new `ref` prop handling and `useActionState` to smoothly transition your components.
- 💡Prioritize generic components for increased reusability and type inference, reducing boilerplate and improving maintainability.
What this skill does
- •Define strictly typed props extending native HTML elements
- •Implement discriminated unions for state and variant-based logic
- •Apply React 19 patterns like useActionState and promise unwrapping
- •Generate type-safe custom hooks with const-assertion return types
- •Create generic components with inferred types for data tables or lists
When not to use it
- ✕Development environments strictly using vanilla JavaScript
- ✕Projects requiring heavy reliance on deprecated legacy React patterns
- ✕Non-React frontend frameworks like Vue or Svelte
Example workflow
- Define the component interface extending React.ComponentPropsWithoutRef
- Implement the component logic using modern React hooks
- Apply discriminated unions if the component has conditional variants
- Use proper event typing for input, form, or mouse interactions
- Verify type inference with generic props for reusability
- Validate against React 19 migration standards like useActionState
Prerequisites
- –TypeScript knowledge
- –Node.js environment
- –React 19 development dependencies
Pitfalls & limitations
- !Failing to use the null-guard pattern when consuming context
- !Over-complicating generic constraints leading to complex error messages
- !Attempting to use forwardRef in React 19 where it is deprecated
FAQ
How it compares
Generic prompts often produce imprecise event types or missing prop definitions; this skill enforces specific TypeScript patterns and React 19 standards to guarantee full compile-time safety.
📄 Full skill instructions — original source: softaworks/agent-toolkit
Type-safe React = compile-time guarantees = confident refactoring.
<when_to_use>
- Building typed React components
- Implementing generic components
- Typing event handlers, forms, refs
- Using React 19 features (Actions, Server Components, use())
- Router integration (TanStack Router, React Router)
- Custom hooks with proper typing
NOT for: non-React TypeScript, vanilla JS React
</when_to_use>
<react_19_changes>
React 19 breaking changes require migration. Key patterns:
**ref as prop** - forwardRef deprecated:
// React 19 - ref as regular prop
type ButtonProps = {
ref?: React.Ref<HTMLButtonElement>;
} & React.ComponentPropsWithoutRef<'button'>;
function Button({ ref, children, ...props }: ButtonProps) {
return <button ref={ref} {...props}>{children}</button>;
}**useActionState** - replaces useFormState:
import { useActionState } from 'react';
type FormState = { errors?: string[]; success?: boolean };
function Form() {
const [state, formAction, isPending] = useActionState(submitAction, {});
return <form action={formAction}>...</form>;
}**use()** - unwraps promises/context:
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise); // Suspends until resolved
return <div>{user.name}</div>;
}See [react-19-patterns.md](references/react-19-patterns.md) for useOptimistic, useTransition, migration checklist.
</react_19_changes>
<component_patterns>
**Props** - extend native elements:
type ButtonProps = {
variant: 'primary' | 'secondary';
} & React.ComponentPropsWithoutRef<'button'>;
function Button({ variant, children, ...props }: ButtonProps) {
return <button className={variant} {...props}>{children}</button>;
}**Children typing**:
type Props = {
children: React.ReactNode; // Anything renderable
icon: React.ReactElement; // Single element
render: (data: T) => React.ReactNode; // Render prop
};**Discriminated unions** for variant props:
type ButtonProps =
| { variant: 'link'; href: string }
| { variant: 'button'; onClick: () => void };
function Button(props: ButtonProps) {
if (props.variant === 'link') {
return <a href={props.href}>Link</a>;
}
return <button onClick={props.onClick}>Button</button>;
}</component_patterns>
<event_handlers>
Use specific event types for accurate target typing:
// Mouse
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
e.currentTarget.disabled = true;
}
// Form
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
}
// Input
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
// Keyboard
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') e.currentTarget.blur();
}See [event-handlers.md](references/event-handlers.md) for focus, drag, clipboard, touch, wheel events.
</event_handlers>
<hooks_typing>
**useState** - explicit for unions/null:
const [user, setUser] = useState<User | null>(null);
const [status, setStatus] = useState<'idle' | 'loading'>('idle');**useRef** - null for DOM, value for mutable:
const inputRef = useRef<HTMLInputElement>(null); // DOM - use ?.
const countRef = useRef<number>(0); // Mutable - direct access**useReducer** - discriminated unions for actions:
type Action =
| { type: 'increment' }
| { type: 'set'; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'set': return { ...state, count: action.payload };
default: return state;
}
}**Custom hooks** - tuple returns with as const:
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue(v => !v);
return [value, toggle] as const;
}**useContext** - null guard pattern:
const UserContext = createContext<User | null>(null);
function useUser() {
const user = useContext(UserContext);
if (!user) throw new Error('useUser outside UserProvider');
return user;
}See [hooks.md](references/hooks.md) for useCallback, useMemo, useImperativeHandle, useSyncExternalStore.
</hooks_typing>
<generic_components>
Generic components infer types from props - no manual annotations at call site.
**Pattern** - keyof T for column keys, render props for custom rendering:
type Column<T> = {
key: keyof T;
header: string;
render?: (value: T[keyof T], item: T) => React.ReactNode;
};
type TableProps<T> = {
data: T[];
columns: Column<T>[];
keyExtractor: (item: T) => string | number;
};
function Table<T>({ data, columns, keyExtractor }: TableProps<T>) {
return (
<table>
<thead>
<tr>{columns.map(col => <th key={String(col.key)}>{col.header}</th>)}</tr>
</thead>
<tbody>
{data.map(item => (
<tr key={keyExtractor(item)}>
{columns.map(col => (
<td key={String(col.key)}>
{col.render ? col.render(item[col.key], item) : String(item[col.key])}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}**Constrained generics** for required properties:
type HasId = { id: string | number };
function List<T extends HasId>({ items }: { items: T[] }) {
return <ul>{items.map(item => <li key={item.id}>...</li>)}</ul>;
}See [generic-components.md](examples/generic-components.md) for Select, List, Modal, FormField patterns.
</generic_components>
<server_components>
React 19 Server Components run on server, can be async.
**Async data fetching**:
export default async function UserPage({ params }: { params: { id: string } }) {
const user = await fetchUser(params.id);
return <div>{user.name}</div>;
}**Server Actions** - 'use server' for mutations:
'use server';
export async function updateUser(userId: string, formData: FormData) {
await db.user.update({ where: { id: userId }, data: { ... } });
revalidatePath(/users/${userId});
}**Client + Server Action**:
'use client';
import { useActionState } from 'react';
import { updateUser } from '@/actions/user';
function UserForm({ userId }: { userId: string }) {
const [state, formAction, isPending] = useActionState(
(prev, formData) => updateUser(userId, formData), {}
);
return <form action={formAction}>...</form>;
}**use() for promise handoff**:
// Server: pass promise without await
async function Page() {
const userPromise = fetchUser('123');
return <UserProfile userPromise={userPromise} />;
}
// Client: unwrap with use()
'use client';
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}See [server-components.md](examples/server-components.md) for parallel fetching, streaming, error boundaries.
</server_components>
<routing>
Both TanStack Router and React Router v7 provide type-safe routing solutions.
**TanStack Router** - Compile-time type safety with Zod validation:
import { createRoute } from '@tanstack/react-router';
import { z } from 'zod';
const userRoute = createRoute({
path: '/users/$userId',
component: UserPage,
loader: async ({ params }) => ({ user: await fetchUser(params.userId) }),
validateSearch: z.object({
tab: z.enum(['profile', 'settings']).optional(),
page: z.number().int().positive().default(1),
}),
});
function UserPage() {
const { user } = useLoaderData({ from: userRoute.id });
const { tab, page } = useSearch({ from: userRoute.id });
const { userId } = useParams({ from: userRoute.id });
}**React Router v7** - Automatic type generation with Framework Mode:
import type { Route } from "./+types/user";
export async function loader({ params }: Route.LoaderArgs) {
return { user: await fetchUser(params.userId) };
}
export default function UserPage({ loaderData }: Route.ComponentProps) {
const { user } = loaderData; // Typed from loader
return <h1>{user.name}</h1>;
}See [tanstack-router.md](references/tanstack-router.md) for TanStack patterns and [react-router.md](references/react-router.md) for React Router patterns.
</routing>
<rules>
ALWAYS:
- Specific event types (MouseEvent, ChangeEvent, etc)
- Explicit useState for unions/null
- ComponentPropsWithoutRef for native element extension
- Discriminated unions for variant props
- as const for tuple returns
- ref as prop in React 19 (no forwardRef)
- useActionState for form actions
- Type-safe routing patterns (see routing section)
NEVER:
- any for event handlers
- JSX.Element for children (use ReactNode)
- forwardRef in React 19+
- useFormState (deprecated)
- Forget null handling for DOM refs
- Mix Server/Client components in same file
- Await promises when passing to use()
</rules>
<references>
- [hooks.md](references/hooks.md) - useState, useRef, useReducer, useContext, custom hooks
- [event-handlers.md](references/event-handlers.md) - all event types, generic handlers
- [react-19-patterns.md](references/react-19-patterns.md) - useActionState, use(), useOptimistic, migration
- [generic-components.md](examples/generic-components.md) - Table, Select, List, Modal patterns
- [server-components.md](examples/server-components.md) - async components, Server Actions, streaming
- [tanstack-router.md](references/tanstack-router.md) - TanStack Router typed routes, search params, navigation
- [react-router.md](references/react-router.md) - React Router v7 loaders, actions, type generation, forms
</references>
How to Use This Skill Unit
Option A: Project-Specific (Recommended)
- Click "Download" above
- In your project, create the directory:
.agent/skills/react-dev/ - Save the file as
SKILL.md - The agent will automatically discover the skill based on its description.
Option B: Global Installation (All Agents)
Save the file to these locations to make it available across all projects:
- Claude Code:
~/.claude/skills/softaworks/agent-toolkit/react-dev/SKILL.md - Cursor:
~/.cursor/skills/softaworks/agent-toolkit/react-dev/SKILL.md - Antigravity:
~/.gemini/antigravity/skills/softaworks/agent-toolkit/react-dev/SKILL.md
🚀 Install with CLI:npx skills add softaworks/agent-toolkit
