This skill should be used when building React components with TypeScript, typing hooks, handling events, or when React TypeScript, React 19, Server Components are mentioned. Covers type-safe patterns for React 18-19 including generic components, proper event typing, and routing integration (TanStack Router, React Router).
The react-dev skill provides comprehensive TypeScript patterns and best practices for building type-safe React applications. It serves as a complete reference for modern React development with TypeScript, covering React 18-19 features, including Server Components, type-safe routing, and proper event handling.
This skill exists to eliminate TypeScript guesswork in React development by providing compile-time guarantees, confident refactoring, and production-ready patterns that catch bugs before runtime.
Activate this skill when working on:
use() hook, useActionStateTrigger phrases:
Not for:
The skill provides progressive disclosure of React TypeScript knowledge:
The skill is structured around:
Covers all major breaking changes in React 19:
forwardRef, refs are regular props nowuseFormStateType-safe patterns for common component scenarios:
ComponentPropsWithoutRefReactNode, ReactElement, render propsSpecific event types for accurate TypeScript inference:
MouseEvent<HTMLButtonElement>)FormEvent<HTMLFormElement>)ChangeEvent<HTMLInputElement>)KeyboardEvent<HTMLInputElement>)Proper TypeScript patterns for all React hooks:
useState - Explicit types for unions and nullable valuesuseRef - DOM refs (null) vs mutable refs (direct access)useReducer - Discriminated union actionsuseContext - Null guard patternsas constModern patterns for Server Components and Server Actions:
'use server' directiveuse() hookComprehensive routing patterns for both major routers:
Reusable components that work with any data type:
type ButtonProps = {
variant: 'primary' | 'secondary';
} & React.ComponentPropsWithoutRef<'button'>;
function Button({ variant, children, ...props }: ButtonProps) {
return (
<button
className={variant}
{...props}
>
{children}
</button>
);
}
// Usage
<Button variant="primary" onClick={(e) => console.log(e.currentTarget.disabled)}>
Click me
</Button>
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>
);
}
// Usage - types are inferred!
type User = { id: number; name: string; email: string };
const users: User[] = [...];
<Table
data={users}
columns={[
{ key: 'name', header: 'Name' },
{ key: 'email', header: 'Email' }
]}
keyExtractor={user => user.id}
/>
// actions/user.ts
'use server';
export async function updateUser(userId: string, formData: FormData) {
const name = formData.get('name') as string;
await db.user.update({
where: { id: userId },
data: { name }
});
revalidatePath(`/users/${userId}`);
return { success: true };
}
// UserForm.tsx
'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}>
<input name="name" />
<button disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
{state.success && <p>Saved!</p>}
</form>
);
}
function Form() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
console.log(Object.fromEntries(formData));
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value); // Typed correctly!
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.currentTarget.blur(); // currentTarget is typed as HTMLInputElement
}
};
return (
<form onSubmit={handleSubmit}>
<input
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
</form>
);
}
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue(v => !v);
return [value, toggle] as const; // Tuple type preserved!
}
// Usage
const [isOpen, toggleOpen] = useToggle(false);
// isOpen is boolean, toggleOpen is () => void
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() {
// All fully typed from the route definition!
const { user } = useLoaderData({ from: userRoute.id });
const { tab, page } = useSearch({ from: userRoute.id });
const { userId } = useParams({ from: userRoute.id });
return <div>{user.name} - {tab} - Page {page}</div>;
}
The skill provides:
✅ Use specific event types - MouseEvent<HTMLButtonElement> not React.MouseEvent
✅ Explicit useState for unions/null - useState<User | null>(null)
✅ Extend native elements - ComponentPropsWithoutRef<'button'>
✅ Discriminated unions for variants - Type-safe props based on variant
✅ as const for tuple returns - Preserve tuple types in custom hooks
✅ ref as prop in React 19 - No forwardRef needed
✅ useActionState for forms - Not deprecated useFormState
✅ Type-safe routing patterns - Use provided router patterns
❌ Use any for event handlers - Defeats TypeScript's purpose
❌ Use JSX.Element for children - Use ReactNode instead
❌ Use forwardRef in React 19+ - It's deprecated
❌ Use useFormState - Deprecated in React 19
❌ Forget null handling for DOM refs - Always use ref?.current
❌ Mix Server/Client in same file - Will cause hydration errors
❌ Await promises before use() - Defeats streaming/Suspense
The skill includes detailed reference files:
useState, useRef, useReducer, useContext, custom hooksuseActionState, use(), useOptimistic, migrationThese files are loaded on-demand to keep context efficient. The skill will reference them when deeper knowledge is needed.
This skill follows progressive disclosure principles:
This keeps the skill's context footprint minimal while providing comprehensive coverage when needed.
use(), useActionStatecreateRouteVersion: 1.0.0 Last Updated: 2026-01-20 Maintained by: Softaworks Agent Skills
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer