react-component

from vapvarun/claude-backup

Personal backup of Claude Code skills and plugins

5 stars0 forksUpdated Jan 26, 2026
npx skills add https://github.com/vapvarun/claude-backup --skill react-component

SKILL.md

React Component Development

Modern React patterns with TypeScript, hooks, and best practices.

Component Structure

Basic Component Template

import { useState, useCallback, memo } from 'react';
import type { FC, ReactNode } from 'react';
import styles from './Button.module.css';

interface ButtonProps {
  /** Button content */
  children: ReactNode;
  /** Button variant style */
  variant?: 'primary' | 'secondary' | 'danger';
  /** Size of the button */
  size?: 'sm' | 'md' | 'lg';
  /** Whether the button is disabled */
  disabled?: boolean;
  /** Loading state */
  loading?: boolean;
  /** Click handler */
  onClick?: () => void;
  /** Additional CSS classes */
  className?: string;
}

export const Button: FC<ButtonProps> = memo(({
  children,
  variant = 'primary',
  size = 'md',
  disabled = false,
  loading = false,
  onClick,
  className,
}) => {
  const handleClick = useCallback(() => {
    if (!disabled && !loading && onClick) {
      onClick();
    }
  }, [disabled, loading, onClick]);

  return (
    <button
      type="button"
      className={`${styles.button} ${styles[variant]} ${styles[size]} ${className ?? ''}`}
      disabled={disabled || loading}
      onClick={handleClick}
      aria-busy={loading}
    >
      {loading ? <Spinner size="sm" /> : children}
    </button>
  );
});

Button.displayName = 'Button';

Hooks Best Practices

useState

// BAD: Multiple related states
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');

// GOOD: Group related state
interface FormData {
  firstName: string;
  lastName: string;
  email: string;
}

const [formData, setFormData] = useState<FormData>({
  firstName: '',
  lastName: '',
  email: '',
});

// Update single field
const updateField = (field: keyof FormData, value: string) => {
  setFormData(prev => ({ ...prev, [field]: value }));
};

useEffect

// BAD: Missing cleanup
useEffect(() => {
  const subscription = api.subscribe(handler);
  // Memory leak - no cleanup!
}, []);

// GOOD: Proper cleanup
useEffect(() => {
  const subscription = api.subscribe(handler);
  return () => subscription.unsubscribe();
}, [handler]);

// BAD: Stale closure
useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1); // count is stale!
  }, 1000);
  return () => clearInterval(interval);
}, []); // Missing count dependency

// GOOD: Functional update
useEffect(() => {
  const interval = setInterval(() => {
    setCount(prev => prev + 1); // Always uses latest
  }, 1000);
  return () => clearInterval(interval);
}, []);

useCallback & useMemo

// useCallback - Memoize functions
const handleSubmit = useCallback(async (data: FormData) => {
  await api.submit(data);
  onSuccess();
}, [onSuccess]);

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

// useMemo - Memoize objects/arrays passed as props
const config = useMemo(() => ({
  theme: 'dark',
  locale: 'en',
}), []); // Stable reference

// DON'T over-memoize simple values
// BAD
const doubled = useMemo(() => count * 2, [count]);

// GOOD - Simple math doesn't need memoization
const doubled = count * 2;

useRef

// DOM references
const inputRef = useRef<HTMLInputElement>(null);

const focusInput = () => {
  inputRef.current?.focus();
};

// Mutable values that don't trigger re-renders
const renderCount = useRef(0);
renderCount.current += 1;

// Previous value pattern
function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

Custom Hooks

Data Fetching Hook

interface UseFetchResult<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
  refetch: () => void;
}

function useFetch<T>(url: string): UseFetchResult<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);
      const response = await fetch(url);
      if (!response.ok) throw new Error('Fetch failed');
      const json = await response.json();
      setData(json);
    } catch (err) {
      setError(err instanceof Error ? err : new Error('Unknown error'));
    } finally {
      setLoading(false);
    }
  }, [url]);

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

  return { data, loading, error, refetch: fetchData };
}

Form Hook

interface UseFormOptions<T> {
  initialValues: T;
  validate?: (values: T) => Partial<Record<keyof T, string>>;
  onSubmit: (values: T) => void | Promise<void>;
}

function useForm<T extends Record<string, any>>({
  initialValues,
  validate,
  onSubmit,
}: UseFormOptions<T>) {
  const [values

...
Read full content

Repository Stats

Stars5
Forks0