
HOC (Higher Order Component) en React: Guía Paso a Paso 2025

Juan Correa
Desarrollador de Software Senior
Un High Order Component (HOC) o Componentes de Orden Superior, nos provee de una manera de reusar comportamiento y lógica de estado entre componentes.
En este artículo, te guiaré a través de lo que es un HOC, por qué es útil y cómo puedes implementarlo en tus proyectos.
Table of Contents
¿Qué es un HOC?
un HOC es una función de Javascript que recibe por lo menos un componente por parámetro y retorna un nuevo componente inyectando por props la lógica que se quiere reusar.
En otras palabras, un HOC es una función que recibe un componente y retorna un nuevo componente enriquecido.
El principal beneficio de los HOCs es la reutilización de lógica.
Imagina que tienes varias funcionalidades comunes que se deben compartir entre diferentes componentes.
En lugar de duplicar esta lógica en cada componente, puedes aislarla en un HOC y simplemente envolver tus componentes con él.
¿Quieres dominar los Patrones de Diseño en React.js?
En mi ebook "Patrones avanzados en React.js", encontrarás más información sobre los patrones explicados en este post y contenido de alta calidad que te ayudará a llevar tus habilidades al siguiente nivel.
Descargar ebook gratisVentajas y desventajas al usar un HOC
Las principales ventajas al usar un HOC son:
- Favorece el mantenimiento al tener lógica separada en una función que tenga solo una razón para cambiar (principio de responsabilidad única).
- Evitamos la duplicidad de código.
- Aprovecha la naturaleza de la composición de React (que puede ser un arma de doble filo como veremos a continuación).
Las principales desventajas al usar un HOC son:
- Es posible tener una colisión de nombres de los props que el HOC inyecta y los que nosotros pasamos directamente al componente (en el ejemplo verás a qué me refiero).
- Podemos tender a incrementar demasiado nuestro árbol de componentes conforme nuestra aplicación crece en complejidad pues estamos usando la composición (esta es la parte de doble filo mencionada antes).
En qué casos aplicar un HOC
Para saber si necesitas crear un HOC considera lo siguiente:
- Has identificado que tienes componentes con código duplicado debido a que manejan el mismo tipo de lógica en comportamiento. Ejemplos:
- Manejo de estado interno.
- Suscripciones a apis o eventos.
- Aprovechar la composición para reusar mismos componentes pero con un comportamiento diferente según uno u otro HOC.
Ejemplo en vídeo de un HOC
Si lo prefieres, he grabado un video en mi canal de YouTube donde explico este mismo tema. ¡Te invito a verlo!
Pero si prefieres leer, sigue con el artículo.
Ejemplo de un HOC en React
Para que el concepto quede mejor explicado, vamos a hacer un ejemplo de un formulario controlado de manera tradicional y después vamos a abstraer la lógica del manejo de estado del formulario.
Con esto vamos a ser capaces de crear tantos formularios como queramos sin preocuparnos por la lógica de cómo manejarlo ya que estará definida y será reusable.
const Form = ({ onSubmit }) => {
const [formValues, setFormValues] = useState({
name: '',
address: '',
age: '',
phone: '',
})
const handleChange = (e) => {
const {
target: { name, value },
} = e
setFormValues({ ...formValues, [name]: value })
}
const handleSubmit = (e) => {
e.preventDefault()
onSubmit(JSON.stringify(formValues))
}
return (
<form onSubmit={handleSubmit}>
<div>
<p>Name</p>
<input type="text" name="name" value={formValues.name} onChange={handleChange} />
</div>
<div>
<p>Address</p>
<input type="text" name="address" value={formValues.address} onChange={handleChange} />
</div>
<div>
<p>Age</p>
<input type="number" name="age" value={formValues.age} onChange={handleChange} />
</div>
<div>
<p>Phone</p>
<input type="tel" name="phone" value={formValues.phone} onChange={handleChange} />
</div>
<div>
<button type="submit">Send</button>
</div>
</form>
)
}
Como puedes ver, es un formulario controlado común, es decir, un formulario cuyos valores de los inputs son controlados por las variables de estado de React que a su vez cambian de acuerdo al evento change de los inputs.
El único prop que recibe es la función onSubmit para que haga lo que el componente padre quiera hacer con los valores del formulario después de hacer submit.
La manera de implementarlo sería la siguiente.
export default function App() {
const showFormValues = (formValues) => alert(formValues)
return (
<div className="App">
<h2>Contact Form</h2>
<Form onSubmit={showFormValues} />
</div>
)
}
Puedes ver el código en este enlace: https://codesandbox.io/s/normal-form-example-kpl0m.
Creando un HOC
Para crear nuestro HOC debemos preguntarnos: ¿Cuál es la lógica que quiero abstraer y para qué?
En este ejemplo, lo que queremos es abstraer la lógica de manejo de estado de un formulario, que sea independientemente de su UI.
Esto para aplicar el principio DRY (Don’t Repeat Yourself).
Es buena idea comenzar con cambios pequeños y progresivos, por lo que primero crearemos el HOC como un envolvedor que de momento aún no agregue lógica.
const WithControlledForm = (Form) => {
const WithForm = (props) => {
return <Form {...props} />
}
return WithForm
}
Existe una convención de nombres de HOC en la que inician con la palabra “with” (“con”) seguido de aquello que exprese lo que hace el HOC.
En este caso withControlledForm porque vamos a abstraer la lógica para manejar un formulario controlado.
Y para usarlo basta con hacer:
// FormWrapped es igual a lo que retorna WithControlledForm, que es un componente
// Pasamos por parámetro el componente Form, que es el que queremos envolver
const FormWrapped = withControlledForm(Form)
export default function App() {
const showFormValues = (formValues) => alert(formValues)
return (
<div className="App">
<h2>Contact Form</h2>
<FormWrapped onSubmit={showFormValues} />
</div>
)
}
Nota que estamos creando la constante FormWrapped cuyo valor es el componente llamado WithForm que retorna nuestro HOC.
También pasamos el prop onSubmit y cualesquiera que queramos pasar en el futuro.
Es importante que nuestro HOC sea una constante que se cree una sola vez, en especial hay que tener cuidado de no crearlos dentro de un componente tipo función, o dentro del método render si tenemos un componente de tipo clase.
De lo contrario, estaríamos creando un nuevo HOC cada vez que se renderice el componente, lo cual no es eficiente.
Incluso si React.js llega a optimizar esto, es un buen hábito crear los HOCs fuera de los componentes.
Ahora vamos a añadir lógica para manejar el formulario anterior, es decir, el estado del formulario y la lógica mínima necesaria para actualizar el estado.
Necesitamos definir el estado inicial del formulario, por lo tanto, pasaremos un segundo parámetro a nuestro HOC para poder definirlo.
const withControlledForm = (Form, initialState = {}) => {
const WithForm = ({ onSubmit, ...rest }) => {
const [formValues, setFormValues] = useState(initialState)
const handleChange = (e) => {
const {
target: { name, value },
} = e
setFormValues({ ...formValues, [name]: value })
}
const handleSubmit = (e) => {
e.preventDefault()
onSubmit(formValues)
}
const finalProps = {
...rest,
formValues,
handleChange,
handleSubmit,
}
return <Form {...finalProps} />
}
return WithForm
}
¡Genial! ya estamos manejando la lógica para controlar el estado de un formulario.
Ahora probemos en nuestro componente Form haciendo las siguientes adecuaciones:
- Ya no será responsable de manejar la lógica del formulario controlado, eso lo hace nuestro HOC por nosotros, por lo que hay que remover todo el código que se encargaba de ello.
- Hacer referencia a los props para controlar el formulario.
Con estos cambios nuestro componente de formulario se ve ahora así:
const Form = ({ handleSubmit, formValues, handleChange }) => {
return (
<form onSubmit={handleSubmit}>
<div>
<p>Name</p>
<input type="text" name="name" value={formValues.name} onChange={handleChange} />
</div>
<div>
<p>Address</p>
<input type="text" name="address" value={formValues.address} onChange={handleChange} />
</div>
<div>
<p>Age</p>
<input type="number" name="age" value={formValues.age} onChange={handleChange} />
</div>
<div>
<p>Phone</p>
<input type="tel" name="phone" value={formValues.phone} onChange={handleChange} />
</div>
<div>
<button type="submit">Send</button>
</div>
</form>
)
}
Con esto hemos logrado nuestro objetivo inicial de abstraer lógica de manejo del formulario.
Ahora vamos a mejorar nuestro HOC aplicando algunas convenciones más.
El hecho de tener un componente envolvedor como parte de un HOC hace que nuestro árbol de componentes también se vea reflejado al momento de depurar con la herramienta React devtools.
Por lo que es buena idea asignarle un nombre que indique que el componente es el resultado de un HOC. El siguiente ejemplo es tomado de la documentación oficial de React.
const withControlledForm = (Form, initialState = {}) => {
const WithForm = ({ onSubmit, ...rest }) => {
/* resto del código... */
}
WithForm.displayName = `WithForm(${getDisplayName(Form)})`
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
return WithForm
}
Puedes ver y editar todo el código en este enlace: https://codesandbox.io/s/form-with-hoc-0bvxd?file=/src/App.js
Usando el HOC
Para dar un ejemplo de cómo este HOC nos ayuda a reusar lógica, vamos a crear dos formularios de ejemplo nuevos, un FormA y un FormB que serán iguales en su contenido.
const FormA = ({ handleSubmit, formValues, handleChange }) => (
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
value={formValues.name}
onChange={handleChange}
placeholder="input form A"
/>
</form>
)
El componente FormB es exactamente igual, solo cambiamos el placeholder.
const FormA = ({ handleSubmit, formValues, handleChange }) => (
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
value={formValues.name}
onChange={handleChange}
placeholder="input form B"
/>
</form>
)
Y para usarlos sería como sigue a continuación.
const initialStateFormA = { name: '' }
const FormAWrapped = withControlledForm(FormA, initialStateFormA)
const initialStateFormB = { name: '' }
const FormBWrapped = withControlledForm(FormB, initialStateFormB)
export default function App() {
const showFormValues = (formValues) => alert(formValues)
return (
<div className="App">
<h2>Contact Form</h2>
<FormWrapped onSubmit={showFormValues} />
<FormAWrapped onSubmit={showFormValues} />
<FormBWrapped onSubmit={showFormValues} />
</div>
)
}
En este ejemplo hemos visto cómo crear un formulario básico con inputs y reusar la lógica del estado con un HOC, de este modo podemos crear tantos formularios como queramos sin tener que repetir esa lógica de manera individual.
En una implementación más apegada a la realidad, nos faltaría considerar el resto de elementos que tiene un formulario, tales como un select, textarea, radio buttons, etc, y sus correspondientes manejadores de eventos; también hacer validaciones con sus respectivos mensajes; y si vamos más allá, que los mensajes sean multi idioma.
Pero el propósito de este capítulo es demostrar lo útil que puede ser usar el patrón de HOC para reusar lógica entre componentes.
Conclusión sobre el patrón HOC
Existen muchos otros casos de uso en los que puedes reusar lógica con un HOC, tal como en los ciclos de vida de un componente, manejadores de otros eventos (aparte del change de un input) y subscripciones a apis de terceros.
Te recomiendo que veas este repositorio (https://github.com/klarna/higher-order-components) con ejemplos de HOC. Aunque ha sido archivado, considero que es útil para temas de que conozcas más ejemplos y casos de uso.
Recursos adicionales
Si te ha gustado este análisis sobre el patrón Custom Hook, te invito a explorar los otros patrones avanzados en React JS que he analizado en la guía principal.
Y recuerda, si quieres llevar tus habilidades al siguiente nivel, no te pierdas el curso completo en el Canal de YouTube de Developero.