Higher-Order Components (HOCs) explicados

Lectura
30 min~7 min lectura
CONCEPTO CLAVE: Un Higher-Order Component (HOC) es una función que toma un componente como argumento y devuelve un nuevo componente con funcionalidad adicional. No es un componente en sí mismo, sino un patrón de composición que permite reutilizar lógica entre múltiples componentes.

¿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}

; });
💡 Tip: Por convención, los HOCs suelen nombrarse con el prefijo 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 (
      
    );
  };
};
⚠️ Advertencia: Ten cuidado con el nombre de las props que pasas. Si tu HOC pasa una prop llamada 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}

setHasError(false)}> Reintentar
); } 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}

{user.name}

); // Componente envuelto con HOC const UserProfileWithLoading = withLoading(UserProfile); // Uso
📌 Nota importante: Los HOCs no modifican el componente original ni heredan su comportamiento. En cambio, lo envuelven en un nuevo componente. Esto se conoce como "composición", uno de los principios fundamentales de React.

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 , pero cada uno tiene su caso de uso:

CaracterísticaHOCsHooks
ReutilizaciónLógica compartida entre componentesReutilización dentro de un solo componente
PropsPueden añadir/transformar propsNo modifican props directamente
RenderizadoCrean nuevos componentes en el árbolSe usan dentro de componentes existentes
Uso típicoInyección de dependencias, wrapper de funcionalidadesEstado, efectos secundarios, contexto
📌 Regla general: Si puedes lograr lo mismo con un Hook, preférelo. Los Hooks son más directos y no modifican la estructura del componente. Sin embargo, los HOCs siguen siendo útiles para inyección de dependencias y cuando necesitas envolver componentes con elementos visuales adicionales.

Mejores prácticas con HOCs

  1. No mutar el componente original: Los HOCs deben ser funciones puras que retornan nuevos componentes sin modificar el original.
  2. Pasar props relevantes: Asegúrate de pasar todas las props al componente envuelto, excepto las que el HOC consuma.
  3. Nombrar descriptivamente: Usa el prefijo with y nombres claros como withAuthentication, withDimensions, etc.
  4. Documentar props añadidas: Indica claramente qué nuevas props añade el HOC.
  5. 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 ;
}
💡 Tip: Si estás escribiendo un HOC que solo necesita acceder a un hook, considera si un Hook personalizado (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."
⚠️ Precaución con refs: Los HOCs no pasan refs automáticamente. Si necesitas una ref al componente envuelto, usa forwardRef o considera usar otra alternativa como Hooks.
🧠 Quiz

¿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
✅ Respuesta correcta: B. Los HOCs son funciones que toman un componente y devuelven uno nuevo con funcionalidad adicional (envoltura), mientras que los Hooks son funciones que se utilizan dentro de los componentes para acceder a funcionalidades como estado o efectos. Cada uno tiene sus casos de uso apropiados.

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.