Introducción

La forma en que fluye la información en React (desde un Componente Padre hacia un Componente Hijo) fuerza al desarrollador a ubicar el State en los Componentes superiores del Árbol de Componentes. Con aplicaciones chicas, en dónde no hay necesidad de que nuestro Árbol sea excesivamente grande, esto no suele ser un problema. Ahora bien, cuando las aplicaciones empiezan a crecer y el mismo valor de State debe estar presente en Componentes muy por debajo del Componente que lo contiene, es cuando nos enfrentamos con el problema del «prop drilling». 

Llamamos «prop drilling» a la  necesidad de hacer un «pasa mano» en el que algunos Componentes reciben una prop con el único objetivo de pasarla a uno de los Componentes que contiene. Esta práctica dificulta muchísimo mantener correctamente las distintas actualizaciones de State y, por tanto, complica la corrección de errores.

Otro problema que enfrentamos cuando trabajamos con React es la imposibilidad de comunicar dos Componentes que se encuentren al mismo nivel en el Árbol de Componentes (hermanos) sin involucrar a un Componente superior (padre) en el proceso.

Existen librerías externas, por ejemplo react-redux que se pueden agregar para dar solución a estos problemas. Context API es una solución nativa, es decir, tenemos acceso a ella simplemente por el hecho de estar trabajando con React.

Context API - Crear Contexto

En el post ReactJS: Higher Order Component Vs. Hooks Personalizados, hablamos de dos de los conceptos que se utilizan para crear un Contexto. La Composición y los HOC.

Un Contexto es un archivo que se divide en dos partes. Por un lado, la creación del espacio de almacenamiento y, por el otro, la creación del Provider. 

El espacio de almacenamiento se genera con la función createContext(default). Esta función recibe un valor que se aplicará en caso de que no defina ningún Provider. El uso del valor por defecto no es obligatorio, pero es una buena práctica para evitar que se rompa la aplicación en caso de error con el Provider.

El Provider es un Componente que aplica Composición para permitir que sus children accedan a los valores almacenados en el espacio de memoria. El Provider se arma mediante la declaración de una función que tiene que retornar el Componente<Contexto.Provider>. Este Componente es posible gracias a la creación del espacio de almacenamiento que hicimos con createContext().

El Provider recibe la props value, en ella debemos poner todos los datos que necesitamos hacer accesibles a los children.

Dentro del Provider podremos utilizar los Hooks useState y useEffect de la misma forma que en cualquier Componente. Esto permite que un Provider manipule el Ciclo de Vida de los Componentes que acceden a sus valores. Si un valor del Provider cambia, todo Componente que acceda a ese valor es debidamente actualizado.

Una vez creados, debemos exportar el Provider y el Contexto. Por convención, usamos export para el Provider y export default para el Contexto.


Context API - Uso del Contexto

Una vez generados el Contexto y el Provider necesitamos asegurarnos que los Componentes deseados tengan acceso. Para ello debemos envolverlos en el Provider.

Hacer esto permite que cualquier Componente que se encuentre dentro del Provider sea capaz de utilizar el Hook useContext(Contexto) para acceder a los datos almacenados. useContext(Contexto) devuelve los datos que se encuentran dentro de la prop value del Componente Provider. 

Ejemplo Completo

Consideraciones de uso

  • Context es una herramienta pensada para aplicaciones relativamente grandes que tienen varios niveles de Componentes y muchos Componentes en el mismo nivel que emplean la misma data. Si ese no es el caso de tu aplicación, Lifting State Up sigue siendo una excelente opción.
  • Si estamos queriendo evitar «prop drilling» en algunos Componentes, antes de recurrir a Context API podríamos considerar la opción de la Composición.
  • Es posible tener más de un Contexto por aplicación e, incluso, podemos hacer que un mismo Componente reciba información de dos Contextos distintos. Es importante recordar que en React toda la información fluye de arriba hacia abajo, por lo que, de necesitar que un Componente reciba la información de más de un Contexto, debemos envolver un Contexto en otro.
  • Si un Componente intenta acceder a los valores de un Contexto, pero no se encuentra envuelto por el Provider del mismo, se genera un error que rompe la ejecución de la aplicación. El valor por defecto que se puede pasar cuando creamos el Contexto ayuda con este problema. Igualmente, la única forma de que un Componente acceda a los valores de un Contexto es que esté envuelto por el Provider

Cuando utilizar Context API

  • Cambio de tema visual - Si la app permite que el usuario elija entre un tema de colores, entonces Context API será ideal para que todos los Componentes se actualicen en consecuencia de la elección del usuario.
  • Autenticación de usuario - Muchas veces necesitamos que los Componentes reconozcan al usuario conectado. Para esos casos. Context API es perfecto.
  • Ruteo - La mayoría de las librerías que permiten el manejo de rutas utilizan Context API para que sus Componentes internos “sepan” la ruta actual.
  • Manejo de State - Cuando tenemos una app muy grande, Lifting State Up nos fuerza a agregar valores de State en los Componentes más cercanos a la cima del Árbol de Componentes. En estos casos es mejor considerar el uso de Context API

Conclusión

Context API permite “desacoplar el State” del Árbol de Componentes, haciendo más cómoda la comunicación entre Componentes. Sin embargo, Lifting State Up y Composición siguen siendo excelentes formas de compartir valores.

Context API está pensado para su uso aplicaciones medianamente grandes o más. Además, es posible combinarlos con las otras técnicas de manejo de State.