Added 16 custom skills: - ralph (RalphLoop autonomous agent) - brainstorming (with Ralph integration) - dispatching-parallel-agents - autonomous-loop - multi-ai-brainstorm - cognitive-context, cognitive-core, cognitive-planner, cognitive-safety - tool-discovery-agent - ui-ux-pro-max (full design system) - wordpress-ai - agent-pipeline-builder - dev-browser - planning-with-files - playwright-skill Also organized remaining skills that were at root level into skills/ folder. Total: 272 skills from skills.sh + 16 custom upgrades Co-Authored-By: Claude <noreply@anthropic.com>
392 lines
10 KiB
Markdown
392 lines
10 KiB
Markdown
---
|
|
name: react-dev
|
|
version: 1.0.0
|
|
description: 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).
|
|
---
|
|
|
|
# React TypeScript
|
|
|
|
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:
|
|
|
|
```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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
type ButtonProps = {
|
|
variant: 'primary' | 'secondary';
|
|
} & React.ComponentPropsWithoutRef<'button'>;
|
|
|
|
function Button({ variant, children, ...props }: ButtonProps) {
|
|
return <button className={variant} {...props}>{children}</button>;
|
|
}
|
|
```
|
|
|
|
**Children typing**:
|
|
|
|
```typescript
|
|
type Props = {
|
|
children: React.ReactNode; // Anything renderable
|
|
icon: React.ReactElement; // Single element
|
|
render: (data: T) => React.ReactNode; // Render prop
|
|
};
|
|
```
|
|
|
|
**Discriminated unions** for variant props:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```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](references/event-handlers.md) for focus, drag, clipboard, touch, wheel events.
|
|
|
|
</event_handlers>
|
|
|
|
<hooks_typing>
|
|
|
|
**useState** - explicit for unions/null:
|
|
|
|
```typescript
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [status, setStatus] = useState<'idle' | 'loading'>('idle');
|
|
```
|
|
|
|
**useRef** - null for DOM, value for mutable:
|
|
|
|
```typescript
|
|
const inputRef = useRef<HTMLInputElement>(null); // DOM - use ?.
|
|
const countRef = useRef<number>(0); // Mutable - direct access
|
|
```
|
|
|
|
**useReducer** - discriminated unions for actions:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
function useToggle(initial = false) {
|
|
const [value, setValue] = useState(initial);
|
|
const toggle = () => setValue(v => !v);
|
|
return [value, toggle] as const;
|
|
}
|
|
```
|
|
|
|
**useContext** - null guard pattern:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```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](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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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>
|