Cuando trabajamos con React Context, es tentador consumirlo en cada componente que necesite datos. Sin embargo, existe una forma mucho más limpia de estructurar una aplicación: combinar React Context con una buena composición de componentes. Esto permite reducir llamados innecesarios al contexto y mantener una arquitectura clara, donde cada componente recibe solo lo que necesita sin intermediarios.
¿Cómo mover los llamados de contexto a un solo componente padre?
La primera mejora consiste en centralizar los llamados a React Context en un único componente, en este caso AppUI, en lugar de que cada componente hijo consuma el contexto por su cuenta [01:05].
- Se cortan las propiedades
totalTodos y completedTodos del TodoCounter y se pasan como props desde AppUI.
- Se elimina el
import del contexto y el llamado a useContext dentro de TodoCounter.
- Se repite el mismo proceso con
TodoSearch, moviendo searchValue y setSearchValue como props [02:15].
El resultado es que los componentes TodoCounter y TodoSearch ya no dependen directamente de React Context. Reciben su información como propiedades, lo cual los hace más reutilizables y fáciles de testear.
¿Qué problema aparece al introducir un componente intermedio como TodoHeader?
El escenario se complica cuando se introduce un componente TodoHeader que envuelve tanto al contador como al buscador dentro de un elemento <header> [03:20]. Al hacer esto sin composición adecuada, surge el problema conocido como prop drilling: AppUI debe enviar todas las propiedades a TodoHeader, y este a su vez debe reenviarlas a TodoCounter y TodoSearch.
TodoHeader termina recibiendo propiedades que no utiliza directamente.
- Solo actúa como intermediario, pasando
totalTodos, completedTodos, searchValue y setSearchValue a sus hijos.
- El código se vuelve más difícil de mantener a medida que crece la jerarquía.
Este patrón es exactamente lo que queremos evitar.
¿Cómo aplicar composición con children para eliminar el prop drilling?
La solución es usar la propiedad especial children de React [06:08]. En lugar de que TodoHeader defina internamente qué componentes renderiza, se le permite recibir sus hijos desde el componente que lo invoca.
¿Qué cambia dentro de TodoHeader?
- Se eliminan todas las props específicas como
totalTodos o searchValue.
- Se recibe únicamente
children y se renderiza dentro del elemento <header>.
- Ya no necesita importar
TodoCounter ni TodoSearch.
¿Cómo queda el componente AppUI?
En AppUI, se llama a TodoHeader como un componente con etiqueta de apertura y cierre. Dentro se colocan TodoCounter y TodoSearch directamente, pasándoles sus props correspondientes [06:45].
jsx
<TodoHeader>
<TodoCounter
totalTodos={totalTodos}
completedTodos={completedTodos}
/>
<TodoSearch
searchValue={searchValue}
setSearchValue={setSearchValue}
/>
</TodoHeader>
Con este patrón, no importa cuántos niveles de profundidad existan entre AppUI y los componentes que necesitan los datos. Pueden ser nietos, bisnietos o incluso más profundos en la jerarquía. La comunicación es directa porque AppUI define qué componentes se renderizan y qué datos reciben.
¿Por qué esta composición es más saludable?
- Se evita que componentes intermedios manejen props que no les corresponden.
- Se reduce la dependencia de
useContext a un solo punto de la aplicación.
- Los componentes hijos se mantienen puros y desacoplados del mecanismo de estado global.
- La estructura queda preparada para, en aplicaciones más pequeñas, incluso prescindir completamente de React Context [07:55].
El concepto de children en React es fundamental para lograr este tipo de arquitectura flexible. Permite que un componente contenedor no necesite conocer de antemano qué va a renderizar, delegando esa responsabilidad al componente padre que lo utiliza.
Si tu aplicación no tiene una jerarquía de componentes extremadamente profunda, esta composición puede ser suficiente para manejar el estado sin necesidad de un contexto global. ¿Has probado a refactorizar tus componentes usando children en lugar de pasar props por múltiples niveles? Comparte tu experiencia.