Pasar datos en profundidad con Context
Normalmente, pasarás información de un componente padre a un componente hijo mediante props. Pero pasar props puede volverse verboso e inconveniente si tienes que pasarlos a través de muchos componentes intermedios, o si muchos componentes de tu aplicación necesitan la misma información.Contextpermite al componente padre poner cierta información disponible para cualquier componente en el árbol debajo de él—sin importar cuán profundo—sin pasarla explícitamente a través de props.
Aprenderás
- Qué es el “prop drilling”
- Cómo reemplazar el paso repetitivo de props con context
- Casos de uso comunes para context
- Alternativas comunes a context
El problema de pasar props
Pasar propses una excelente manera de canalizar datos explícitamente a través de tu árbol de UI hacia los componentes que los usan.
Pero pasar props puede volverse verboso e inconveniente cuando necesitas pasar alguna prop profundamente a través del árbol, o si muchos componentes necesitan la misma prop. El ancestro común más cercano podría estar muy alejado de los componentes que necesitan datos, yelevar el estadotan alto puede llevar a una situación llamada “prop drilling”.
Elevar el estado


Prop drilling


¿No sería genial si hubiera una manera de “teletransportar” datos a los componentes del árbol que los necesitan sin pasar props? ¡Con la función de context de React, la hay!
Context: una alternativa a pasar props
Context permite que un componente padre proporcione datos a todo el árbol debajo de él. Hay muchos usos para context. Aquí hay un ejemplo. Considera este componenteHeadingque acepta unlevelpara su tamaño:
Digamos que quieres que múltiples encabezados dentro de la mismaSectiontengan siempre el mismo tamaño:
Actualmente, pasas la proplevela cada<Heading>por separado:
Sería bueno si pudieras pasar la proplevelal componente<Section>en su lugar y eliminarla del<Heading>. De esta manera podrías asegurar que todos los encabezados en la misma sección tengan el mismo tamaño:
Pero, ¿cómo puede el componente<Heading>saber el nivel de su<Section>más cercano?Eso requeriría alguna forma para que un hijo "pregunte" por datos desde algún lugar superior en el árbol.
No puedes hacerlo solo con props. Aquí es donde entra en juego el contexto. Lo harás en tres pasos:
- Crearun contexto. (Puedes llamarlo
LevelContext, ya que es para el nivel de encabezado). - Usarese contexto desde el componente que necesita los datos. (
HeadingusaráLevelContext). - Proporcionarese contexto desde el componente que especifica los datos. (
SectionproporcionaráLevelContext).
El contexto permite que un padre—¡incluso uno lejano!—proporcione algunos datos a todo el árbol dentro de él.
Usando contexto en hijos cercanos


Usando contexto en hijos lejanos


Paso 1: Crear el contexto
Primero, necesitas crear el contexto. Necesitarásexportarlo desde un archivopara que tus componentes puedan usarlo:
El único argumento paracreateContextes el valorpredeterminado. Aquí,1se refiere al nivel de encabezado más grande, pero podrías pasar cualquier tipo de valor (incluso un objeto). Verás la importancia del valor predeterminado en el siguiente paso.
Paso 2: Usar el contexto
Importa el HookuseContextde React y tu contexto:
Actualmente, el componenteHeading lee levelde las props:
En su lugar, elimina la proplevely lee el valor del contexto que acabas de importar,LevelContext:
useContextes un Hook. Al igual queuseState y useReducer, solo puedes llamar a un Hook directamente dentro de un componente de React (no dentro de bucles o condiciones).useContextle dice a React que el componenteHeadingquiere leer elLevelContext.
Ahora que el componenteHeadingno tiene una proplevel, ya no necesitas pasar la prop level aHeadingen tu JSX así:
Actualiza el JSX para que sea laSectionla que lo reciba en su lugar:
Como recordatorio, este es el marcado que estabas intentando hacer funcionar:
Observa que este ejemplo aún no funciona del todo. Todos los encabezados tienen el mismo tamaño porqueaunque estásusandoel contexto, aún no lo hasproporcionado.¡React no sabe de dónde obtenerlo!
Si no proporcionas el contexto, React usará el valor predeterminado que especificaste en el paso anterior. En este ejemplo, especificaste1como argumento paracreateContext, por lo queuseContext(LevelContext)devuelve1, estableciendo todos esos encabezados como<h1>. Resolvamos este problema haciendo que cadaSectionproporcione su propio contexto.
Paso 3: Proporcionar el contexto
El componenteSectionactualmente renderiza sus hijos:
Envuelve los hijos con un proveedor de contextopara proporcionarles elLevelContext:
Esto le dice a React: "si cualquier componente dentro de este<Section>solicitaLevelContext, dales estelevel". El componente usará el valor del<LevelContext>más cercano en el árbol de UI por encima de él.
Es el mismo resultado que el código original, pero no necesitas pasar la proplevela cada componenteHeading. En su lugar, "descubre" su nivel de encabezado preguntando a laSectionmás cercana por encima:
- Pasas una prop
levelal<Section>. Sectionenvuelve a sus hijos en<LevelContext value={level}>.Headingsolicita el valor más cercano deLevelContextque esté por encima conuseContext(LevelContext).
Usar y proporcionar contexto desde el mismo componente
Actualmente, todavía tienes que especificar manualmente ellevelde cada sección:
Dado que el contexto te permite leer información de un componente superior, cadaSectionpodría leer elleveldelSectionque está por encima, y pasarlevel + 1hacia abajo automáticamente. Así es como podrías hacerlo:
Con este cambio, ya no necesitas pasar la proplevel ni al <Section>ni al<Heading>:
Ahora tantoHeadingcomoSectionleen elLevelContextpara averiguar qué tan "profundos" están. Y elSectionenvuelve a sus hijos en elLevelContextpara especificar que cualquier cosa dentro de él está en un nivel "más profundo".
Nota
Este ejemplo utiliza niveles de encabezado porque muestran visualmente cómo los componentes anidados pueden anular el contexto. Pero el contexto es útil para muchos otros casos de uso. Puedes pasar cualquier información que necesite todo el subárbol: el tema de color actual, el usuario que ha iniciado sesión actualmente, etc.
El contexto pasa a través de componentes intermedios
Puedes insertar tantos componentes como quieras entre el componente que proporciona el contexto y el que lo usa. Esto incluye tanto componentes integrados como<div>como componentes que puedas construir tú mismo.
En este ejemplo, el mismo componentePost(con un borde discontinuo) se renderiza en dos niveles de anidamiento diferentes. Observa que el<Heading>dentro de él obtiene su nivel automáticamente del<Section>más cercano:
No hiciste nada especial para que esto funcione. UnaSectionespecifica el contexto para el árbol dentro de ella, por lo que puedes insertar un<Heading>en cualquier lugar, y tendrá el tamaño correcto. ¡Pruébalo en el sandbox de arriba!
El contexto te permite escribir componentes que "se adaptan a su entorno" y se muestran de manera diferente dependiendo dedónde(o, en otras palabras,en qué contexto) se están renderizando.
El funcionamiento del contexto podría recordarte a laherencia de propiedades CSS.En CSS, puedes especificarcolor: bluepara un<div>, y cualquier nodo DOM dentro de él, sin importar cuán profundo esté, heredará ese color a menos que algún otro nodo DOM en el medio lo anule concolor: green. De manera similar, en React, la única forma de anular algún contexto proveniente de arriba es envolver a los hijos en un proveedor de contexto con un valor diferente.
En CSS, propiedades diferentes comocolor y background-colorno se anulan entre sí. Puedes establecer el<div>’scoloren rojo sin afectar elbackground-color. De manera similar,los diferentes contextos de React no se anulan entre sí.Cada contexto que creas concreateContext()está completamente separado de los demás, y vincula componentes que usan y proveenese contexto particular. Un componente puede usar o proveer muchos contextos diferentes sin problema.
Antes de usar el contexto
El contexto es muy tentador de usar. Sin embargo, esto también significa que es demasiado fácil abusar de él.El hecho de que necesites pasar algunas props varios niveles hacia abajo no significa que debas poner esa información en el contexto.
Aquí hay algunas alternativas que deberías considerar antes de usar el contexto:
- Comienza porpasar props.Si tus componentes no son triviales, no es inusual pasar una docena de props a través de una docena de componentes. Puede parecer tedioso, pero deja muy claro qué componentes usan qué datos. La persona que mantiene tu código estará agradecida de que hayas hecho explícito el flujo de datos con props.
- Extrae componentes ypasa JSX como hijosa ellos.Si pasas algunos datos a través de muchas capas de componentes intermedios que no usan esos datos (y solo los pasan más abajo), esto a menudo significa que olvidaste extraer algunos componentes en el camino. Por ejemplo, tal vez pasas props de datos como
postsa componentes visuales que no los usan directamente, como<Layout posts={posts} />. En su lugar, haz queLayouttomechildrencomo prop, y renderiza<Layout><Posts posts={posts} /></Layout>. Esto reduce el número de capas entre el componente que especifica los datos y el que los necesita.
Si ninguno de estos enfoques funciona bien para ti, considera el contexto.
Casos de uso para el contexto
- Temas:Si tu aplicación permite al usuario cambiar su apariencia (por ejemplo, modo oscuro), puedes colocar un proveedor de contexto en la parte superior de tu aplicación y usar ese contexto en los componentes que necesiten ajustar su apariencia visual.
- Cuenta actual:Muchos componentes pueden necesitar conocer al usuario que ha iniciado sesión actualmente. Ponerlo en contexto hace que sea conveniente leerlo en cualquier parte del árbol. Algunas aplicaciones también te permiten operar con múltiples cuentas al mismo tiempo (por ejemplo, para dejar un comentario como un usuario diferente). En esos casos, puede ser conveniente envolver una parte de la interfaz de usuario en un proveedor anidado con un valor de cuenta actual diferente.
- Enrutamiento:La mayoría de las soluciones de enrutamiento usan contexto internamente para mantener la ruta actual. Así es como cada enlace "sabe" si está activo o no. Si construyes tu propio enrutador, es posible que también quieras hacerlo.
- Gestión del estado:A medida que tu aplicación crece, puedes terminar con mucho estado cerca de la parte superior de tu aplicación. Muchos componentes distantes debajo pueden querer cambiarlo. Es comúnusar un reductor junto con el contextopara gestionar un estado complejo y pasarlo a componentes distantes sin demasiadas complicaciones.
El contexto no se limita a valores estáticos. Si pasas un valor diferente en el siguiente renderizado, ¡React actualizará todos los componentes que lo lean debajo! Por eso el contexto a menudo se usa en combinación con el estado.
En general, si alguna información es necesaria para componentes distantes en diferentes partes del árbol, es una buena indicación de que el contexto te ayudará.
Recapitulación
- El contexto permite que un componente proporcione información a todo el árbol debajo de él.
- Para pasar contexto:
- Créalo y expórtalo con
export const MyContext = createContext(defaultValue). - Pásalo al Hook
useContext(MyContext)para leerlo en cualquier componente hijo, sin importar cuán profundo esté. - Envuelve a los hijos en
<MyContext value={...}>para proporcionarlo desde un padre.
- Créalo y expórtalo con
- El contexto pasa a través de cualquier componente intermedio.
- El contexto te permite escribir componentes que "se adaptan a su entorno".
- Antes de usar contexto, intenta pasar props o pasar JSX como
children.
Prueba algunos desafíos
Challenge 1 of 1:Reemplaza el prop drilling con contexto #
En este ejemplo, activar la casilla de verificación cambia la prop imageSize que se pasa a cada <PlaceImage>. El estado de la casilla se mantiene en el componente de nivel superior App, pero cada <PlaceImage> necesita ser consciente de ello.
Actualmente, App pasa imageSize a List, que lo pasa a cada Place, que lo pasa al PlaceImage. Elimina la prop imageSize, y en su lugar pásala desde el componente App directamente a PlaceImage.
Puedes declarar el contexto en Context.js.
