Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add TheSimpleApp/agent-skills --skill "react-excellence"
Install specific skill from multi-skill repository
# Description
React/Next.js development excellence with clean architecture, TypeScript best practices, and production patterns. Use when building or improving React apps.
# SKILL.md
name: react-excellence
description: React/Next.js development excellence with clean architecture, TypeScript best practices, and production patterns. Use when building or improving React apps.
license: MIT
metadata:
author: thesimpleapp
version: "1.0"
React Excellence
Build scalable, maintainable React applications with TypeScript and modern patterns.
Architecture Standard
Folder Structure (Next.js App Router)
src/
├── app/ # App Router pages
│ ├── (auth)/ # Route groups
│ │ ├── login/
│ │ └── register/
│ ├── dashboard/
│ └── layout.tsx
├── features/ # Feature modules
│ └── [feature]/
│ ├── components/ # Feature UI
│ ├── hooks/ # Feature hooks
│ ├── services/ # API/business logic
│ ├── types/ # Feature types
│ └── index.ts # Public exports
├── shared/
│ ├── components/
│ │ ├── ui/ # Base UI components
│ │ └── layout/ # Layout components
│ ├── hooks/ # Shared hooks
│ ├── lib/ # Utilities
│ └── types/ # Shared types
├── config/ # App configuration
└── styles/ # Global styles
TypeScript Standards
Type Definitions
// Always use interfaces for objects
interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
// Use type for unions/intersections
type Status = 'idle' | 'loading' | 'success' | 'error';
// Generic types for reusability
type ApiResponse<T> = {
data: T;
error: null;
} | {
data: null;
error: string;
};
Component Props
// Props interface with JSDoc
interface ButtonProps {
/** Button label text */
children: React.ReactNode;
/** Click handler */
onClick?: () => void;
/** Visual variant */
variant?: 'primary' | 'secondary' | 'ghost';
/** Disabled state */
disabled?: boolean;
/** Loading state */
loading?: boolean;
}
// Use React.FC sparingly, prefer explicit return
function Button({
children,
onClick,
variant = 'primary',
disabled = false,
loading = false,
}: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled || loading}
className={cn(buttonVariants({ variant }))}
>
{loading ? <Spinner /> : children}
</button>
);
}
Component Patterns
Composition over Configuration
// ❌ Avoid: Too many props
<Card
title="User"
subtitle="Details"
image="/user.jpg"
actions={[...]}
footer={...}
/>
// ✅ Prefer: Composition
<Card>
<Card.Header>
<Card.Title>User</Card.Title>
<Card.Subtitle>Details</Card.Subtitle>
</Card.Header>
<Card.Image src="/user.jpg" />
<Card.Content>...</Card.Content>
<Card.Footer>...</Card.Footer>
</Card>
Custom Hooks
// Data fetching hook
function useUser(userId: string) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => userService.getUser(userId),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
// Form hook
function useLoginForm() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (data: LoginData) => {
setIsLoading(true);
setError(null);
try {
await authService.login(data);
} catch (e) {
setError(e instanceof Error ? e.message : 'Login failed');
} finally {
setIsLoading(false);
}
};
return { handleSubmit, isLoading, error };
}
State Management
Server State (TanStack Query)
// queries.ts
export const userQueries = {
all: () => ['users'] as const,
lists: () => [...userQueries.all(), 'list'] as const,
list: (filters: Filters) => [...userQueries.lists(), filters] as const,
details: () => [...userQueries.all(), 'detail'] as const,
detail: (id: string) => [...userQueries.details(), id] as const,
};
// Usage
const { data } = useQuery({
queryKey: userQueries.detail(userId),
queryFn: () => getUser(userId),
});
Client State (Zustand)
interface AuthStore {
user: User | null;
isAuthenticated: boolean;
login: (user: User) => void;
logout: () => void;
}
const useAuthStore = create<AuthStore>((set) => ({
user: null,
isAuthenticated: false,
login: (user) => set({ user, isAuthenticated: true }),
logout: () => set({ user: null, isAuthenticated: false }),
}));
Supabase Integration
Client Setup
// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';
export const createClient = () =>
createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export const createServerSupabase = () => {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get: (name) => cookieStore.get(name)?.value,
set: (name, value, options) => cookieStore.set(name, value, options),
remove: (name, options) => cookieStore.set(name, '', options),
},
}
);
};
Repository Pattern
// features/users/services/userRepository.ts
class UserRepository {
constructor(private supabase: SupabaseClient) {}
async getUser(id: string): Promise<User> {
const { data, error } = await this.supabase
.from('users')
.select('*')
.eq('id', id)
.single();
if (error) throw new Error(error.message);
return data;
}
async updateUser(id: string, updates: Partial<User>): Promise<User> {
const { data, error } = await this.supabase
.from('users')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) throw new Error(error.message);
return data;
}
}
Error Handling
Error Boundary
'use client';
interface ErrorBoundaryProps {
children: React.ReactNode;
fallback?: React.ReactNode;
}
class ErrorBoundary extends React.Component<
ErrorBoundaryProps,
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('Error:', error, info);
// Send to error tracking service
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <DefaultErrorFallback />;
}
return this.props.children;
}
}
API Error Handling
class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
public code?: string
) {
super(message);
this.name = 'ApiError';
}
}
async function fetchWithErrorHandling<T>(
url: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new ApiError(
error.message || 'Request failed',
response.status,
error.code
);
}
return response.json();
}
Testing Standards
Component Tests
import { render, screen, userEvent } from '@testing-library/react';
describe('LoginForm', () => {
it('submits with valid credentials', async () => {
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
await userEvent.type(screen.getByLabelText(/email/i), '[email protected]');
await userEvent.type(screen.getByLabelText(/password/i), 'password123');
await userEvent.click(screen.getByRole('button', { name: /sign in/i }));
expect(onSubmit).toHaveBeenCalledWith({
email: '[email protected]',
password: 'password123',
});
});
it('shows error for invalid email', async () => {
render(<LoginForm onSubmit={() => {}} />);
await userEvent.type(screen.getByLabelText(/email/i), 'invalid');
await userEvent.click(screen.getByRole('button', { name: /sign in/i }));
expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
});
});
Performance Checklist
□ Use React.memo for expensive pure components
□ Use useMemo/useCallback appropriately (not everywhere)
□ Lazy load routes and heavy components
□ Optimize images with next/image
□ Use Suspense boundaries
□ Profile with React DevTools
□ Check Core Web Vitals
Styling (Tailwind + shadcn/ui)
Component Variants with CVA
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
{
variants: {
variant: {
primary: 'bg-primary text-white hover:bg-primary/90',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4',
lg: 'h-12 px-6 text-lg',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.