Lección 3 de 12 · Lectura y práctica
Render Props: Compartiendo Lógica Visual
CONCEPTO CLAVE: El patrón Render Props es una técnica en React para compartir código entre componentes mediante una prop cuya función es renderizar algo. En lugar de heredar comportamiento, los componentes "reciben" la lógica que.
Del concepto a la práctica
Taller de esta lección
Render Props resulta útil cuando un contenedor calcula datos y el consumidor debe decidir completamente la representación. La prop función convierte la salida visual en una decisión explícita: el contenedor conoce la lógica de seguimiento, pero no impone si se muestra una coordenada, una alerta o una tabla.
Ejemplo resuelto
function LoadState({ load, children }) {
const result = load();
return children(result);
}
const view = <LoadState load={() => ({status:"ready", count:3})}>
{({status, count}) => <p>{status}: {count}</p>}
</LoadState>;La salida visible es ready: 3, y el mismo contenedor podría recibir otra función de renderizado.
Pruébalo
Define una carga que devuelva
{status:"empty", count:0}.Usa
childrenpara mostrar “Sin resultados” cuandocountsea cero.Verifica que la condición vive en el consumidor.
Comprueba tu resultado
Error frecuente: llamar a children sin comprobar que sea una función. Corrígelo validando la API o documentando que es obligatoria.
Ejercicio: crea dos consumidores para el mismo resultado; uno debe mostrar texto y otro un botón deshabilitado. Ambos deben recibir los mismos datos.
load en este fragmento es una función sin efectos que devuelve datos ya disponibles, no una petición HTTP ni una Promise. Para carga remota, inicia la operación en un flujo permitido, gestiona sus estados y entrega el resultado al render; no ejecutes una escritura o petición repetida dentro del render.

Mapa de prácticaUna condición decide qué camino ejecuta el programa. Un bucle repite una operación mientras queden elementos, y termina cuando la lista se agota. Separar ambas ideas ayuda a seguir el flujo y comprobar casos límite.
Ver la infografía en tamaño completo ↗¿Qué es el Patrón Render Props?
El patrón Render Props es una forma de compartir lógica entre componentes en React. Consiste en crear un componente que acepta una prop (generalmente llamada render o directamente como children) que es una función. Esta función retorna JSX y recibe los datos o métodos que el componente envolvente quiere compartir.
Imagina que tienes un componente que maneja la posición del mouse. En lugar de copiar y pegar ese código en cada lugar donde lo necesitas, puedes crear un componente MouseTracker que haga todo el trabajo pesado y "pase" la información a través de una función.
render) le indica al componente qué debe renderizar en cada momento, basándose en el estado interno que ese componente maneja.Sintaxis Básica
Existen dos formas principales de implementar el patrón Render Props:
1. Usando la prop "render"
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
};
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
// Uso:
<MouseTracker render={mouse => (
<h1>Posición del mouse: {mouse.x}, {mouse.y}</h1>
)}/>
2. Usando children como función (más común)
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
};
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{this.props.children(this.state)}
</div>
);
}
}
// Uso:
<MouseTracker>
{mouse => (
<h1>Posición del mouse: {mouse.x}, {mouse.y}</h1>
)}
</MouseTracker>
children es más popular porque mantiene una sintaxis JSX más limpia y natural, sin necesidad de pasar la prop explícitamente.Ejemplo Práctico: Componente de Petición de Datos
Uno de los usos más poderosos de Render Props es crear componentes que manejen lógica asíncrona, como peticiones HTTP, y compartan el resultado con los componentes que lo necesitan.
class DataFetcher extends React.Component {
state = {
data: null,
loading: true,
error: null
};
async componentDidMount() {
try {
const response = await fetch(this.props.url);
const data = await response.json();
this.setState({ data, loading: false });
} catch (error) {
this.setState({ error, loading: false });
}
}
render() {
return this.props.children(this.state);
}
}
// Uso:
<DataFetcher url="https://api.ejemplo.com/usuarios">
{({ data, loading, error }) => {
if (loading) return <div>Cargando...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data.map(usuario => (
<li key={usuario.id}>{usuario.nombre}</li>
))}
</ul>
);
}}
</DataFetcher>
render del componente padre. Esto puede causar re-renderizados innecesarios porque se crearía una nueva función en cada render. Usa métodos bindeados o funcionesflecha definidas fuera de render.Ejemplo: Componente de Lista con Filtros
Veamos un ejemplo más complejo que combine lógica de filtrado y renderizado:
class FilterableList extends React.Component {
state = {
filterText: '',
onlyShowFavorites: false
};
setFilterText = (text) => {
this.setState({ filterText: text });
};
toggleFavorites = () => {
this.setState(prev => ({
onlyShowFavorites: !prev.onlyShowFavorites
}));
};
getFilteredItems() {
const { filterText, onlyShowFavorites } = this.state;
return this.props.items.filter(item => {
const matchesText = item.name
.toLowerCase()
.includes(filterText.toLowerCase());
const matchesFavorite = onlyShowFavorites ? item.isFavorite : true;
return matchesText && matchesFavorite;
});
}
render() {
const props = {
items: this.getFilteredItems(),
filterText: this.state.filterText,
onlyShowFavorites: this.state.onlyShowFavorites,
setFilterText: this.setFilterText,
toggleFavorites: this.toggleFavorites
};
return this.props.children(props);
}
}
// Uso:
<FilterableList items={productos}>
{({ items, filterText, onlyShowFavorites, setFilterText, toggleFavorites }) => (
<div>
<input
type="text"
placeholder="Buscar..."
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
/>
<label>
<input
type="checkbox"
checked={onlyShowFavorites}
onChange={toggleFavorites}
/>
Solo favoritos
</label>
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
{item.isFavorite && '⭐'}
</li>
))}
</ul>
</div>
)}
</FilterableList>
Render Props vs. Higher-Order Components (HOC)
Render Props no es el único patrón para compartir lógica. Los Higher-Order Components (HOC) son otra alternativa popular. Veamos las diferencias:
| Aspecto | Render Props | HOC |
|---|---|---|
| Estructura | Componente que recibe una función como children | Función que retorna un nuevo componente |
| Anidamiento | Se leen naturalmente en el JSX | Puede causar "wrapper hell" con muchos niveles |
| Props | Las props se pasan explícitamente | Puede haber conflictos de props (necesitas recomponer props) |
| Flexibilidad | Muy flexible para renderizado condicional | Mejor para modificar comportamiento global |
Render Props con Hooks
Con la introducción de los Hooks en React 16.8, muchos de los casos de uso de Render Props pueden resolverse de manera más elegante con hooks personalizados. Sin embargo, Render Props sigue siendo útil cuando:
- Necesitas renderizar algo basado en el estado de un componente que no quieres convertir en hook.
- Estás trabajando con librerías de terceros que ya usan este patrón.
- Quieres mantener la composición de componentes para equipos que ya conocen el patrón.
// Equivalente usando hooks:
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:
function Componente() {
const mouse = useMousePosition();
return <h1>X: {mouse.x}, Y: {mouse.y}</h1>;
}
Ejemplo Real: Componente de Portal
Veamos cómo implementar un Render Props para un componente de Portal que renderiza contenido en otro lugar del DOM:
import { createPortal } from 'react-dom';
class Portal extends React.Component {
constructor(props) {
super(props);
this.portalRoot = document.getElementById(props.portalId || 'portal-root');
if (!this.portalRoot) {
this.portalRoot = document.createElement('div');
this.portalRoot.id = props.portalId || 'portal-root';
document.body.appendChild(this.portalRoot);
}
}
render() {
return createPortal(
this.props.children,
this.portalRoot
);
}
}
// Uso:
<Portal portalId="modal-root">
<div className="modal-overlay">
<div className="modal-content">
{this.props.children}
</div>
</div>
</Portal>
Ver más: Render Props para Control de Teclado
A continuación, un ejemplo avanzado de Render Props para manejar atajos de teclado:
class KeyboardHandler extends React.Component {
state = {
pressedKeys: new Set()
};
componentDidMount() {
window.addEventListener('keydown', this.handleKeyDown);
window.addEventListener('keyup', this.handleKeyUp);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener('keyup', this.handleKeyUp);
}
handleKeyDown = (event) => {
this.setState(prev => ({
pressedKeys: new Set([...prev.pressedKeys, event.key])
}));
};
handleKeyUp = (event) => {
this.setState(prev => {
const newKeys = new Set(prev.pressedKeys);
newKeys.delete(event.key);
return { pressedKeys: newKeys };
});
};
isKeyPressed = (key) => {
return this.state.pressedKeys.has(key);
};
render() {
return this.props.children({
pressedKeys: this.state.pressedKeys,
isKeyPressed: this.isKeyPressed
});
}
}
// Uso:
<KeyboardHandler>
{({ isKeyPressed }) => (
<div>
{isKeyPressed('Control') && isKeyPressed('s') && (
<Notification message="Guardando..." />
)}
</div>
)}
</KeyboardHandler>
Mejores Prácticas
- Nombrado descriptivo: Usa nombres claros para las props que pasas al children. Por ejemplo, en lugar de solo
{d => ...}, usa{({ data, loading, error }) => ...}. - Evita funciones en render: No definas funciones flecha dentro del método render del componente padre, ya que esto crea nuevas referencias en cada render.
- Documenta las props: Si creas un componente de Render Props reutilizable, documenta qué datos/métodos recibe la función children.
- Combina con Memo: Si necesitas optimizar el rendimiento, considera usar
React.memoouseMemopara evitar re-renderizados innecesarios.
"Los Render Props son como un contrato: el componente padre promete proporcionar ciertos datos y métodos, mientras que el componente hijo decide exactamente cómo usarlos para renderizarse."
Resumen
El patrón Render Props es una herramienta poderosa para compartir lógica visual entre componentes React. Su principal ventaja es la flexibilidad: mientras que los HOC envuelven componentes, los Render Props te dan control total sobre el renderizado.
Aunque los Hooks han simplificado muchos casos de uso, Render Props sigue siendo relevante especialmente cuando:
- Trabajas con librerías existentes que usan este patrón
- Necesitas compartir lógica de renderizado compleja
- Quieres mantener una jerarquía de componentes más plana
¿Cuál es la principal ventaja del patrón Render Props sobre los Higher-Order Components (HOC)?
- A) Es más rápido en ejecución
- B) Permite mayor flexibilidad en el renderizado y evita el "wrapper hell"
- C) No requiere usar clases
- D) Funciona solo con hooks
Sigue con React Intermedio: Patrones y Optimización
Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.
Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.