Volver a React Intermedio

Patrones Avanzados de React

Estos patrones resuelven problemas comunes de composición y reutilización de forma elegante.

Componentes Compuestos

Componentes que comparten estado implícito a través de Context:

interface ContextoPestañas { pestañaActiva: string; setPestañaActiva: (pestaña: string) => void; } const ContextoPestañas = createContext<ContextoPestañas | null>(null); function Pestañas({ defaultPestaña, children }: { defaultPestaña: string; children: ReactNode }) { const [pestañaActiva, setPestañaActiva] = useState(defaultPestaña); return ( <ContextoPestañas.Provider value={{ pestañaActiva, setPestañaActiva }}> <div className="pestañas">{children}</div> </ContextoPestañas.Provider> ); } function ListaPestañas({ children }: { children: ReactNode }) { return <div className="lista-pestañas" role="tablist">{children}</div>; } function Pestaña({ id, children }: { id: string; children: ReactNode }) { const ctx = useContext(ContextoPestañas)!; return ( <button role="tab" aria-selected={ctx.pestañaActiva === id} onClick={() => ctx.setPestañaActiva(id)} className={ctx.pestañaActiva === id ? 'activa' : ''} > {children} </button> ); } function PanelPestaña({ id, children }: { id: string; children: ReactNode }) { const ctx = useContext(ContextoPestañas)!; if (ctx.pestañaActiva !== id) return null; return <div role="tabpanel">{children}</div>; } Pestañas.Lista = ListaPestañas; Pestañas.Pestaña = Pestaña; Pestañas.Panel = PanelPestaña; // Uso function App() { return ( <Pestañas defaultPestaña="perfil"> <Pestañas.Lista> <Pestañas.Pestaña id="perfil">Perfil</Pestañas.Pestaña> <Pestañas.Pestaña id="config">Configuración</Pestañas.Pestaña> </Pestañas.Lista> <Pestañas.Panel id="perfil"><Perfil /></Pestañas.Panel> <Pestañas.Panel id="config"><Configuracion /></Pestañas.Panel> </Pestañas> ); }

Render Props

Pasar una función como prop para compartir lógica:

interface PosMouse { x: number; y: number; } function RastreadorRaton({ render }: { render: (pos: PosMouse) => ReactNode }) { const [pos, setPos] = useState<PosMouse>({ x: 0, y: 0 }); return ( <div className="rastreador" onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })} > {render(pos)} </div> ); } // Uso <RastreadorRaton render={({ x, y }) => ( <div>Ratón: {x}, {y}</div> )} />

Higher-Order Components (HOC)

function conAutenticacion<P extends object>(Componente: React.ComponentType<P>) { return function ComponenteAutenticado(props: P) { const { usuario, cargando } = useAuth(); if (cargando) return <Spinner />; if (!usuario) return <Navigate to="/login" />; return <Componente {...props} />; }; } // Uso const DashboardProtegido = conAutenticacion(Dashboard);

Máquinas de Estado con useReducer

type Estado = 'inactivo' | 'cargando' | 'exitoso' | 'error'; interface EstadoFetch<T> { estado: Estado; datos: T | null; error: string | null; } type AccionFetch<T> = | { type: 'FETCH' } | { type: 'EXITO'; datos: T } | { type: 'ERROR'; error: string }; function reductorFetch<T>( state: EstadoFetch<T>, action: AccionFetch<T> ): EstadoFetch<T> { switch (action.type) { case 'FETCH': return { estado: 'cargando', datos: null, error: null }; case 'EXITO': return { estado: 'exitoso', datos: action.datos, error: null }; case 'ERROR': return { estado: 'error', datos: null, error: action.error }; default: return state; } }