Skip to content
Back to skills

React Advanced Coding Skill

ASecurity

Reusable skill module for React/TypeScript development. Covers React 18/19 features, component design, typed props with JSDoc, hooks, TanStack Query, accessibility, and Vitest + RTL test generation.

  • 3 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 5, 2026
developmenttypescriptbashreactnodetestingapifrontenddocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 21 files and shows the line behind each finding

Scanned October 1, 2026

npx -y skills add sharmapuneet1510/awesome-prompts --skill skills --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of React Advanced Coding Skill?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for React Advanced Coding Skill
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sharmapuneet1510-react-advanced-coding-skill/badge)](https://www.skillsdirectory.com/skills/sharmapuneet1510-react-advanced-coding-skill)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: React Advanced Coding Skill
version: 2.1
description: >
  Reusable skill module for React/TypeScript development. Covers React 18/19
  features, component design, typed props with JSDoc, hooks, TanStack Query,
  accessibility, and Vitest + RTL test generation.
applies_to: [react, typescript, vite, tanstack-query, zustand, tailwindcss]
---

# React Advanced Coding Skill — v2.1

## Quick Card

> Read this card first. Load a section below only when the task needs it.

| | |
|---|---|
| **Use when** | Writing or reviewing React 18+/19 + TypeScript |
| **Skip when** | Full page/feature scaffolding — `frontend_skill` wraps this skill |
| **Inputs** | React and TS versions (§1 — detect first), state and data libraries |
| **Produces** | Typed components and hooks with Vitest + RTL tests |
| **Steps** | 1. Detect versions → 2. Design components → 3. Strict types → 4. Hooks → 5. All states → 6. Forms → 7. a11y → 8. Tests |
| **Done when** | §9 rules hold; tests ship with the component |
| **Senior defaults** | Strict TS, no `any` · server state in TanStack Query only · loading / error / empty always rendered · react-hook-form + Zod · stable keys, exhaustive effect deps · on React 19, `useOptimistic` / `useFormStatus` over hand-rolled pending state |
| **Load on demand** | §1 versions · §2 components · §3 TypeScript · §4 hooks · §5 states · §6 forms · §7 a11y · §8 tests |
| **Run report** | `html_report_skill` — adds: Components and hooks added |
| **Pairs with** | `frontend_skill`, `test_skill` §4 |

---

## 1. Version Detection First

Check what is installed before writing code:

```bash
node -v
cat package.json | grep '"react"'
cat package.json | grep '"typescript"'
```

| React Version | Key Features |
|--------------|-------------|
| React 17 | Stable, no concurrent features |
| React 18 | Concurrent rendering, `useTransition`, `useDeferredValue`, `Suspense` for data |
| React 19 | `use()` hook, `useFormStatus`, `useOptimistic`, Server Actions |

---

## 2. Component Design Principles

### One Component = One Job

Each component should do exactly one thing. If it does more, split it.

```jsx
❌ UserDashboard — renders layout, fetches data, shows charts, handles modals
✅ UserDashboard  — layout only
✅ useUserStats   — fetches the data (custom hook)
✅ StatsChart     — renders one chart
✅ UserModal      — one modal
```

### Component File Structure

Every non-trivial component lives in its own folder:

```jsx
features/orders/
  components/
    OrderCard/
      OrderCard.tsx         ← the component
      OrderCard.test.tsx    ← the tests
      index.ts              ← export: export { OrderCard } from './OrderCard'
  hooks/
    useOrders.ts            ← TanStack Query hook
  types/
    order.types.ts          ← TypeScript types for orders
```

---

## 3. TypeScript — Always Strict

### Typed Props with JSDoc (Documentation)

Every exported component must have a named interface for its props, with JSDoc:

```tsx
/**
 * Displays a summary card for a single customer order.
 *
 * Shows the order ID, current status (colour-coded), and formatted total.
 * Clicking the card triggers the onClick handler.
 *
 * @example
 * <OrderCard
 *   orderId={42}
 *   status="SHIPPED"
 *   totalAmount={99.99}
 *   currency="GBP"
 *   onClick={(id) => navigate(`/orders/${id}`)}
 * />
 */
interface OrderCardProps {
  /** Unique identifier for this order. */
  orderId: number;

  /** Current status of the order. Determines the badge colour. */
  status: 'PENDING' | 'CONFIRMED' | 'SHIPPED' | 'DELIVERED' | 'CANCELLED';

  /** Total monetary value of the order. */
  totalAmount: number;

  /** ISO 4217 currency code for formatting (e.g. 'GBP', 'USD'). */
  currency: string;

  /** Called when the user clicks the card. Receives the orderId. */
  onClick: (orderId: number) => void;
}

export function OrderCard({ orderId, status, totalAmount, currency, onClick }: OrderCardProps) {
  // ...
}
```

### Type Aliases and Discriminated Unions

Use discriminated unions to make impossible states impossible:

```tsx
/**
 * Represents the state of an async operation.
 * Only one state can be active at a time.
 */
type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

// Usage in a component — TypeScript narrows the type in each branch
function renderState<T>(state: AsyncState<T>) {
  switch (state.status) {
    case 'idle':    return <EmptyState />;
    case 'loading': return <Spinner />;
    case 'success': return <DataView data={state.data} />;  // state.data is T here
    case 'error':   return <ErrorMessage error={state.error} />;
  }
}
```

---

## 4. Hooks — Best Practices

### Custom Hooks for Data Fetching (TanStack Query)

Never put `fetch` calls inside component bodies. Put them in custom hooks.

```tsx
// hooks/useOrders.ts

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

/**
 * Query key factory for orders.
 *
 * Centralises all order-related cache keys so they are consistent
 * across the app and easy to invalidate.
 */
const orderKeys = {
  all: ['orders'] as const,
  byCustomer: (customerId: number) => [...orderKeys.all, 'customer', customerId] as const,
  detail: (orderId: number) => [...orderKeys.all, 'detail', orderId] as const,
};

/**
 * Fetches all orders for a given customer.
 *
 * Handles caching, background refetching, and error state automatically.
 * The component using this hook does not need any useState or useEffect.
 *
 * @param customerId - The customer whose orders to load. Must be > 0.
 * @returns TanStack Query result — { data, isPending, isError }
 */
export function useOrders(customerId: number) {
  return useQuery({
    queryKey: orderKeys.byCustomer(customerId),
    queryFn: () => fetchOrdersForCustomer(customerId),
    enabled: customerId > 0,  // Don't fetch if customerId is not ready
    staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes
  });
}

/**
 * Provides a mutation for creating a new order.
 *
 * On success, automatically invalidates the customer's order list
 * so the UI refreshes with the new order.
 *
 * @param customerId - Used to invalidate the correct cache entry on success.
 */
export function useCreateOrder(customerId: number) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (request: CreateOrderRequest) => createOrder(request),
    onSuccess: () => {
      // Tell TanStack Query that the customer's order list is stale
      queryClient.invalidateQueries({ queryKey: orderKeys.byCustomer(customerId) });
    },
  });
}
```

### Custom State Hook

```tsx
/**
 * Manages the open/closed state of a modal dialog.
 *
 * @returns isOpen flag and open/close handlers.
 *
 * @example
 * const { isOpen, open, close } = useModal();
 * <button onClick={open}>Open</button>
 * <Modal isOpen={isOpen} onClose={close} />
 */
export function useModal() {
  const [isOpen, setIsOpen] = useState(false);

  /** Opens the modal. */
  const open = useCallback(() => setIsOpen(true), []);

  /** Closes the modal. */
  const close = useCallback(() => setIsOpen(false), []);

  return { isOpen, open, close };
}
```

---

## 5. Always Handle All States

Never render a component that might receive `undefined` data.

```tsx
/**
 * Displays the list of orders for the current customer.
 *
 * Handles loading, error, empty, and populated states explicitly.
 * The user always sees something meaningful — never a blank screen.
 */
export function OrderList({ customerId }: { customerId: number }) {
  const { data: orders, isPending, isError } = useOrders(customerId);

  // While data is loading — show a skeleton
  if (isPending) {
    return <OrderListSkeleton />;
  }

  // If the request failed — show a helpful message
  if (isError) {
    return (
      <ErrorMessage
        title="Could not load orders"
        description="Please check your connection and try refreshing."
      />
    );
  }

  // If the list is empty — explain why
  if (orders.length === 0) {
    return <EmptyState message="You have no orders yet." />;
  }

  // Happy path — render the list
  return (
    <ul aria-label="Your orders">
      {orders.map((order) => (
        // Never use array index as key — use a stable unique ID
        <li key={order.id}>
          <OrderCard {...order} />
        </li>
      ))}
    </ul>
  );
}
```

---

## 6. Forms — react-hook-form + Zod

```tsx
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

/**
 * Validation schema for the create order form.
 *
 * Zod validates all fields before the submit handler is called.
 * Error messages are defined here, not in the component.
 */
const createOrderSchema = z.object({
  customerName: z
    .string()
    .min(2, 'Name must be at least 2 characters')
    .max(100, 'Name must be 100 characters or fewer'),
  email: z
    .string()
    .email('Please enter a valid email address'),
  quantity: z
    .number({ invalid_type_error: 'Quantity must be a number' })
    .int('Quantity must be a whole number')
    .min(1, 'Quantity must be at least 1'),
});

/** TypeScript type is derived from the schema — always stays in sync */
type CreateOrderFormValues = z.infer<typeof createOrderSchema>;

/**
 * Form for creating a new order.
 *
 * Validates all fields before submitting. Shows field-level error
 * messages when validation fails. Disables the button while submitting.
 */
export function CreateOrderForm({ onSuccess }: { onSuccess: () => void }) {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<CreateOrderFormValues>({
    resolver: zodResolver(createOrderSchema),
  });

  async function onSubmit(data: CreateOrderFormValues) {
    await submitOrder(data);
    onSuccess();
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)} noValidate>

      {/* Customer Name field with accessible error message */}
      <div>
        <label htmlFor="customerName">Customer Name</label>
        <input
          id="customerName"
          type="text"
          aria-required="true"
          aria-invalid={!!errors.customerName}
          aria-describedby={errors.customerName ? 'customerName-error' : undefined}
          {...register('customerName')}
        />
        {errors.customerName && (
          <span id="customerName-error" role="alert" className="text-red-600">
            {errors.customerName.message}
          </span>
        )}
      </div>

      {/* Submit button — disabled and shows feedback while submitting */}
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Create Order'}
      </button>

    </form>
  );
}
```

---

## 7. Accessibility Checklist

Every component must pass these checks before being complete:

- [ ] Every `<input>` has a linked `<label>` via `htmlFor` / `id`
- [ ] Error messages use `role="alert"` so screen readers announce them
- [ ] Interactive elements (`button`, `a`, custom) are reachable via `Tab`
- [ ] Custom clickable elements have `role="button"` and handle `Enter`/`Space` keys
- [ ] Images have meaningful `alt` text (or `alt=""` if decorative)
- [ ] Colour is not the only way to convey status (use text or icons too)
- [ ] Focus is managed correctly when modals open/close

---

## 8. Testing Standards — Vitest + React Testing Library

### Component Unit Tests

```tsx
// OrderCard.test.tsx

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { OrderCard } from './OrderCard';

describe('OrderCard', () => {
  const defaultProps = {
    orderId: 42,
    status: 'PENDING' as const,
    totalAmount: 99.99,
    currency: 'GBP',
    onClick: vi.fn(),
  };

  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('displays the order ID, status, and formatted amount', () => {
    // Arrange & Act
    render(<OrderCard {...defaultProps} />);

    // Assert — test what the user sees, not the internal state
    expect(screen.getByText('Order #42')).toBeInTheDocument();
    expect(screen.getByText('PENDING')).toBeInTheDocument();
    expect(screen.getByText('£99.99')).toBeInTheDocument();
  });

  it('calls onClick with the correct order ID when clicked', async () => {
    // Arrange
    const user = userEvent.setup();
    render(<OrderCard {...defaultProps} />);

    // Act
    await user.click(screen.getByRole('button'));

    // Assert
    expect(defaultProps.onClick).toHaveBeenCalledOnce();
    expect(defaultProps.onClick).toHaveBeenCalledWith(42);
  });

  it('can be activated with the Enter key for keyboard users', async () => {
    // Accessibility: keyboard users must be able to interact
    const user = userEvent.setup();
    render(<OrderCard {...defaultProps} />);

    await user.tab();
    await user.keyboard('{Enter}');

    expect(defaultProps.onClick).toHaveBeenCalledWith(42);
  });

  it('has an accessible label describing the order', () => {
    render(<OrderCard {...defaultProps} />);

    // Screen readers should announce this clearly
    expect(screen.getByRole('button', { name: /order 42/i })).toBeInTheDocument();
  });
});
```

### Hook Tests

```tsx
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { http, HttpResponse } from 'msw';
import { server } from '../../../mocks/server';

describe('useOrders', () => {
  it('returns orders when API responds successfully', async () => {
    // Arrange — mock the API response
    server.use(
      http.get('/api/v1/orders', () =>
        HttpResponse.json([{ id: 1, status: 'PENDING' }])
      )
    );

    const queryClient = new QueryClient();
    const wrapper = ({ children }) => (
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    );

    // Act
    const { result } = renderHook(() => useOrders(1), { wrapper });

    // Assert
    await waitFor(() => expect(result.current.isSuccess).toBe(true));
    expect(result.current.data).toHaveLength(1);
  });
});
```

---

## 9. Code Quality Rules (Quick Reference)

| Rule | Detail |
|------|--------|
| `any` type | Never — use `unknown`, generics, or proper types |
| Array key | Never use index — use stable unique IDs |
| Server state | TanStack Query only — never in Zustand or useState |
| Inline styles | Never — use Tailwind classes or CSS Modules |
| `console.log` | Remove before committing |
| useEffect deps | Must be exhaustive — use ESLint `react-hooks` rule |
| Missing states | Always handle loading, error, and empty |
| Tests | Always in the same response as the component |

Files in this skill

  • README.md10.2 KB
  • adr_skill.md8.4 KB
  • agent_skill_design_skill.md3.1 KB
  • apache_camel_skill.md15.5 KB
  • apache_pulsar_skill.md17.1 KB
  • ba_create_skill.md18.9 KB
  • backend_skill.md22.1 KB
  • code_documentation_skill.md13.4 KB
  • code_formatting_skill.md11.7 KB
  • code_health_skill.md9.8 KB
  • code_review_skill.md36.7 KB
  • context_builder_skill.md11.7 KB
  • current_tech_spec_skill.md6.3 KB
  • database_skill.md18.4 KB
  • debugging_skill.md3.4 KB
  • error_handling_skill.md18.4 KB
  • frontend_skill.md23.6 KB
  • java_advanced_skill.md15 KB
  • jira_html_report_skill.md15.5 KB
  • jira_incremental_spec_generator_skill.md19.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.