Skip to Content

Render Props Pattern

¿Qué son los Render Props?

Render Props es un patrón donde un componente recibe una función como prop que retorna elementos JSX. En lugar de renderizar su propia UI, el componente ejecuta esta función pasándole datos y estado.

Cómo funciona: El componente padre maneja toda la lógica (estado, efectos, validaciones) pero no decide cómo se ve. En su lugar, llama a una función que recibe como prop, pasándole todos los datos que calculó. Esa función es quien decide qué renderizar.

// El componente DataProvider: // 1. Maneja el estado (data, loading) // 2. NO decide cómo renderizar // 3. Ejecuta la función children pasándole el estado <DataProvider> {({ data, loading }) => ( <div> {loading ? <Spinner /> : <DataList data={data} />} </div> )} </DataProvider>

Concepto clave: Separas completamente la lógica (qué datos tienes) de la presentación (cómo se ven esos datos).

El Caso Principal: Componentes “Headless”

¿Qué es un Componente Headless?

Un componente que:

  • Tiene lógica compleja (estado, efectos, validaciones)
  • NO renderiza UI por sí mismo
  • Delega el renderizado al consumidor via render props

Ejemplo Simple: Toggle

El problema: Necesitas lógica de “encender/apagar” en múltiples lugares (switch, modal, accordion), pero cada uno se ve completamente diferente.

La solución headless: Creas un componente que solo maneja el estado booleano y la función para cambiarlo, pero no renderiza nada. Cada consumidor decide cómo usar esa lógica.

// Componente headless - Solo maneja la lógica de toggle function Toggle({ children }) { const [isOn, setIsOn] = useState(false); const toggle = () => setIsOn(!isOn); // Clave: NO renderiza nada, solo ejecuta children con la lógica return children({ isOn, toggle }); }

Uso 1: Como Switch - La misma lógica, pero renderizada como botón:

<Toggle> {({ isOn, toggle }) => ( <button onClick={toggle}> {isOn ? '🔛' : '⬜'} </button> )} </Toggle>

Uso 2: Como Modal - La misma lógica, pero renderizada como modal:

<Toggle> {({ isOn, toggle }) => ( <div> <button onClick={toggle}>Open Modal</button> {isOn && <Modal onClose={toggle} />} </div> )} </Toggle>

Ventaja clave: Escribes la lógica de toggle una sola vez, pero puedes usarla para switch, modal, accordion, dropdown, etc. Cada uno se ve completamente diferente pero comparte la misma lógica.

Se Combina Bien con Compound Components

Por qué se complementan: Render Props maneja la lógica y estado, mientras que Compound Components proporcionan una API declarativa para estructurar la UI. Es como tener lo mejor de ambos mundos.

Cómo funciona: El componente padre (Dropdown) maneja todo el estado del dropdown (abierto/cerrado, selección, etc.) via render props. Los compound components (Dropdown.Trigger, Dropdown.Content) proporcionan piezas pre-construidas que ya saben cómo comportarse.

// Render Props + Compound Components <Dropdown> {({ isOpen, toggle }) => ( <> {/* Compound component que ya sabe cómo ser un trigger */} <Dropdown.Trigger onClick={toggle}> Options {isOpen ? '▲' : '▼'} </Dropdown.Trigger> {/* Compound component que ya sabe cómo ser contenido */} <Dropdown.Content isOpen={isOpen}> <Dropdown.Item>Edit</Dropdown.Item> <Dropdown.Item>Delete</Dropdown.Item> </Dropdown.Content> </> )} </Dropdown>

Beneficio de la combinación: Obtienes lo mejor de ambos patrones:

  • Flexibilidad total del render prop para manejar el estado y la lógica
  • API declarativa y fácil de los compound components para estructurar la UI
  • Reutilización inteligente: Puedes reorganizar los elementos como quieras, pero cada pieza ya sabe cómo comportarse correctamente

En el 90% de Casos: NO Uses Render Props

En Creative Minds trabajamos con Next.js 15, Server Components y Zustand. Estas herramientas modernas hacen que Render Props sea innecesario en la mayoría de casos.

Para Datos del Servidor: Usa Server Components

El problema: Cuando usas render props para datos del servidor, estás agregando una capa de abstracción innecesaria. Los Server Components de Next.js 15 ya pueden obtener datos directamente y renderizar la UI en el servidor de forma más eficiente.

Por qué la composición directa es superior:

  • Más simple: Menos código, menos anidación
  • Más rápido: Se renderiza completamente en el servidor
  • Mejor SEO: El HTML se genera en el servidor
  • Menos JavaScript: No necesitas la función render prop en el cliente
// ❌ NO - Render props innecesario para datos del servidor // Estás agregando complejidad sin beneficio <Dashboard.Root> {({ user, stats }) => ( <div> <h1>Welcome, {user.name}!</h1> <p>Sales: ${stats.total}</p> </div> )} </Dashboard.Root> // ✅ SÍ - Composición directa en Server Component // Más simple, más rápido, menos código export async function DashboardPage() { const user = await dashboardService.getUserData('user-id'); const stats = await dashboardService.getStats('user-id'); return ( <div> <h1>Welcome, {user.name}!</h1> <p>Sales: ${stats.total}</p> </div> ); }

Para Estado del Cliente: Usa Zustand

El problema: Zustand fue creado específicamente para eliminar el “wrapper hell” - la anidación excesiva de providers que era común con React Context. Si usas render props con Zustand, estás reintroduciendo exactamente el problema que Zustand fue diseñado para resolver.

Por qué Zustand directo es superior:

  • Sin wrappers: Cualquier componente accede al estado directamente
  • Mejor performance: No hay re-renders innecesarios por providers
  • Más simple: Menos código, menos anidación
  • Más flexible: Puedes usar el estado fuera de componentes React
// ❌ NO - Contradice completamente el propósito de Zustand // Estás reintroduciendo el wrapper hell que Zustand elimina <Cart.Context> {({ items, addItem }) => <CartUI items={items} onAdd={addItem} />} </Cart.Context> // ✅ SÍ - Zustand directo como fue diseñado // Cualquier componente accede al estado sin wrappers 'use client'; import { useCartStore } from './store'; export function CartFeature() { const { items, addItem } = useCartStore(); return <CartUI items={items} onAdd={addItem} />; }

Para Lógica Simple: Usa Custom Hooks

El problema: Para lógica simple que solo maneja estado y efectos básicos, render props agrega anidación y complejidad visual innecesaria. Los custom hooks son la herramienta perfecta para este tipo de lógica reutilizable.

Por qué custom hooks son superiores para lógica simple:

  • Código más lineal: Fácil de leer de arriba hacia abajo
  • Mejor composición: Puedes combinar múltiples hooks fácilmente
  • Menos anidación: No necesitas la función render prop
  • Más familiar: Sigue el patrón estándar de React hooks
// ❌ NO - Render props innecesario para lógica simple // La anidación hace el código más difícil de leer <DataFetcher url="/api/users"> {({ data, loading }) => <UserList users={data} loading={loading} />} </DataFetcher> // ✅ SÍ - Custom hook más limpio y directo // Código lineal, fácil de leer y componer function useUsers() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch('/api/users') .then(res => res.json()) .then(setData) .finally(() => setLoading(false)); }, []); return { data, loading }; } export function UsersPage() { const { data, loading } = useUsers(); return <UserList users={data} loading={loading} />; }

Cuándo SÍ Usar Render Props

La regla fundamental: Render Props es una herramienta muy específica. Solo úsala cuando tengas lógica compleja que necesitas reutilizar en múltiples lugares, pero donde cada uso requiere una presentación completamente diferente (no solo estilos diferentes, sino estructura de UI diferente).

Úsalo cuando:

  • Tienes lógica muy compleja (múltiples estados, efectos, validaciones) que necesitas reutilizar en múltiples lugares
  • Cada uso requiere UI completamente diferente - no solo estilos diferentes, sino estructura de componentes diferente
  • Custom hooks no son suficientes porque necesitas prop getters, encapsulación más fuerte, o manejo avanzado de eventos y accesibilidad

NO lo uses para:

  • Pasar datos simples entre módulos y features - usa props normales
  • Estado global - usa Zustand directamente sin wrappers
  • Datos del servidor - usa Server Components con composición directa
  • Lógica simple - usa custom hooks que son más directos

Regla de Oro:

Si dudas, NO uses Render Props. Empieza siempre con la alternativa más simple (props normales, custom hooks, Zustand directo, Server Components). Solo considera Render Props cuando hayas probado las otras opciones y no sean suficientes.


Checklist: ¿Debo Usar Render Props?

Antes de implementar Render Props, pregúntate:

  • ¿La lógica es realmente compleja? (múltiples estados, efectos, validaciones)
  • ¿Se usará en 3+ lugares con UI completamente diferente?
  • ¿Un custom hook no es suficiente?
  • ¿No puedo usar Server Components + composición directa?
  • ¿No puedo usar Zustand directamente?

Si marcaste todas las casillas, entonces considera Render Props. Si no, usa las alternativas más simples.


Last updated on