¿Qué es un Higher-Order Component?
Imagina que tienes múltiples componentes en tu aplicación que necesitan la misma funcionalidad: autenticación, suscripción a datos, logging, control de acceso, entre otros. ¿Cómo compartirías esa lógica sin repetir código? Aquí es donde los HOCs entran en acción.
Un HOC es esencialmente una función de orden superior, siguiendo el mismo principio que las funciones de orden superior en JavaScript (como map, filter, reduce). La diferencia es que en lugar de operar sobre arrays o valores, operan sobre componentes de React.
Sintaxis básica de un HOC
const withSomething = (WrappedComponent) => {
return function EnhancedComponent(props) {
// Lógica adicional aquí
return ;
};
};Veamos esto desglosado:
- withSomething: Es el HOC, una función que recibe un componente (
WrappedComponent) - WrappedComponent: Es el componente original que será "envuelto" con funcionalidad adicional
- EnhancedComponent: Es el nuevo componente que se retorna, con las props originales más la nueva funcionalidad
Ejemplo práctico: HOC de autenticación
Uno de los usos más comunes de los HOCs es proteger rutas o componentes que requieren autenticación. Veamos cómo implementar esto:
// HOC de autenticación
const withAuth = (WrappedComponent) => {
return function AuthenticatedComponent(props) {
const { isAuthenticated, user } = useAuth(); // Hook de autenticación
if (!isAuthenticated) {
return ;
}
return (
);
};
};
// Uso del HOC
const Dashboard = withAuth(function Dashboard({ user }) {
return Bienvenido, {user.name}
;
});with> (como withAuth>, withLoading>, withTranslation>), lo que indica claramente que es un componente de orden superior.Pasando props a través del HOC
Es crucial que un HOC pase las props al componente envuelto. Esto se hace mediante el spread operator {...props}. Pero, ¿qué pasa cuando el HOC también necesita recibir props?
const withDataFetching = (WrappedComponent, dataUrl) => {
return function DataFetcher(props) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(dataUrl)
.then(res => res.json())
.then(result => {
setData(result);
setLoading(false);
});
}, [dataUrl]);
// Pasamos las props originales + los nuevos datos
return (
);
};
};data y el componente envuelto también tiene una prop data, habrá un conflicto. Usa nombres únicos o documenta claramente las props que añade el HOC.HOC para manejo de errores
Otro patrón útil es crear un HOC que maneje errores en componentes que realizan operaciones asíncronas:
const withErrorBoundary = (WrappedComponent) => {
return function ErrorBoundaryWrapper(props) {
const [hasError, setHasError] = useState(false);
const [error, setError] = useState(null);
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
if (hasError) {
return (
Algo salió mal
{error.message}
);
}
return ;
};
};HOC para estados de carga
Crear estados de carga repetitivos puede ser tedioso. Con un HOC, podemos encapsular esta lógica:
const withLoading = (WrappedComponent) => {
return function LoadingWrapper({ isLoading, ...props }) {
if (isLoading) {
return (
Cargando...
);
}
return ;
};
};
// Componente envuelto sin lógica de loading
const UserProfile = ({ user }) => (
{user.name}
);
// Componente envuelto con HOC
const UserProfileWithLoading = withLoading(UserProfile);
// Uso
Encadenamiento de HOCs
Una de las ventajas de los HOCs es que se pueden encadenar. Esto permite compose múltiples funcionalidades:
const enhance = compose(
withAuth, // Primero: verificar autenticación
withLoading, // Segundo: mostrar loading si es necesario
withErrorBoundary, // Tercero: capturar errores
withTranslation() // Cuarto: proporcionar traducciones
);
const EnhancedComponent = enhance(UserProfile);
// O usando la composición manualmente
const EnhancedUserProfile = withTranslation(
withErrorBoundary(
withLoading(
withAuth(UserProfile)
)
)
);La función compose (disponible en librerías como lodash/fp o recompose) simplemente facilita leer este encadenamiento de derecha a izquierda.
HOCs vs Hooks: ¿Cuándo usar cada uno?
Con la introducción de los Hooks en React 16.8, surge la pregunta: ¿los HOCs siguen siendo relevantes? La respuesta es sí, pero cada uno tiene su caso de uso:
| Característica | HOCs | Hooks |
|---|---|---|
| Reutilización | Lógica compartida entre componentes | Reutilización dentro de un solo componente |
| Props | Pueden añadir/transformar props | No modifican props directamente |
| Renderizado | Crean nuevos componentes en el árbol | Se usan dentro de componentes existentes |
| Uso típico | Inyección de dependencias, wrapper de funcionalidades | Estado, efectos secundarios, contexto |
Mejores prácticas con HOCs
- No mutar el componente original: Los HOCs deben ser funciones puras que retornan nuevos componentes sin modificar el original.
- Pasar props relevantes: Asegúrate de pasar todas las props al componente envuelto, excepto las que el HOC consuma.
- Nombrar descriptivamente: Usa el prefijo
with> y nombres claros comowithAuthentication>,withDimensions>, etc. - Documentar props añadidas: Indica claramente qué nuevas props añade el HOC.
- Evitar HOCs dentro del método render: Esto causaría que el componente se remonte en cada renderizado.
Errores comunes con HOCs
Ver más
1. No pasar props correctamente:
// ❌ Incorrecto - Pierdes las props originales
const withExample = (WrappedComponent) => {
return function(props) {
return ;
};
};
// ✅ Correcto
const withExample = (WrappedComponent) => {
return function({ newProp, ...rest }) {
return ;
};
};2. HOCs en el método render:
// ❌ Incorrecto - Causa remount del componente
render() {
const EnhancedComponent = withAuth(MyComponent);
return ;
}
// ✅ Correcto - Definir fuera del método render
const EnhancedComponent = withAuth(MyComponent);
render() {
return ;
}useSomething) sería una mejor opción. Los Hooks son más flexibles y no alteran la estructura del JSX.Ejemplo completo: HOC de Theme con display name
const withTheme = (WrappedComponent) => {
function WithThemeComponent(props) {
const theme = useContext(ThemeContext);
return (
);
}
// Importante para debugging en React DevTools
WithThemeComponent.displayName = `WithTheme(${getDisplayName(WrappedComponent)})`;
return WithThemeComponent;
};
// Helper para obtener el nombre del componente
const getDisplayName = (WrappedComponent) => {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
};
// Uso
const Button = ({ theme, ...props }) => (
);
const ThemedButton = withTheme(Button);
// En React DevTools verás: "WithTheme(Button)""Los HOCs son como un escudo protector alrededor de tus componentes, añadiendo funcionalidad sin modificar el componente original. Es un patrón poderoso que, aunque hoy comparta protagonismo con los Hooks, sigue siendo esencial para ciertos escenarios."
forwardRef o considera usar otra alternativa como Hooks.¿Cuál es la principal diferencia entre un Higher-Order Component y un Hook personalizado?
- A) Los HOCs pueden modificar el componente original
- B) Los HOCs envuelven componentes, mientras que los Hooks se usan dentro de componentes existentes
- C) Los Hooks son más rápidos que los HOCs en todos los casos
- D) No hay diferencia, son intercambiables
Conclusión
Los Higher-Order Components son un patrón elegante para reutilizar lógica en React. Aunque los Hooks han simplificado muchos casos de uso, los HOCs siguen siendo valiosos cuando necesitas:
- Envolver componentes con elementos visuales adicionales
- Inyectar dependencias de manera transparente
- Crear abstracciones que funcionan con cualquier componente
- Implementar patrones de render props de manera más declarativa
Recuerda siempre seguir las mejores prácticas: no mutar componentes originales, pasar props correctamente, y usar nombres descriptivos. Con estos conocimientos, estás preparado para utilizar HOCs de manera efectiva en tus proyectos React.