react-dev

This skill should be used when building React components with TypeScript, typing hooks, handling events, or when React TypeScript, React 19, Server Components…

INSTALLATION
npx skills add https://github.com/davila7/claude-code-templates --skill react-dev
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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:

// React 19 - ref as regular prop

type ButtonProps = {

  ref?: React.Ref<HTMLButtonElement>;

} &#x26; 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 for useOptimistic, useTransition, migration checklist.

</react_19_changes>

<component_patterns>

Props - extend native elements:

type ButtonProps = {

  variant: 'primary' | 'secondary';

} &#x26; 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 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 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 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 for parallel fetching, streaming, error boundaries.

</server_components>

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 for TanStack patterns and react-router.md for React Router patterns.

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()
  • hooks.md - useState, useRef, useReducer, useContext, custom hooks
  • react-router.md - React Router v7 loaders, actions, type generation, forms
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card