TheSimpleApp

react-excellence

0
0
# Install this skill:
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.