¿Te has preguntado por qué algunos componentes de React son fáciles de mantener mientras que otros se convierten en un dolor de cabeza? Los patrones de diseño han sido la solución a este problema.
Imagina que estás construyendo una app y necesitas compartir lógica entre varios componentes. ¿Usas un HOC? ¿Render Props? ¿O mejor los modernos Hooks?
Hoy vamos a desmenuzar tres que, honestamente, deberías tener en tu arsenal: Higher-Order Components (HOC), Render Props y Hooks.
- ¿Por qué son importantes los patrones de diseño en React?
- Higher-Order Components (HOC): El patrón clásico
- Render Props: Compartiendo Código con Funciones
- React Hooks: La solución moderna
- Ejemplo práctico: Implementando los tres patrones
- Tabla comparativa: mostrando las características, ventajas y desventajas de cada patrón
- Errores Comunes que Debes Evitar
- Mejores prácticas y recomendaciones
- Recursos adicionales para profundizar
- Conclusión: Eligiendo el patrón correcto
¿Por qué son importantes los patrones de diseño en React?
Antes de sumergirnos en cada patrón, es importante entender por qué existen.

React se basa en la composición de componentes, y a medida que nuestras aplicaciones crecen, nos encontramos con la necesidad de compartir lógica entre componentes sin repetir código. Los patrones de diseño nos proporcionan soluciones probadas para estos desafíos comunes.
Higher-Order Components (HOC): El patrón clásico
Un Higher-Order Component es una función que toma un componente y retorna un nuevo componente con funcionalidad adicional. Piensa en los HOC como decoradores que envuelven tus componentes para añadirles superpoderes.
¿Cómo funcionan los HOC?
Los HOC son una técnica avanzada en React para reutilizar la lógica de componentes. No forman parte de la API de React en sí, sino que emergen de su naturaleza composicional. Mientras que un componente transforma props en UI, un HOC transforma un componente en otro componente.
Aquí tienes un ejemplo básico de un HOC:
// HOC que añade funcionalidad de autenticación
function withAuthentication(Component) {
return function AuthenticatedComponent(props) {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
// Lógica de autenticación
checkAuth().then(setIsAuthenticated);
}, []);
if (!isAuthenticated) {
return <div>Por favor, inicia sesión</div>;
}
return <Component {...props} isAuthenticated={isAuthenticated} />;
};
}
// Uso del HOC
const ProtectedDashboard = withAuthentication(Dashboard);
Cuando usar HOC
- Cuando necesitas aplicar la misma lógica a múltiples componentes
- Para integrar con bibliotecas de terceros que usan este patrón
- Cuando trabajas en código legacy que ya usa HOC
Ventajas de los HOC
- Reutilización de lógica: Puedes usar la misma lógica a varios componentes sin duplicar código
- Separación de concerns: Mantiene la lógica de negocio separada de la presentación
- Composición: Puedes componer múltiples HOC para añadir diferentes funcionalidades
Desventajas de los HOC
- Wrapper Hell: Anidar múltiples HOC puede resultar en una jerarquía de componentes difícil de debuggear
- Colisión de props: Diferentes HOC pueden sobrescribir props con el mismo nombre
- Complejidad: Pueden hacer que el código sea más difícil de entender para desarrolladores menos experimentados

Render Props: Compartiendo Código con Funciones
El patrón Render Props es una técnica para compartir código entre componentes usando una prop cuyo valor es una función. Un componente con una render prop toma una función que retorna un elemento React y la llama en lugar de implementar su propia lógica de renderizado.
Implementando Render Props
Es especialmente útil cuando quieres compartir comportamiento, pero dar control total sobre cómo se renderiza la UI. Veamos un ejemplo:
// Componente con Render Prop
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY
});
};
return (
<div onMouseMove={handleMouseMove}>
{render(position)}
</div>
);
}
// Uso del componente
function App() {
return (
<MouseTracker
render={({ x, y }) => (
<h1>La posición del mouse es: {x}, {y}</h1>
)}
/>
);
}
Cuando usar Render Props
- Cuando necesitas máxima flexibilidad en cómo se renderiza la UI
- Para bibliotecas que exponen funcionalidad a otros desarrolladores
- Cuando el consumer necesita control total sobre la renderización
Ventajas de Render Props
- Flexibilidad: El componente que consume la render prop tiene control total sobre cómo se renderiza
- Claridad: Es explícito sobre qué datos se están compartiendo
- No hay colisión de props: Los datos se pasan directamente a través de la función
Desventajas de Render Props
- Callback Hell: Anidar múltiples render props puede crear un código difícil de leer
- Verbosidad: Requiere más código boilerplate que otros patrones
- Performance: Crear funciones inline en cada render puede afectar el rendimiento

React Hooks: La solución moderna
Los Hooks te permiten usar estado y otras características de React sin escribir clases. Han cambiado la forma en que escribimos componentes React y, en muchos casos, han reemplazado tanto a los HOC como a las Render Props.
Los Hooks más comunes
React proporciona varios Hooks integrados, pero los más utilizados son:
- useState: Para manejar estado local en componentes funcionales
- useEffect: Para manejar efectos secundarios (similar a los métodos de ciclo de vida)
- useContext: Para acceder al contexto de React
- useCallback y useMemo: Para optimización de rendimiento
Creando Custom Hooks
Una de las características más poderosas de los Hooks es la capacidad de crear tus propios Hooks personalizados. Esto te permite extraer lógica de componentes en funciones reutilizables:
// Custom Hook para tracking del mouse
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY
});
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return position;
}
// Uso del Custom Hook
function MouseComponent() {
const { x, y } = useMousePosition();
return (
<div>
<h1>La posición del mouse es: {x}, {y}</h1>
</div>
);
}
Cuando usar los Hooks
- Para nuevos proyectos o componentes (es el enfoque recomendado actualmente)
- Cuando quieres código más simple y mantenible
- Para compartir lógica sin afectar la jerarquía de componentes
- Cuando necesitas manejar estado y efectos en componentes funcionales
Ventajas de los Hooks
- Simplicidad: El código es más legible y fácil de entender
- Reutilización: Los Custom Hooks permiten compartir lógica sin cambiar la jerarquía de componentes
- No más clases: Puedes usar características de React sin escribir componentes de clase
- Mejor organización: Puedes agrupar código relacionado en lugar de dividirlo por métodos de ciclo de vida
- Composición: Es fácil componer múltiples Hooks sin crear wrapper hell
Reglas de los Hooks
Para usar Hooks correctamente, debes seguir dos reglas fundamentales:
- Solo llama Hooks en el nivel superior: No llames Hooks dentro de loops, condiciones o funciones anidadas
- Solo llama Hooks desde funciones de React: Llámalos desde componentes funcionales o desde Custom Hooks
Veamos una comparación visual de un componente de clase vs un componente funcional con Hooks
graph LR
subgraph "Componente de Clase (Antes de Hooks)"
A["class MouseTracker extends React.Component"] --> B["constructor(props)"]
B --> C["this.state = {x: 0, y: 0}"]
C --> D["componentDidMount()"]
D --> E["window.addEventListener('mousemove', this.handleMouseMove)"]
E --> F["handleMouseMove(event)"]
F --> G["this.setState({x: event.clientX, y: event.clientY})"]
G --> H["render()"]
H --> I["return <div>{this.state.x}, {this.state.y}</div>"]
I --> J["componentWillUnmount()"]
J --> K["window.removeEventListener('mousemove', this.handleMouseMove)"]
end
style A fill:#ff6b6b,stroke:#333,stroke-width:2px,color:#fff
style C fill:#ffd93d,stroke:#333,stroke-width:2px
style G fill:#ffd93d,stroke:#333,stroke-width:2px,color:#000
style I fill:#6bcf7f,stroke:#333,stroke-width:2px,color:#000
graph LR
subgraph "Componente Funcional con Hooks (Moderno)"
L["function MouseTracker()"] --> M["const [position, setPosition] = useState({x: 0, y: 0})"]
M --> N["useEffect(() => {...})"]
N --> O["window.addEventListener('mousemove', handleMouseMove)"]
O --> P["handleMouseMove actualiza position"]
P --> Q["return () => window.removeEventListener(...)"]
Q --> R["return <div>{position.x}, {position.y}</div>"]
end
style L fill:#61dafb,stroke:#333,stroke-width:2px,color:#000
style M fill:#98fb98,stroke:#333,stroke-width:2px,color:#000
style N fill:#98fb98,stroke:#333,stroke-width:2px,color:#000
style R fill:#6bcf7f,stroke:#333,stroke-width:2px,color:#000
Ejemplo práctico: Implementando los tres patrones
Para ilustrar mejor las diferencias, implementemos la misma funcionalidad (un formulario de autenticación) usando los tres patrones:
Versión con HOC
function withFormValidation(Component) {
return function ValidatedComponent(props) {
const [errors, setErrors] = useState({});
const validate = (values) => {
const newErrors = {};
if (!values.email) newErrors.email = 'Email requerido';
if (!values.password) newErrors.password = 'Password requerido';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
return <Component {...props} validate={validate} errors={errors} />;
};
}
const LoginForm = withFormValidation(({ validate, errors }) => {
// Implementación del formulario
});
Versión con Render Props
function FormValidation({ children }) {
const [errors, setErrors] = useState({});
const validate = (values) => {
const newErrors = {};
if (!values.email) newErrors.email = 'Email requerido';
if (!values.password) newErrors.password = 'Password requerido';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
return children({ validate, errors });
}
function LoginForm() {
return (
<FormValidation>
{({ validate, errors }) => (
// Implementación del formulario
)}
</FormValidation>
);
}
Versión con Hooks
function useFormValidation() {
const [errors, setErrors] = useState({});
const validate = (values) => {
const newErrors = {};
if (!values.email) newErrors.email = 'Email requerido';
if (!values.password) newErrors.password = 'Password requerido';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
return { validate, errors };
}
function LoginForm() {
const { validate, errors } = useFormValidation();
// Implementación del formulario
}
Tabla comparativa: mostrando las características, ventajas y desventajas de cada patrón
| Característica | HOC (Higher-Order Components) | Render Props | Hooks |
|---|---|---|---|
| Sintaxis | Función que toma un componente y devuelve un nuevo componente | Componente que usa una función como prop para compartir lógica | Funciones especiales que se llaman dentro de componentes funcionales |
| Reutilización de lógica | Alta – Envuelve componentes con lógica compartida | Alta – Proporciona lógica a través de una función render | Muy alta – Custom Hooks pueden extraer y compartir lógica fácilmente |
| Legibilidad del código | Puede disminuir con múltiples HOC anidados (wrapper hell) | Puede volverse complejo con anidación profunda | Excelente – Código más limpio y lineal |
| Debugging | Difícil – Múltiples capas de componentes en DevTools | Medio – Puede ser confuso con callbacks anidados | Fácil – Estructura más plana y clara |
| Colisión de props | Posible – HOC pueden sobrescribir props accidentalmente | Mínimo – Control explícito sobre qué se pasa | No existe – Cada Hook maneja su propio estado |
| Flexibilidad de renderizado | Baja – La UI está predefinida por el HOC | Muy alta – Control total sobre cómo se renderiza | Alta – Separa lógica de presentación completamente |
| Composición | Requiere anidar múltiples funciones HOC | Puede crear «pirámide de la perdición» con múltiples render props | Natural – Múltiples Hooks se usan directamente sin anidación |
| Tipado (TypeScript) | Complejo – Requiere tipos genéricos avanzados | Medio – Necesita tipar correctamente la función render | Simple – Los tipos se infieren fácilmente |
| Rendimiento | Puede crear componentes adicionales innecesarios | Puede causar re-renders si no se optimiza correctamente | Optimizable con useMemo y useCallback |
| Curva de aprendizaje | Alta – Concepto de programación funcional avanzado | Media – Requiere entender callbacks y closures | Baja a media – Sintaxis más intuitiva |
| Soporte oficial | Patrón válido pero no recomendado para código nuevo | Patrón válido pero menos usado desde Hooks | Enfoque recomendado oficialmente por React |
| Casos de uso ideales | Integración con librerías legacy, código existente | Librerías que necesitan máxima flexibilidad de renderizado | Todo desarrollo nuevo, manejo de estado y efectos |
| Ventajas principales | ✅ Bueno para añadir comportamiento a componentes existentes ✅ Patrón bien establecido en ecosistema React ✅ Útil para integración con Redux, Router, etc. | ✅ Máxima flexibilidad en la UI ✅ Evita colisiones de props ✅ Útil para librerías públicas | ✅ Código más limpio y conciso ✅ Reutilización sin modificar jerarquía ✅ Mejor experiencia de desarrollo ✅ Testing más simple |
| Desventajas principales | ❌ Wrapper hell con múltiples HOC ❌ Dificulta debugging ❌ Posibles conflictos de nombres de props ❌ Complejidad adicional | ❌ Puede crear código verboso ❌ Pirámide de callbacks anidados ❌ Puede afectar rendimiento sin optimización ❌ Sintaxis menos intuitiva | ❌ Reglas estrictas que seguir ❌ No disponible en componentes de clase ❌ Requiere React 16.8+ ❌ Curva de aprendizaje inicial para efectos |
| Ejemplo de uso | withAuth(Component) | <Mouse render={(pos) => ...}/>; | const [state, setState] = useState() |
| Estado actual | 🟡 Mantenimiento – Usar solo si es necesario | 🟡 Nicho – Casos específicos de uso | 🟢 Recomendado – Estándar actual de React |
Errores Comunes que Debes Evitar
1: Usar HOC cuando Hooks es suficiente
Muchos desarrolladores usan HOC por costumbre, cuando un simple custom Hook haría el trabajo mejor.
❌ Mal: Crear un HOC complejo para compartir un estado simple
// HOC complejo para algo simple
const withCounter = (Component) => {
return class extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<Component
count={this.state.count}
increment={this.increment}
{...this.props}
/>
);
}
};
};
// Usar el HOC
const MyComponent = ({ count, increment }) => (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Incrementar</button>
</div>
);
export default withCounter(MyComponent);✅ Bien: Crear un custom Hook de 10 líneas
// Custom Hook de 10 líneas
const useCounter = (initialValue = 0) => {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return { count, increment };
};
// Usar el Hook directamente
const MyComponent = () => {
const { count, increment } = useCounter(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Incrementar</button>
</div>
);
};2: Callback Hell con Render Props
Anidar múltiples render props puede crear código ilegible.
// Múltiples render props anidados = código ilegible
const UserProfile = () => (
<AuthProvider>
{(auth) => (
<ThemeProvider>
{(theme) => (
<LanguageProvider>
{(language) => (
<DataProvider>
{(data) => (
<div style={{ color: theme.textColor }}>
{auth.isAuthenticated ? (
<h1>{language.greeting}, {data.userName}</h1>
) : (
<p>{language.pleaseLogin}</p>
)}
</div>
)}
</DataProvider>
)}
</LanguageProvider>
)}
</ThemeProvider>
)}
</AuthProvider>
);
export default UserProfile;Solución: Si necesitas anidar más de 2 render props, considera refactorizar a Hooks.
// Custom Hooks para cada contexto const useAuth = () => useContext(AuthContext); const useTheme = () => useContext(ThemeContext); const useLanguage = () => useContext(LanguageContext); const useData = () => useContext(DataContext); // Componente limpio y plano const UserProfile = () => { const { isAuthenticated } = useAuth(); const { textColor } = useTheme(); const { greeting, pleaseLogin } = useLanguage(); const { userName } = useData(); return ( <div style={{ color: textColor }}> {isAuthenticated ? ( <h1>{greeting}, {userName}</h1> ) : ( <p>{pleaseLogin}</p> )} </div> ); }; export default UserProfile;
3: No memorizar en Hooks
Olvidar usar useCallback y useMemo puede causar re-renders innecesarios.
// Cada render crea nuevas funciones y objetos
const SearchComponent = ({ onSearch }) => {
const [query, setQuery] = useState('');
const [filters, setFilters] = useState({ category: '', minPrice: 0 });
// ❌ Nueva función en cada render
const handleSearch = () => {
onSearch(query, filters);
};
// ❌ Nuevo objeto en cada render
const searchConfig = {
debounce: 300,
caseSensitive: false
};
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<SearchButton
onClick={handleSearch}
config={searchConfig}
/>
</div>
);
};
// Este componente se re-renderiza innecesariamente
const SearchButton = React.memo(({ onClick, config }) => {
console.log('SearchButton renderizado');
return <button onClick={onClick}>Buscar</button>;
});Regla: Si pasas una función u objeto como prop, probablemente necesitas memorizarlo.
// Funciones y objetos memorizados
const SearchComponent = ({ onSearch }) => {
const [query, setQuery] = useState('');
const [filters, setFilters] = useState({ category: '', minPrice: 0 });
// ✅ Función memoizada con useCallback
const handleSearch = useCallback(() => {
onSearch(query, filters);
}, [query, filters, onSearch]);
// ✅ Objeto memoizado con useMemo
const searchConfig = useMemo(() => ({
debounce: 300,
caseSensitive: false
}), []); // Dependencias vacías porque no cambia
// ✅ Callback memoizado para el input
const handleInputChange = useCallback((e) => {
setQuery(e.target.value);
}, []);
return (
<div>
<input
value={query}
onChange={handleInputChange}
/>
<SearchButton
onClick={handleSearch}
config={searchConfig}
/>
</div>
);
};
// Ahora solo se re-renderiza cuando realmente cambian las props
const SearchButton = React.memo(({ onClick, config }) => {
console.log('SearchButton renderizado');
return <button onClick={onClick}>Buscar</button>;
});Regla práctica de memoización:
useCallback: Para funciones que pasas como propsuseMemo: Para objetos/arrays que pasas como props o cálculos costososReact.memo: Para componentes que reciben las props memorizadas
⚠️ Advertencia: No memoices todo. Solo hazlo cuando:
- El componente hijo usa React.memo
- Hay cálculos costosos
- Las props causan re-renders innecesarios comprobados con profiler
Mejores prácticas y recomendaciones
Basándonos en la experiencia de la comunidad React y las recomendaciones oficiales, aquí están algunas mejores prácticas:
Para proyectos nuevos
- Prioriza los Hooks: Son la solución moderna y recomendada por el equipo de React
- Crea Custom Hooks: Extrae lógica reutilizable en Custom Hooks bien nombrados
- Mantén los Hooks simples: Un Hook debe hacer una cosa y hacerla bien
Para proyectos existentes
- No refactorices todo de inmediato: Los HOC y Render Props siguen siendo válidos
- Migra gradualmente: Convierte componentes a Hooks cuando los modifiques
- Mantén la consistencia: Usa el mismo patrón en componentes relacionados
Consideraciones de rendimiento
- Usa
useMemoyuseCallbackpara optimizar Hooks cuando sea necesario - Evita crear funciones inline en render props cuando el rendimiento sea crítico
- Considera el impacto en el árbol de componentes al usar HOC
Recursos adicionales para profundizar
Si quieres seguir aprendizando sobre estos patrones, aquí tienes algunos recursos valiosos:
- Patterns.dev – Guía completa de patrones en React
- Documentación oficial de React Hooks
- Documentación oficial de HOC
- Documentación oficial de Render Props
Conclusión: Eligiendo el patrón correcto
Ahora que conoces los tres patrones principales de React, es momento de aplicarlos.
Desafío de hoy:
- Abre uno de tus proyectos React actuales
- Identifica un componente que repite lógica
- Refactorízalo usando el patrón más apropiado
📬 ¿Te gustó este contenido? Suscríbete a mi canal de WhatsApp para más contenido de React, JavaScript y desarrollo web cada semana.
👉 Únete a mi Canal de WhatsApp para más recursos
👉 Sigueme en mis redes sociales para más contenido de programación y productividad