¿Qué Problema Resuelven estos Patrones?
Imaginemos que tenemos un componente que maneja datos de estado o efectos secundarios, y queremos que diferentes partes de nuestra aplicación utilicen esa lógica de formas completamente distintas. Los patrones de Render Props y Children como Función nos permiten achieved esto sin necesidad de crear múltiples componentes o usar herramientas externas como Context o HOCs complicados.
Estos patrones son especialmente útiles cuando:
- Necesitamos compartir lógica de estado entre componentes
- Queremos mantener el control del renderizado en el componente hijo
- Buscamos una alternativa más flexible a los High Order Components (HOCs)
Children como Función (Children as a Function)
Este patrón consiste en pasar una función como children de un componente. El componente padre entonces invoca esa función, pasándole los datos o métodos que necesita compartir. El componente hijo decide qué hacer con esos datos.
Sintaxis Básica
function Contador({ children }) {
const [contador, setContador] = useState(0);
return children({
contador,
incrementar: () => setContador(c => c + 1),
decrementar: () => setContador(c => c - 1)
});
}
// Uso
function App() {
return (
<Contador>
{({ contador, incrementar, decrementar }) => (
<div>
<h2>Contador: {contador}</h2>
<button onClick={incrementar}>+</button>
<button onClick={decrementar}>-</button>
</div>
)}
</Contador>
);
}Contador no renderiza nada por sí mismo. En su lugar, llama a children como una función, pasándole el estado y las funciones. Esto le da total flexibilidad al consumidor sobre cómo renderizar la interfaz.Render Props
El patrón de Render Props es muy similar, pero en lugar de usar children, usamos una prop específica (generalmente llamada render o component) que recibe una función.
Sintaxis con Render Prop
function Contador({ render }) {
const [contador, setContador] = useState(0);
return render({
contador,
incrementar: () => setContador(c => c + 1),
decrementar: () => setContador(c => c - 1)
});
}
// Uso con prop render
function App() {
return (
<Contador
render={({ contador, incrementar, decrementar }) => (
<div className="tarjeta"
<p>Valor: {contador}</p>
<button onClick={incrementar}>+</button>
<button onClick={decrementar}>-</button>
</div>
)}
/>
);
}children, el patrón Render Props usa una prop nombrada. Ambos logran el mismo resultado, pero la elección depende de la legibilidad y convenciones de tu equipo.Ejemplo Práctico: Componente de Datos
Veamos un ejemplo más completo donde cremos un componente que maneja la solicitud de datos y permite renderizarlos de diferentes formas:
function DataFetcher({ url, children }) {
const [datos, setDatos] = useState(null);
const [cargando, setCargando] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setCargando(true);
fetch(url)
.then(res => {
if (!res.ok) throw new Error('Error en la solicitud');
return res.json();
})
.then(data => {
setDatos(data);
setError(null);
})
.catch(err => {
setError(err.message);
setDatos(null);
})
.finally(() => setCargando(false));
}, [url]);
return children({ datos, cargando, error });
}
// Ahora podemos usarlo de múltiples formas
function App() {
return (
<DataFetcher url="https://api.ejemplo.com/usuarios">
{({ datos, cargando, error }) => {
if (cargando) return <p>Cargando...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{datos.map(usuario => (
<li key={usuario.id}>{usuario.nombre}</li>
))}
</ul>
);
}}
</DataFetcher>
);
}Casos de Uso Comunes
| Patrón | Uso Ideal | Ejemplo |
|---|---|---|
| Children como Función | Composición flexible, APIs declarativas | Componentes de UI con estado interno |
| Render Props | Cuando necesitas múltiples render props | Componentes de animación, datos |
Componente de Animación con Render Props
function Animator({ estado, children }) {
const [visible, setVisible] = useState(false);
useEffect(() => {
if (estado === 'entrada') {
setVisible(true);
} else if (estado === 'salida') {
setVisible(false);
}
}, [estado]);
return children({ visible });
}
// Uso
function BotonAnimado() {
return (
<Animator estado="entrada">
{({ visible }) => (
<div className={`fade ${visible ? 'visible' : 'hidden'}`}>
Contenido animado
</div>
)}
</Animator>
);
}Buenas Prácticas
- Mantén la simplicidad: No uses estos patrones cuando un componente simple sea suficiente.
- Documenta tus APIs: Asegúrate de que otros desarrolladores entiendan qué datos recibe la función de render.
- Evita recrear funciones en cada render: Usa
useCallbackcuando sea necesario para optimizar el rendimiento. - Considera alternativas modernas: En muchos casos, hooks personalizados como
useState,useReducero Context pueden ser más simples.
Comparación con otras alternativas
Los patrones de Render Props y Children como Función fueron ampliamente usados antes de la introducción de los Hooks en React 16.8. Aunque los Hooks han reducido la necesidad de estos patrones para muchos casos, siguen siendo herramientas válidas para ciertos escenarios de composición.
vs High Order Components (HOCs)
// HOC - Wrappea el componente
const withContador = (Componente) => {
return function ComponenteEnvuelto(props) {
const [contador, setContador] = useState(0);
return (
<Componente
contador={contador}
incrementar={() => setContador(c => c + 1)}
{...props}
/>
);
};
};
// Children como Función - Envuelve la lógica
function Contador({ children }) {
const [contador, setContador] = useState(0);
return children({ contador, incrementar: () => setContador(c => c + 1) });
}
// Children como Función es más fácil de leer y depurarfunction ModalManager({ children, renderHeader, renderFooter }) {
const [isOpen, setIsOpen] = useState(false);
const abrir = () => setIsOpen(true);
const cerrar = () => setIsOpen(false);
if (!isOpen) {
return <button onClick={abrir}>Abrir Modal</button>;
}
return (
<div className="modal-overlay">
<div className="modal">
{renderHeader({ cerrar })}
<div className="modal-content">
{children}
</div>
{renderFooter({ cerrar })}
</div>
</div>
);
}
// Uso
function App() {
return (
<ModalManager
renderHeader={({ cerrar }) => (
<div className="header">
<h2>Título</h2>
<button onClick={cerrar}>×</button>
</div>
)}
renderFooter={({ cerrar }) => (
<div className="footer">
<button onClick={cerrar}>Cancelar</button>
<button onClick={cerrar}>Aceptar</button>
</div>
)}
>
<p>Contenido del modal</p>
</ModalManager>
);
}Consideraciones de Rendimiento
Es importante entender que cada vez que el componente padre se re-renderiza, la función de render o children se ejecuta nuevamente. Esto puede causar problemas de rendimiento si la función crea nuevas referencias en cada render.
// ❌ Problema: Nueva función en cada render
function App() {
return (
<Contador>
{({ contador }) => <MostrarContador valor={contador} />}
</Contador>
);
}
// ✅ Solución: Usar React.memo o useMemo
const MostrarContador = React.memo(({ valor }) => {
console.log('Renderizando');
return <span>{valor}</span>;
});📌 Recuerda: En la mayoría de los casos, el impacto en el rendimiento será mínimo. Solo optimiza cuando profiler confirme que hay un problema real.Conclusión
Los patrones de Render Props y Children como Función son herramientas poderosas para compartir lógica y composición en React. Aunque los Hooks han proporcionado alternativas más directas para muchos casos de uso, estos patrones siguen siendo relevantes cuando necesitas:
Compartir lógica de estado entre componentes de manera flexibleMantener el control del renderizado fuera del componente que maneja la lógicaCrear APIs declarativas y fáciles de usar
Dominar estos patrones te dará una comprensión más profunda de cómo React maneja la composición y te preparará para crear componentes más flexibles y reutilizables.
🧠 Quiz¿Cuál es la diferencia principal entre el patrón Render Props y Children como Función?
A) No hay diferencia, son exactamente lo mismoB) Render Props usa una prop nombrada, mientras que Children como Función usa la prop especial childrenC) Children como Función solo funciona con componentes funcionalesD) Render Props es más rápido que Children como Función
✅ Respuesta correcta: B) La diferencia principal es que Render Props típicamente usa una prop específica (como render o component), mientras que Children como Función utiliza la prop especial children que todo componente React tiene.