Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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).
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 79% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 101% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 146% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 125% | 0% |
Type-safe React = compile-time guarantees = confident refactoring.
<when_to_use>
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:
typescript// 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:
typescriptimport { 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:
typescriptfunction UserProfile({ userPromise }: { userPromise: Promise<User> }) { const user = use(userPromise); // Suspends until resolved return <div>{user.name}</div>; }
See react-19-patterns.md for useOptimistic, useTransition, migration checklist.
</react_19_changes>
<component_patterns>
Props - extend native elements:
typescripttype ButtonProps = { variant: 'primary' | 'secondary'; } & React.ComponentPropsWithoutRef<'button'>; function Button({ variant, children, ...props }: ButtonProps) { return <button className={variant} {...props}>{children}</button>; }
Children typing:
typescripttype Props = { children: React.ReactNode; // Anything renderable icon: React.ReactElement; // Single element render: (data: T) => React.ReactNode; // Render prop };
Discriminated unions for variant props:
typescripttype 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:
typescript// 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 for focus, drag, clipboard, touch, wheel events.
</event_handlers>
<hooks_typing>
useState - explicit for unions/null:
typescriptconst [user, setUser] = useState<User | null>(null); const [status, setStatus] = useState<'idle' | 'loading'>('idle');
useRef - null for DOM, value for mutable:
typescriptconst inputRef = useRef<HTMLInputElement>(null); // DOM - use ?. const countRef = useRef<number>(0); // Mutable - direct access
useReducer - discriminated unions for actions:
typescripttype 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:
typescriptfunction useToggle(initial = false) { const [value, setValue] = useState(initial); const toggle = () => setValue(v => !v); return [value, toggle] as const; }
useContext - null guard pattern:
typescriptconst UserContext = createContext<User | null>(null); function useUser() { const user = useContext(UserContext); if (!user) throw new Error('useUser outside UserProvider'); return user; }
See 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:
typescripttype 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:
typescripttype 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 for Select, List, Modal, FormField patterns.
</generic_components>
<server_components>
React 19 Server Components run on server, can be async.
Async data fetching:
typescriptexport 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:
typescript'use server'; export async function updateUser(userId: string, formData: FormData) { await db.user.update({ where: { id: userId }, data: { ... } }); revalidatePath(`/users/${userId}`); }
Client + Server Action:
typescript'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:
typescript// 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 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:
typescriptimport { 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:
typescriptimport 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 for TanStack patterns and react-router.md for React Router patterns.
</routing>
<rules>
ALWAYS:
NEVER:
</rules>
<references>
</references>
Other measured skills in the registry, with their headline benchmark lift.