MCPInstall

react-web

React web development with hooks, React Query, Zustand

Installs
339
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/alinaqi/maggy --skill react-web
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads react-web automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

React Web Skill


Test-First Development (MANDATORY)

CRITICAL: Tests MUST be written BEFORE implementation code. This is non-negotiable for frontend components.

The TFD Workflow

1. Write test file first → Defines expected behavior
2. Run test (it fails) → Confirms test is valid
3. Write minimal code → Just enough to pass
4. Run test (it passes) → Validates implementation
5. Refactor if needed → Tests catch regressions

Component Development Order

# CORRECT ORDER - Test first
1. Create Button.test.tsx    # Write tests for expected behavior
2. Run tests (they fail)     # npm test -- Button
3. Create Button.tsx         # Implement to pass tests
4. Run tests (they pass)     # Verify implementation
5. Create Button.module.css  # Style after logic works

# WRONG ORDER - Never do this
1. Create Button.tsx         # ❌ No tests exist yet
2. Create Button.module.css  # ❌ Still no tests
3. "I'll add tests later"    # ❌ Tests never get written

Test File Structure (Create First)

// Button.test.tsx - CREATE THIS FIRST
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  // Define ALL expected behaviors upfront
  describe('rendering', () => {
    it('renders with label', () => {
      render(<Button label="Click me" onClick={() => {}} />);
      expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
    });

    it('applies variant class', () => {
      render(<Button label="Click" onClick={() => {}} variant="secondary" />);
      expect(screen.getByRole('button')).toHaveClass('secondary');
    });
  });

  describe('interactions', () => {
    it('calls onClick when clicked', () => {
      const onClick = vi.fn();
      render(<Button label="Click me" onClick={onClick} />);
      fireEvent.click(screen.getByRole('button'));
      expect(onClick).toHaveBeenCalledTimes(1);
    });

    it('does not call onClick when disabled', () => {
      const onClick = vi.fn();
      render(<Button label="Click me" onClick={onClick} disabled />);
      fireEvent.click(screen.getByRole('button'));
      expect(onClick).not.toHaveBeenCalled();
    });
  });

  describe('accessibility', () => {
    it('has correct aria attributes when disabled', () => {
      render(<Button label="Click" onClick={() => {}} disabled />);
      expect(screen.getByRole('button')).toHaveAttribute('aria-disabled', 'true');
    });
  });
});

Hook Test First Pattern

// useCounter.test.ts - CREATE THIS FIRST
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('starts at initial value', () => {
    const { result } = renderHook(() => useCounter(5));
    expect(result.current.count).toBe(5);
  });

  it('increments', () => {
    const { result } = renderHook(() => useCounter());
    act(() => result.current.increment());
    expect(result.current.count).toBe(1);
  });

  it('decrements', () => {
    const { result } = renderHook(() => useCounter(5));
    act(() => result.current.decrement());
    expect(result.current.count).toBe(4);
  });

  it('resets to initial value', () => {
    const { result } = renderHook(() => useCounter(10));
    act(() => result.current.increment());
    act(() => result.current.reset());
    expect(result.current.count).toBe(10);
  });
});

Enforcement Checklist

Before writing ANY component/hook implementation:

  • Test file exists: Component.test.tsx
  • All expected behaviors have test cases
  • Tests run and FAIL (proves tests are valid)
  • Only THEN create implementation file

If tests are skipped, Claude MUST:

⚠️ TEST-FIRST VIOLATION

Cannot create [Component].tsx - no test file exists.

Creating [Component].test.tsx first with tests for:
- Rendering with required props
- User interactions
- Edge cases
- Accessibility

Project Structure

project/
├── src/
│   ├── core/                   # Pure business logic (no React)
│   │   ├── types.ts
│   │   └── services/
│   ├── components/             # Reusable UI components
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   ├── Button.test.tsx
│   │   │   ├── Button.module.css  # or .styles.ts
│   │   │   └── index.ts
│   │   └── index.ts            # Barrel export
│   ├── pages/                  # Route-level components
│   │   ├── Home/
│   │   │   ├── HomePage.tsx
│   │   │   ├── useHome.ts      # Page-specific hook
│   │   │   └── index.ts
│   │   └── index.ts
│   ├── hooks/                  # Shared custom hooks
│   ├── store/                  # State management
│   ├── api/                    # API client and queries
│   ├── utils/                  # Utilities
│   ├── App.tsx
│   └── main.tsx
├── tests/
│   ├── unit/
│   └── e2e/
├── public/
├── package.json
├── tsconfig.json
├── vite.config.ts              # or next.config.js
└── CLAUDE.md

Component Patterns

Functional Components Only

// Good - simple, testable
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
  variant?: 'primary' | 'secondary';
}

export function Button({
  label,
  onClick,
  disabled = false,
  variant = 'primary'
}: ButtonProps): JSX.Element {
  return (
    <button
      className={styles[variant]}
      onClick={onClick}
      disabled={disabled}
    >
      {label}
    </button>
  );
}

Extract Logic to Hooks

// useHome.ts - all logic here
export function useHome() {
  const [items, setItems] = useState<Item[]>([]);
  const [loading, setLoading] = useState(false);

  const refresh = useCallback(async () => {
    setLoading(true);
    const data = await fetchItems();
    setItems(data);
    setLoading(false);
  }, []);

  useEffect(() => {
    refresh();
  }, [refresh]);

  return { items, loading, refresh };
}

// HomePage.tsx - pure presentation
export function HomePage(): JSX.Element {
  const { items, loading, refresh } = useHome();

  if (loading) return <Spinner />;

  return <ItemList items={items} onRefresh={refresh} />;
}

Props Interface Always Explicit

// Always define props interface, even if simple
interface ItemCardProps {
  item: Item;
  onClick: (id: string) => void;
}

export function ItemCard({ item, onClick }: ItemCardProps): JSX.Element {
  return (
    <div onClick={() => onClick(item.id)}>
      <h3>{item.title}</h3>
    </div>
  );
}

State Management

Local State First

// Start with useState, escalate only when needed
const [value, setValue] = useState('');

Zustand for Global State (if needed)

// store/useAppStore.ts
import { create } from 'zustand';

interface AppState {
  user: User | null;
  theme: 'light' | 'dark';
  setUser: (user: User | null) => void;
  toggleTheme: () => void;
}

export const useAppStore = create<AppState>((set) => ({
  user: null,
  theme: 'light',
  setUser: (user) => set({ user }),
  toggleTheme: () => set((state) => ({
    theme: state.theme === 'light' ? 'dark' : 'light'
  })),
}));

React Query for Server State

// api/queries/useItems.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { itemsApi } from '../client';

export function useItems() {
  return useQuery({
    queryKey: ['items'],
    queryFn: itemsApi.getAll,
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
}

export function useCreateItem() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: itemsApi.create,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['items'] });
    },
  });
}

Routing

React Router (Vite/CRA)

// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';

export function App(): JSX.Element {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/items/:id" element={<ItemPage />} />
        <Route path="*" element={<NotFoundPage />} />
      </Routes>
    </BrowserRouter>
  );
}

Protected Routes

interface ProtectedRouteProps {
  children: JSX.Element;
}

function ProtectedRoute({ children }: ProtectedRouteProps): JSX.Element {
  const { user } = useAppStore();
  const location = useLocation();

  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

Styling

CSS Modules (Preferred)

// Button.module.css
.primary {
  background: var(--color-primary);
  color: white;
}

.secondary {
  background: transparent;
  border: 1px solid var(--color-primary);
}

// Button.tsx
import styles from './Button.module.css';

<button className={styles.primary}>Click</button>

Tailwind (Alternative)

// Use consistent patterns, extract repeated combinations
const buttonVariants = {
  primary: 'bg-blue-500 text-white hover:bg-blue-600',
  secondary: 'bg-transparent border border-blue-500 text-blue-500',
} as const;

<button className={buttonVariants[variant]}>{label}</button>

Forms

React Hook Form + Zod

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

const schema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

type FormData = z.infer<typeof schema>;

export function LoginForm(): JSX.Element {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FormData) => {
    // handle submit
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}

      <input type="password" {...register('password')} />
      {errors.password && <span>{errors.password.message}</span>}

      <button type="submit">Login</button>
    </form>
  );
}

Testing

Component Testing with React Testing Library

import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('calls onClick when clicked', () => {
    const onClick = vi.fn();
    render(<Button label="Click me" onClick={onClick} />);

    fireEvent.click(screen.getByText('Click me'));

    expect(onClick).toHaveBeenCalledTimes(1);
  });

  it('does not call onClick when disabled', () => {
    const onClick = vi.fn();
    render(<Button label="Click me" onClick={onClick} disabled />);

    fireEvent.click(screen.getByText('Click me'));

    expect(onClick).not.toHaveBeenCalled();
  });

  it('applies correct variant class', () => {
    render(<Button label="Click" onClick={() => {}} variant="secondary" />);

    expect(screen.getByRole('button')).toHaveClass('secondary');
  });
});

Hook Testing

import { renderHook, act, waitFor } from '@testing-library/react';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('increments counter', () => {
    const { result } = renderHook(() => useCounter());

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });
});

E2E with Playwright

// tests/e2e/login.spec.ts
import { test, expect } from '@playwright/test';

test('user can login', async ({ page }) => {
  await page.goto('/login');

  await page.fill('[name="email"]', 'test@example.com');
  await page.fill('[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByText('Welcome')).toBeVisible();
});

Visual validation (default for web)

Behavior tests prove logic; they don't prove the UI actually looks and flows right. For any user-facing flow, close the loop visually before calling it done:

  • demo-video (default) — a captioned Playwright walkthrough that records the real flow, doubles as a passing E2E test, and produces a stakeholder proof mp4. This is the harness's default visual validation for web work; a user-facing flow isn't "done" without it (see the Definition of Done in base).
  • visual-validation — autonomous screenshot-regression checks for pixel-level UI changes.

Together: playwright-testing proves behavior, visual-validation catches visual regressions, demo-video proves the whole flow the way a person sees it.


Performance

Memoization

// Memoize expensive components
const ItemList = memo(function ItemList({ items }: ItemListProps) {
  return items.map(item => <ItemCard key={item.id} item={item} />);
});

// Memoize callbacks passed to children
const handleClick = useCallback((id: string) => {
  setSelectedId(id);
}, []);

// Memoize expensive computations
const sortedItems = useMemo(() => {
  return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]);

Code Splitting

// Lazy load routes
const ItemPage = lazy(() => import('./pages/Item'));

<Suspense fallback={<Spinner />}>
  <Route path="/items/:id" element={<ItemPage />} />
</Suspense>

React Web Anti-Patterns

  • ❌ Inline functions in JSX - use useCallback
  • ❌ Logic in render - extract to hooks
  • ❌ Deep component nesting - flatten hierarchy
  • ❌ Index as key in lists - use stable IDs
  • ❌ Direct state mutation - always use setter
  • ❌ Prop drilling > 2 levels - use context or state management
  • ❌ useEffect for derived state - use useMemo
  • ❌ Fetching in useEffect - use React Query
  • ❌ Mixing business logic with UI - keep core/ pure
  • ❌ Large components (>100 lines) - split into smaller pieces
  • ❌ CSS in JS objects - use CSS modules or Tailwind
  • ❌ Ignoring TypeScript errors - fix them

FAQ

What does the react-web skill do?

React web development with hooks, React Query, Zustand

How do I install react-web?

Run npx skills add https://github.com/alinaqi/maggy --skill react-web in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/react-web/ (personal) or .claude/skills/react-web/ (project).

Which AI agents can use react-web?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is react-web safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains react-web?

It is published by alinaqi in alinaqi/maggy. It has 339 recorded installs.

More from alinaqi/maggy

View all →

pwa-development

alinaqi/maggy

Progressive Web Apps - service workers, caching strategies, offline, Workbox

4.1K installs

playwright-testing

alinaqi/maggy

E2E testing with Playwright - Page Objects, cross-browser, CI/CD

Testing & QA3.3K installs

android-kotlin

alinaqi/maggy

Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing

Mobile3.3K installs

code-review

alinaqi/maggy

Mandatory code reviews via /code-review before commits and deploys

ui-mobile

alinaqi/maggy

Mobile UI patterns - React Native, iOS/Android, touch targets

Mobile1.2K installs

posthog-analytics

alinaqi/maggy

PostHog analytics, event tracking, feature flags, dashboards

Data & Databases809 installs

Popular frontend & design skills

View category →

setup-matt-pocock-skills

mattpocock/skills

Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills.

Frontend & Design1.1M installs

frontend-design

anthropics/skills

Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.

OfficialFrontend & Design1M installs

hyperframes-animation

heygen-com/hyperframes

All animation knowledge for HyperFrames — atomic motion rules, multi-phase scene blueprints, scene transitions, broader motion-design techniques, AND the seven runtime adapters (GSAP default, plus Lottie, Three.js, Anime.js, CSS keyframes, Web Animations API, TypeGPU). Use for any motion or animation task: pick 2-4 rules and compose, or load a blueprint, or look up runtime-specific API (e.g. GSAP eases / Lottie player / Three.js mixer). Also covers auditing an existing composition's choreography (animation map) and 24 named text-animation effects. HyperFrames-native: single paused timeline, seek-safe, deterministic.

prototype

mattpocock/skills

Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.

Frontend & Design999.2K installs

hyperframes-core

heygen-com/hyperframes

The HyperFrames composition contract — build one renderable project. Use for composition structure, the data- timing attributes, class="clip", tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Read before writing composition HTML.

Frontend & Design930.5K installs

vercel-react-best-practices

vercel-labs/agent-skills

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.

OfficialFrontend & Design843.1K installs