Context API vs Redux
Allá por marzo de 2018 salió la versión v16.3 de React con una nueva API que hizo dudar y abrió el debate, vigente aún hoy en día, entre programadores acerca del uso de ciertas librerías.
Esta API de la que hablo es la Context API. Con esta nueva API podíamos hacer cosas como tener un contexto al principio de nuestra aplicación y usarlo para evitar hacer prop drilling. Y claro, si podemos hacer esto quiere decir que el uso de una librería de estado global quizás ya no era necesario. Y bueno… es cierto, aunque con matices.
Qué es Redux
Redux es una librería de gestión de estado global. De este tipo de librerías hay muchas, como Zustand, MobX, Pinia (para Vue), NgRX (angular) y muchas más. Como podéis ver, este tipo de librerías no solamente existen para React, sino que suele ser algo que podemos necesitar independientemente del framework o libraría de UI que usemos o, incluso, para aplicaciones con vanilla JS.
¿Qué son los State Managers?
Las librerías de gestión de estado, gestión de estado global o state managers son librerías que nos ayudan a mantener un estado global común a lo largo de toda nuestra aplicación.
Quiere decir, nos ayudan a centralizar y gestionar los datos de nuestra aplicación de manera que podamos acceder a ellos de forma fácil y rápida y (y esto es importante como ya veremos) sin que haya problemas de rendimiento.
Para dar un ejemplo un poco más visual imaginemos una aplicación que organizamos mediante componentes. Tendríamos un componente principal, normalmente llamado App, que engloba y tiene todos los componentes hijos que compondrá nuestra aplicación. Imaginemos el supuesto de que queremos acceder a un dato llamado popA en un componente llamado Foo muy profundo dentro de nuestro árbol de componentes y al mismo dato en otro componente llamado Bar igual de profundo, pero del que el primer ancestro común sea el componente App. Este dato lo tendríamos que poner en App y hacer “prop drilling” (que no es más que un patrón por el cual un componente padre pasa propiedades a su hijo y este, a su vez, pasa estas propiedades a su hijo, sucesivamente) a través de todos los componentes hijos de ambos Foo y Bar para que usen la misma propiedad y así, si cambia propA, se vea reflejado en ambos componentes. Esto en React normalmente lo haríamos con alguna hook de estado como useState o useReducer. Esto tiene varios problemas:
- El primero de ellos es que cuantas más propiedades comunes tengamos más propiedades tenemos que hacer pasar por todos los hijos. Así, todos los padres de Foo deben tener la propiedad propA para poder pasárselo a los hijos para que, finalmente, pueda llegar al destinatario: Foo. Cuantas más propiedades haya en común con otros componentes, más difícil se hará de mantener y gestionar. En nuestra imagen de ejemplo, Foo también comparte propB con el componente Baz, por lo que App debería tener también propB en su estado y pasárselo a sus componentes hijos que sean padres de Foo y de Baz (al igual que propA). Esto plantea un problema de escalado, puesto que cuantas más propiedades comunes haya, un sin fin de componentes intermedios tendrán que gestionar las propiedades solo para pasárselas a los componentes hijos finales que harán uso de esta.
- El segundo problema (en el que profundizaremos más tarde) es que todas estas propiedades tienen que ser estados de App, que es nuestro componente principal.
Con un state manager lo que hacemos es que el estado se gestiona fuera de los componentes en lo que llamamos estado global. Así App no tiene que tener los datos como estados propios del componente y en Foo, Bar o Baz podemos acceder a esos datos y modificarlos sin tener que hacer prop drilling de ellos, accediendo al estado global directamente.
Qué es la Context API
La Context API es una interfaz que nos provee React para evitar el prop drilling. La Context API nos provee de un mecanismo para crear un componente Provider en el cual insertar las propiedades que queramos para después, a través del hook useContext, acceder a esas mismas propiedades insertadas. Normalmente, cuando se usa la Context API como estado global debemos tener dichas propiedades en un componente que se las pasa al Provider. Imaginemos el caso anterior, pero con Context API. En este caso App seguiría teniendo dichas propiedades como estado (ya sea con useState o useReducer) pero en este caso crearíamos un contexto para así generar el Provider e insertar las propiedades. Con esto lo que conseguimos es evitar el prop drilling, ya que ahora en los hijos, haciendo uso de la hook useContext, podrán recoger los valores del Contexto insertados en el Provider. En caso de que tengamos que hacer una modificación de las propiedades en algún hijo también debemos de pasarle al Provider la función que modificará la propiedad para que el hijo pueda recogerla y usarla.
Como podemos ver, el resultado sería similar al que conseguimos con un State Manager, pero no igual.
Algo que tiene que quedar claro es que la Context API no es una API para manejar estado por sí misma, sino un método de inyección de datos. En la propia documentación de React podemos encontrar a React diciéndonos que es común usar la Context API con useReducer para manejar estados complejos.
Qué diferencia entonces Context API de un State Manager
Previamente, hemos dicho que hacer prop drilling tiene 2 problemas:
- Tener que pasar todas las propiedades de los hijos a travels de todos los padres.
- Tener estado en el componente padre más cercano.
Si bien con la Context API resolvemos el primer punto, no pasa así con el segundo.
En el ejemplo propuesto tenemos dos propiedades en el contexto: propA y propB. Ambas están en el mismo contexto, que están a su vez en App que es quien maneja el estado de estas propiedades. El problema está en que cada vez que una de las propiedades cambia en App, App se renderiza y cambia el contexto. Por consecuencia, todos los componentes subscritos a ese contexto se vuelven a renderizar, aunque las propiedades que estén usando no haya cambiado.
En este ejemplo podríamos decir que, si propB cambia, tanto Foo, como Bar y Baz se renderizan, ya que todos ellos están subscritos al contexto.
Cuando usar Context API o Redux
Bien, ya que entendemos lo que son ambas tecnologías y su diferencia podemos vislumbrar algunos ejemplos de usos de ambas. Y es que, aunque decidamos usar alguna librería de gestión de estado global como Redux, no exime a la aplicación del uso de la Context API si vemos que puede tener sentido. Quiero decir, ambas pueden estar usándose en una misma aplicación con sentido.
Si tenemos en cuenta los problemas de renderizaciones que puede darnos el uso de la Context API, podemos adivinar que su uso debemos restringirlo a aplicaciones pequeñas, en las cuales el hecho de que se re-rendericen completamente no tiene por qué ser un problema. También podemos encontrarle utilidad en aplicaciones o datos en concreto en las que tengan pocos cambios o que los cambios que haya que aplicar se apliquen a la aplicación al completo (porque igualmente tiene que renderizar completamente). Por ejemplo, el cambio entre temas oscuro-claro o los datos del login.
Con Redux es al contrario. Principalmente, si nuestro estado global tiene muchos cambios la opción de usar Redux u otra librería de estado global puede que sea más inteligente. Así evitaríamos re-renders innecesarios.
También si es un estado global muy grande o complejo puede ser muy útil usar Redux para disponer de las Redux DevTools y así poder ver el estado global durante nuestro desarrollo de manera fácil.
Una vez ya usamos Redux es fácil que el estado se nos vuelva más complejo, por tamaño y por dificultad. Por ello debemos pensar si debe estar en Redux o si, por el contrario, tiene sentido que esté en un contexto o incluso si podemos mejorar nuestros componentes para evitar ambos.
Igualmente, lo mejor es usar buenas prácticas de code-splitting con Redux y conocer al menos las recomendaciones que nos da el propio equipo de Redux sobre patrones, mejores prácticas y sugerencias a seguir con Redux.
FAQ
¿No usa Redux la Context API de manera interna? ¿No es entonces igual usar Context API directamente?
Sí, con matices. Redux es agnóstico al framework o librería que estemos usando por lo que podemos usar Redux sin React y, por lo tanto, sin Context API. Pero react-redux, que es el enlace entre React y Redux, sí usa Context API. Usa la Context API para hacer inyección de dependencia de la Store y que así la tengamos disponible a lo largo de todos los componentes de nuestra aplicación. La extracción y modificación de datos se hace de manera interna con Redux sin modificar la Store por lo que no tenemos los problemas de re-renders que tenemos con los contextos.
Si ya uso Context API para manejar el estado global, ¿es fácil migrarlo a Redux?
Realmente dependerá de cómo esté programado el contexto. Redux usa reducers para manejar el estado y React, en su documentación, recomienda usar la hook useReducer para manejar el estado de la aplicación (en el caso de que se quiera usar con Context API). Si este es el caso debería de ser relativamente sencillo de migrar.
Si ya uso Context API para manejar el estado global, ¿es fácil migrarlo a Redux? Quiero usar una librería de estado global. ¿Tengo que usar Redux?
No. Redux es una opción más para usar entre tantas otras, siendo quizás la más usada y famosa, pero no la única. Redux y su equipo creador recomiendan usar Redux Toolkit, que es una colección de librerías y mejoras que ellos opinan ser la mejor manera de usar Redux. Esta librería nos provee no solo de Redux sino también, por ejemplo, de RTK Query que es una utilidad de fetching que cachea el estado de las peticiones y su respuesta dentro de la store de redux, siendo así fácil y rápido de usar a lo largo de nuestra aplicación. En los últimos años han surgido otras alternativas como Zustand o Jotai que están ganando popularidad entre desarrolladores, así como otras más longevas como MobX. Cualquiera de ellas puede ser una gran opción. Decantarse por una u otra dependerá de las necesidades del proyecto y cómo pueden cada una de ellas solventarlas, así como (por qué no decirlo) la preferencia del equipo desarrollador.
Links de referencia
- https://redux-toolkit.js.org
- https://react.dev/reference/react/useReducer
- https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/#context-and-rendering-behavior
- https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/
- https://blog.isquaredsoftware.com/2019/06/presentation-react-redux-deep-dive/
- https://blog.isquaredsoftware.com/2021/01/context-redux-differences/#choosing-the-right-tool