Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// HOC que añade protección de autenticación
function withAuth<P extends object>(
WrappedComponent: React.ComponentType<P>
) {
function AuthenticatedComponent(props: P) {
const { user, isLoading } = useAuth();
if (isLoading) return <LoadingSpinner />;
if (!user) return <Navigate to="/login" />;
return <WrappedComponent {...props} />;
}
AuthenticatedComponent.displayName =
`withAuth(${WrappedComponent.displayName ?? WrappedComponent.name ?? "Component"})`;
return AuthenticatedComponent;
}
// Uso
const ProtectedDashboard = withAuth(Dashboard);Cuándo usarlo: Cuando necesitas aplicar la misma preocupación transversal (autenticación, logging, temas, obtención de datos) a muchos componentes sin modificarlos. Menos común hoy gracias a los hooks, pero aún útil para envoltorios a nivel de ruta e integración de librerías de terceros.
import { useEffect, useRef, type ComponentType } from "react";
// HOC que rastrea la visibilidad de los componentes y reporta analítica
function withVisibilityTracking<P extends { id: string }>(
WrappedComponent: ComponentType<P>,
eventName: string
) {
function TrackedComponent(props: P) {
const ref = useRef<HTMLDivElement>(null);
const reported = useRef(false);
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !reported.current) {
reported.current = true;
analytics.track(eventName, { componentId: props.id });
}
},
{ threshold: 0.5 }
);
observer.observe(element);
return () => observer.disconnect();
}, [props.id]);
return (
<div ref={ref}>
<WrappedComponent {...props} />
</div>
);
}
TrackedComponent.displayName =
`withVisibilityTracking(${WrappedComponent.displayName ?? WrappedComponent.name ?? "Component"})`;
return TrackedComponent;
}
// Uso
interface ProductCardProps {
id: string;
name: string;
price: number;
}
function ProductCard({ id, name, price }: ProductCardProps) {
return (
<div className="p-4 border rounded">
<h3>{name}</h3>
<p>${price}</p>
</div>
);
}
const TrackedProductCard = withVisibilityTracking(ProductCard, "product_viewed");
// En una página
function ProductGrid({ products }: { products: ProductCardProps[] }) {
return (
<div className="grid grid-cols-3 gap-4">
{products.map((p) => (
<TrackedProductCard key={p.id} {...p} />
))}
</div>
);
}Lo que esto demuestra:
P extends { id: string }) asegura que el componente envuelto tenga los props requeridosdisplayName establecido para depuración en React DevToolsProductCard original permanece puro y comprobable por sí soloenhance(Component) => EnhancedComponent.withAuth(withTheme(withAnalytics(Component))).| Parámetro | Tipo | Propósito |
|---|---|---|
WrappedComponent | ComponentType<P> | El componente a mejorar |
| Configuración (opcional) | Varía | Configuración para el comportamiento del HOC |
| Valor de retorno | ComponentType<P> (o modificado) | Un nuevo componente con comportamiento añadido |
HOC de inyección de props - añade nuevos props al componente envuelto:
interface WithThemeProps {
theme: Theme;
}
function withTheme<P extends WithThemeProps>(
WrappedComponent: ComponentType<P>
) {
function ThemedComponent(props: Omit<P, keyof WithThemeProps>) {
const theme = useTheme();
return <WrappedComponent {...(props as P)} theme={theme} />;
}
ThemedComponent.displayName =
`withTheme(${WrappedComponent.displayName ?? WrappedComponent.name})`;
return ThemedComponent;
}HOCs compuestos - combina múltiples mejoras:
// Composición manual
const EnhancedComponent = withAuth(withTheme(withAnalytics(BaseComponent)));
// Con una utilidad de composición
import { compose } from "redux"; // o escribe la tuya
const enhance = compose(withAuth, withTheme, withAnalytics);
const EnhancedComponent = enhance(BaseComponent);ComponentType<P> para aceptar componentes de función y clase.Omit<P, keyof InjectedProps> para eliminar props inyectadas de la API externa.P extends object al mínimo para evitar tipos primitivos.displayName en cada HOC para que React DevTools sea usable.React.forwardRef dentro de HOCs si el componente envuelto necesita reenvío de refs.Reenvío de refs - Los refs no se pasan automáticamente a través de HOCs porque ref no es un prop regular. Solución: Usa React.forwardRef dentro del envolvente del HOC.
Métodos estáticos perdidos - Las propiedades estáticas en el componente original no se copian automáticamente al envolvente. Solución: Usa hoist-non-react-statics o copia manualmente las propiedades estáticas necesarias.
Colisiones de nombres de props - Si el HOC inyecta un prop con el mismo nombre que un prop existente, sobrescribe silenciosamente. Solución: Usa nombres de espacio para props inyectados o un prefijo único.
Re-crear el HOC en cada renderizado - Llamar un HOC dentro de un cuerpo de componente crea un nuevo tipo de componente en cada renderizado, destruyendo todo el estado. Solución: Siempre llama HOCs en el ámbito del módulo o en useMemo con extrema precaución.
Dificultad para depurar - HOCs profundamente anidados crean árboles de componentes largos en DevTools. Solución: Establece displayName en cada HOC y considera si los hooks serían más claros.
| Enfoque | Compensación |
|---|---|
| Componentes de orden superior | Mejora transparente; tipado complejo, depuración más difícil |
| Hooks personalizados | Más simples, componibles, mejores tipos; no pueden envolver renderizado |
| Render props | Flujo de datos explícito; más verboso en el sitio de llamada |
| Middleware (Next.js) | Mejor para preocupaciones a nivel de ruta como autenticación en aplicaciones Next.js |
| Decoradores (stage 3) | Azúcar sintáctico; aún no ampliamente soportado en el ecosistema React |
enhance(Component) => EnhancedComponent.// Composición manual
const Enhanced = withAuth(withTheme(withAnalytics(Base)));
// Con una utilidad de composición
const enhance = compose(withAuth, withTheme, withAnalytics);
const Enhanced = enhance(Base);compose.withAnalytics se aplica primero, luego withTheme, luego withAuth.displayName, React DevTools muestra el componente envolvente como "Anonymous" o el nombre interno del envolvente.displayName a withAuth(Dashboard) hace que el árbol de componentes sea legible.displayName o name del componente envuelto.ref no es un prop regular en React; se maneja especialmente y se extrae antes de llegar al componente envuelto.React.forwardRef dentro del HOC para reenviar refs al componente envuelto.interface WithThemeProps { theme: Theme; }
function withTheme<P extends WithThemeProps>(
WrappedComponent: ComponentType<P>
) {
function Themed(props: Omit<P, keyof WithThemeProps>) {
const theme = useTheme();
return <WrappedComponent {...(props as P)} theme={theme} />;
}
return Themed;
}Omit<P, keyof InjectedProps> para eliminar nombres de props inyectados del tipo que ve el consumidor.P extends InjectedProps para que TypeScript sepa que el componente envuelto acepta esos props.Omit<P, keyof InjectedProps> elimina los nombres de props inyectados del tipo que ve el consumidor.theme cuando usa withTheme(Component) porque el HOC lo proporciona.P extends object al mínimo para prevenir parámetros de tipo primitivo.hoist-non-react-statics o copia manualmente las propiedades estáticas necesarias.getStaticProps.themeConfig en lugar de config) o un prefijo único.Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥