Claude-skill-registry frontend-react

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/frontend-react" ~/.claude/skills/majiayu000-claude-skill-registry-frontend-react && rm -rf "$T"
manifest: skills/data/frontend-react/SKILL.md
source content

React Frontend Stack

Live docs: Add

use context7
to prompt for up-to-date React, TanStack Query, Tailwind documentation.

Quick Reference

TopicReference
Componentscomponents.md — Button, Input, Modal, patterns
Statestate.md — useState, Zustand, Context, URL state

Tooling (2025)

ToolPurposeWhy
ViteBuild toolFast HMR, ESM native
React 19UI libraryRSC, Actions, use()
TypeScriptType safetyStrict mode
Tailwind v4StylingUtility-first, Vite plugin
TanStack QueryData fetchingCaching, mutations
ZustandStateSimple, no boilerplate
React Router 7RoutingData loading, actions

Notes:

  • Tailwind v4: new config via Vite plugin, no
    tailwind.config.js
  • TanStack Router may require React 18.3.1 (use
    --legacy-peer-deps
    if needed)

Project Setup

pnpm create vite@latest my-app --template react-ts
cd my-app
pnpm add @tanstack/react-query zustand
pnpm add -D tailwindcss @tailwindcss/vite

Vite Config

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: { '@': '/src' },
  },
});

Tailwind v4

/* src/index.css */
@import "tailwindcss";

Project Structure

src/
├── main.tsx             # Entry point
├── App.tsx              # Root component
├── index.css            # Tailwind imports
├── components/
│   ├── ui/              # Reusable UI (Button, Input, Card)
│   └── features/        # Feature components
├── pages/               # Route components
├── hooks/               # Custom hooks
├── stores/              # Zustand stores
├── api/                 # API client, queries
├── types/               # TypeScript types
└── lib/                 # Utilities

Component Patterns

Functional Component

interface UserCardProps {
  user: User;
  onEdit?: (id: string) => void;
}

export function UserCard({ user, onEdit }: UserCardProps) {
  return (
    <div className="rounded-lg border p-4">
      <h3 className="font-semibold">{user.name}</h3>
      <p className="text-sm text-gray-600">{user.email}</p>
      {onEdit && (
        <button
          onClick={() => onEdit(user.id)}
          className="mt-2 text-blue-600 hover:underline"
        >
          Edit
        </button>
      )}
    </div>
  );
}

Component with Children

interface CardProps {
  title: string;
  children: React.ReactNode;
  className?: string;
}

export function Card({ title, children, className }: CardProps) {
  return (
    <div className={cn("rounded-lg border bg-white p-6", className)}>
      <h2 className="mb-4 text-lg font-semibold">{title}</h2>
      {children}
    </div>
  );
}

Polymorphic Component

type ButtonProps<T extends React.ElementType = 'button'> = {
  as?: T;
  variant?: 'primary' | 'secondary';
  children: React.ReactNode;
} & React.ComponentPropsWithoutRef<T>;

export function Button<T extends React.ElementType = 'button'>({
  as,
  variant = 'primary',
  children,
  className,
  ...props
}: ButtonProps<T>) {
  const Component = as || 'button';
  return (
    <Component
      className={cn(
        'px-4 py-2 rounded-md font-medium',
        variant === 'primary' && 'bg-blue-600 text-white',
        variant === 'secondary' && 'bg-gray-200 text-gray-800',
        className
      )}
      {...props}
    >
      {children}
    </Component>
  );
}

// Usage
<Button>Click me</Button>
<Button as="a" href="/about">Link</Button>

Hooks

Custom Hook Example

function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

useDebounce

function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

TanStack Query

Setup

// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000,
      retry: 1,
    },
  },
});

<QueryClientProvider client={queryClient}>
  <App />
</QueryClientProvider>

Query Hook

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

export function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(r => r.json()),
  });
}

export function useUser(id: string) {
  return useQuery({
    queryKey: ['users', id],
    queryFn: () => fetch(`/api/users/${id}`).then(r => r.json()),
    enabled: !!id,
  });
}

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

  return useMutation({
    mutationFn: (data: CreateUser) =>
      fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      }).then(r => r.json()),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

Usage in Component

function UserList() {
  const { data: users, isLoading, error } = useUsers();
  const createUser = useCreateUser();

  if (isLoading) return <Spinner />;
  if (error) return <Error message={error.message} />;

  return (
    <div>
      {users.map(user => <UserCard key={user.id} user={user} />)}
      <button onClick={() => createUser.mutate({ name: 'New' })}>
        Add User
      </button>
    </div>
  );
}

Zustand State

// stores/auth.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  user: User | null;
  token: string | null;
  login: (user: User, token: string) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      token: null,
      login: (user, token) => set({ user, token }),
      logout: () => set({ user: null, token: null }),
    }),
    { name: 'auth-storage' }
  )
);

// Usage
const { user, login, logout } = useAuthStore();

Forms

import { useState, FormEvent } from 'react';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState<Record<string, string>>({});

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setErrors({});

    if (!email) {
      setErrors(prev => ({ ...prev, email: 'Required' }));
      return;
    }

    // Submit...
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <input
          type="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
          className="w-full rounded border px-3 py-2"
          placeholder="Email"
        />
        {errors.email && <p className="text-sm text-red-500">{errors.email}</p>}
      </div>
      <button type="submit" className="w-full rounded bg-blue-600 py-2 text-white">
        Login
      </button>
    </form>
  );
}

Anti-patterns

Don'tDo Instead
CRA (Create React App)Vite
CSS Modules / styled-componentsTailwind
Redux (complex)Zustand (simple)
useEffect for data fetchingTanStack Query
Prop drillingContext or Zustand
any
types
Proper TypeScript types
Index as keyUnique ID as key
Inline object propsuseMemo or extract