# Patrones de Componentes - Frontend GAMILIT **Fecha de creacion:** 2025-11-29 **Version:** 1.0 **Estado:** VIGENTE **Contexto:** Estandarizacion de 180+ componentes React --- ## 1. Principios Fundamentales ### 1.1 Filosofia de Componentes - **Componentes pequenos y enfocados**: Una responsabilidad por componente - **Composicion sobre herencia**: Componer componentes simples - **Props explicitas**: Siempre tipar props con interfaces - **Colocacion**: Mantener archivos relacionados juntos ### 1.2 Regla de Oro > Si un componente supera las 150 lineas, probablemente debe dividirse. --- ## 2. Anatomia de un Componente Estandar ### 2.1 Estructura de Archivo ```typescript /** * ComponentName * @description Breve descripcion del componente * @see Backend: [ruta si aplica] */ // 1. Imports externos import { useState, useCallback, memo } from 'react'; import { useQuery } from '@tanstack/react-query'; // 2. Imports internos (types, utils, hooks) import type { ComponentProps } from './ComponentName.types'; import { formatDate } from '@shared/utils'; import { useAuth } from '@features/auth/hooks'; // 3. Imports de componentes import { Button, Card } from '@shared/components/ui'; import { LoadingSpinner } from '@shared/components/feedback'; // 4. Constantes locales (si son pocas) const DEFAULT_PAGE_SIZE = 10; // 5. Componente principal export const ComponentName = memo(function ComponentName({ prop1, prop2, onAction, }: ComponentProps) { // 5.1 Hooks (siempre al inicio) const { user } = useAuth(); const [state, setState] = useState(''); // 5.2 Queries/Mutations const { data, isLoading } = useQuery({...}); // 5.3 Handlers (useCallback para optimizar) const handleClick = useCallback(() => { onAction?.(state); }, [state, onAction]); // 5.4 Early returns (loading, error, empty) if (isLoading) return ; if (!data) return null; // 5.5 Render principal return (

{prop1}

); }); // 6. Display name (para debugging) ComponentName.displayName = 'ComponentName'; ``` ### 2.2 Archivo de Types ```typescript // ComponentName.types.ts export interface ComponentProps { /** Descripcion de prop1 */ prop1: string; /** Descripcion de prop2 */ prop2: string; /** Callback cuando se ejecuta accion */ onAction?: (value: string) => void; /** Clases CSS adicionales */ className?: string; /** Contenido hijo */ children?: React.ReactNode; } export interface ComponentState { isOpen: boolean; selectedId: string | null; } ``` --- ## 3. Estructura de Carpetas por Feature ### 3.1 Componente Simple (1 archivo) ``` features/auth/components/ └── LoginButton.tsx ``` ### 3.2 Componente Complejo (carpeta) ``` features/auth/components/LoginForm/ ├── index.ts # Re-export ├── LoginForm.tsx # Componente principal ├── LoginForm.types.ts # Types e interfaces ├── LoginForm.styles.ts # Styled components (si aplica) ├── LoginForm.test.tsx # Tests └── useLoginForm.ts # Hook especifico (si aplica) ``` ### 3.3 index.ts (Barrel Export) ```typescript // features/auth/components/LoginForm/index.ts export { LoginForm } from './LoginForm'; export type { LoginFormProps } from './LoginForm.types'; ``` --- ## 4. Props Patterns ### 4.1 Props Basicas ```typescript interface ButtonProps { // Requeridas label: string; onClick: () => void; // Opcionales con defaults variant?: 'primary' | 'secondary'; size?: 'sm' | 'md' | 'lg'; disabled?: boolean; // Children children?: React.ReactNode; // ClassName para extension className?: string; } // Uso con defaults export const Button = ({ label, onClick, variant = 'primary', size = 'md', disabled = false, className, }: ButtonProps) => { ... }; ``` ### 4.2 Props Compuestas ```typescript // Extender props de elemento HTML interface InputProps extends React.InputHTMLAttributes { label: string; error?: string; } // Uso export const Input = ({ label, error, ...inputProps }: InputProps) => (
{error && {error}}
); ``` ### 4.3 Render Props ```typescript interface DataListProps { data: T[]; renderItem: (item: T, index: number) => React.ReactNode; renderEmpty?: () => React.ReactNode; } export function DataList({ data, renderItem, renderEmpty, }: DataListProps) { if (data.length === 0 && renderEmpty) { return <>{renderEmpty()}; } return
    {data.map(renderItem)}
; } ``` --- ## 5. Composicion de Componentes ### 5.1 Compound Components ```typescript // Card.tsx interface CardProps { children: React.ReactNode; } interface CardHeaderProps { title: string; action?: React.ReactNode; } export const Card = ({ children }: CardProps) => (
{children}
); Card.Header = ({ title, action }: CardHeaderProps) => (

{title}

{action}
); Card.Body = ({ children }: { children: React.ReactNode }) => (
{children}
); Card.Footer = ({ children }: { children: React.ReactNode }) => (
{children}
); // Uso Accion} /> Contenido Footer ``` ### 5.2 HOCs (Higher Order Components) ```typescript // withAuth.tsx export function withAuth

( WrappedComponent: React.ComponentType

) { return function AuthenticatedComponent(props: P) { const { isAuthenticated } = useAuth(); if (!isAuthenticated) { return ; } return ; }; } // Uso export const ProtectedPage = withAuth(DashboardPage); ``` --- ## 6. Performance Patterns ### 6.1 memo para Componentes Puros ```typescript import { memo } from 'react'; // Usar memo cuando: // - Props no cambian frecuentemente // - Componente es costoso de renderizar // - Lista de items grandes export const UserCard = memo(function UserCard({ user }: UserCardProps) { return (

{user.name}

{user.email}

); }); ``` ### 6.2 useMemo para Calculos Costosos ```typescript import { useMemo } from 'react'; export const Dashboard = ({ data }: DashboardProps) => { // Calcular solo cuando data cambia const processedData = useMemo(() => { return data.map(item => ({ ...item, computed: expensiveCalculation(item), })); }, [data]); return ; }; ``` ### 6.3 useCallback para Handlers Estables ```typescript import { useCallback } from 'react'; export const Form = ({ onSubmit }: FormProps) => { const [value, setValue] = useState(''); // Estable, no se recrea en cada render const handleSubmit = useCallback((e: FormEvent) => { e.preventDefault(); onSubmit(value); }, [value, onSubmit]); return
...
; }; ``` ### 6.4 Lazy Loading ```typescript import { lazy, Suspense } from 'react'; // Cargar componente pesado solo cuando se necesita const HeavyChart = lazy(() => import('./HeavyChart')); export const Dashboard = () => ( }> ); ``` --- ## 7. Error Handling ### 7.1 Error Boundary ```typescript import { Component, ErrorInfo, ReactNode } from 'react'; interface Props { children: ReactNode; fallback?: ReactNode; } interface State { hasError: boolean; error?: Error; } export class ErrorBoundary extends Component { state: State = { hasError: false }; static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('Error caught:', error, errorInfo); // Enviar a servicio de monitoreo } render() { if (this.state.hasError) { return this.props.fallback || ; } return this.props.children; } } // Uso }> ``` ### 7.2 Manejo de Estados Async ```typescript export const UserProfile = ({ userId }: { userId: string }) => { const { data, isLoading, isError, error } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), }); // Estado de carga if (isLoading) { return ; } // Estado de error if (isError) { return ; } // Estado vacio if (!data) { return ; } // Render con datos return ; }; ``` --- ## 8. Patrones de Estado ### 8.1 Estado Local vs Global | Tipo de Estado | Donde Guardarlo | |----------------|-----------------| | UI temporal (modals, dropdowns) | useState local | | Datos del formulario | useState o react-hook-form | | Datos del servidor | React Query | | Estado compartido entre features | Zustand store | | Estado de autenticacion | AuthStore (Zustand) | ### 8.2 Elevacion de Estado ```typescript // Estado elevado al padre cuando multiples hijos lo necesitan const Parent = () => { const [selectedId, setSelectedId] = useState(null); return ( <> ); }; ``` --- ## 9. Testing de Componentes ### 9.1 Estructura de Test ```typescript // ComponentName.test.tsx import { render, screen, fireEvent } from '@testing-library/react'; import { ComponentName } from './ComponentName'; describe('ComponentName', () => { // Setup comun const defaultProps = { prop1: 'test', onAction: vi.fn(), }; it('renders correctly', () => { render(); expect(screen.getByText('test')).toBeInTheDocument(); }); it('calls onAction when clicked', async () => { render(); await fireEvent.click(screen.getByRole('button')); expect(defaultProps.onAction).toHaveBeenCalled(); }); it('shows loading state', () => { render(); expect(screen.getByTestId('loading-spinner')).toBeInTheDocument(); }); }); ``` ### 9.2 Testing de Hooks ```typescript import { renderHook, act } from '@testing-library/react'; import { useCounter } from './useCounter'; describe('useCounter', () => { it('increments correctly', () => { const { result } = renderHook(() => useCounter(0)); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); }); }); ``` --- ## 10. Anti-patrones a Evitar ### 10.1 NO: Props Drilling Profundo ```typescript // MAL {/* 4 niveles! */} // BIEN: Usar Context o Zustand const DataContext = createContext(null); ``` ### 10.2 NO: Logica en JSX ```typescript // MAL
{items.filter(x => x.active).map(x => ( ))}
// BIEN: Extraer logica const activeItems = useMemo(() => items.filter(x => x.active).map(processData), [items] );
{activeItems.map(item => )}
``` ### 10.3 NO: Componentes Dios (>300 lineas) ```typescript // MAL: Un componente hace todo const Dashboard = () => { /* 500 lineas */ }; // BIEN: Dividir en subcomponentes const Dashboard = () => ( <> ); ``` --- ## 11. Checklist de Creacion de Componente Antes de crear un nuevo componente: 1. [ ] Verificar que no existe componente similar 2. [ ] Determinar si es shared o feature-specific 3. [ ] Crear interface de Props en archivo separado 4. [ ] Usar nombres descriptivos (verbo + sustantivo) 5. [ ] Implementar estados: loading, error, empty, success 6. [ ] Agregar memo si es puro y costoso 7. [ ] Escribir al menos 1 test basico 8. [ ] Exportar en barrel file --- ## 12. Referencias - **Hooks:** `docs/95-guias-desarrollo/frontend/HOOK-PATTERNS.md` - **Types:** `docs/95-guias-desarrollo/frontend/TYPES-CONVENTIONS.md` - **React Testing Library:** https://testing-library.com/docs/react-testing-library/intro - **React Patterns:** https://reactpatterns.com/ --- ## 13. Changelog | Version | Fecha | Cambios | |---------|-------|---------| | 1.0 | 2025-11-29 | Creacion inicial |