Redux fácil y sencillo.

Josué Acevedo Maldonado
10 min readNov 1, 2020

Haciendo una app con Redux en 20 minutos o casi …

Photo by Farzad Nazifi on Unsplash

En el desarrollo web moderno que emplea frameworks como AngularJS o ReactJS las aplicaciones se construyen mediante componentes, los cuales incluyen HTML, JavaScript y CSS. Estos elementos solo se encargan de un solo elemento de la aplicación, por lo que la solución a desarrollar contempla la integración de múltiples componentes, de tal forma que se construye un árbol de componentes que interaccionan entre ellos.

Árbol de componentes en una aplicación web. Fuente: Elaboración propia

El manejo del estado es necesario para el funcionamiento de estos componentes, se puede decir que el estado es la información que almacena la aplicación web durante su ejecución.

El gestionar el estado de una aplicación basada en componentes consiste en asegurar que la interfaz grafica de usuario muestre el estado actual de la aplicación.

Cada componente es capaz de gestionar su propio estado sin mucho problema, sin embargo cuando la aplicación se vuelve compleja y los componentes necesitan compartir datos entre ellos es cuando se requiere de una herramienta especializada.

Componentes a ambos extremos del árbol, compartiendo datos entre ellos. Fuente: Elaboración propia

Redux es la biblioteca de gestión de estado para JavaScript más popular en el sector, el propósito de Redux es almacenar el estado de forma centralizada y hacer predecibles los cambios del mismo, imponiendo ciertas restricciones sobre las actualizaciones; de tal forma que es posible saber el cuándo, cómo y porque del estado actual que se almacena.

Componentes a ambos extremos del árbol, compartiendo datos entre ellos con Redux. Fuente: Elaboración propia

Redux

Redux presenta tres principios fundamentales:

  • Única fuente de verdad: El estado de toda la aplicación esta almacenado en un único objeto, guardado en un único store. Algunas funcionalidades que históricamente han sido difíciles de implementar (Deshacer/Rehacer) se vuelven secillas si todo el estado se guarda en un solo objeto.
  • El estado es de solo lectura: No es posible realizar un cambio en el estado de forma directa, la única forma de modificar el estado es mediante una funcion.
  • Los cambios se realizan con funciones puras: Para especificar como el objeto del estado es transformado, se utilizan funciones puras (a mismos inputs, mismos outputs), conocidos como reducers. Los reducers toman el estado anterior y un bloque de información (acción), y devuelven un nuevo estado. Recuerda devolver un nuevo objeto de estado en vez de modificar el anterior.

Aplicación.

Para ejemplificar el uso de Redux crearemos una aplicación de tareas pendientes (lista-tareas), usando ReactJS como framework de Frontend.

Primero crearemos una nueva app de ReactJS, para lo cual emplearemos el comando:

npx create-react-app lista-tareas
cd lista-tareas
npm start

Para instalar la última versión estable de Redux usaremos:

npm i redux

Nuestra aplicación contempla almacenar en un arreglo cada tarea a realizar como un objeto de estado con los siguientes atributos:

{
id: Int
text: String
}

Por lo tanto, este arreglo de objetos “tarea” estará almacenado en el store, tendremos un <input> desde el cual agregaremos una nueva tarea a la lista y un conjunto de etiquetas <li> donde mostraremos cada tarea en una lista <ul>; tanto el <input> como el <ul>, se encontrarán en componentes individuales ubicados en partes distintas del árbol de nuestra aplicación.

En primer lugar, haremos una función reducer. Por lo tanto, dentro de nuestra carpeta src de nuestra aplicación de ReactJS creamos una carpeta llama reducers, esto con la finalidad de tener ordenado nuestro proyecto, y dentro nuestra carpeta reducers creamos un archivo llamado index.js.

Dentro de este archivo (src/reducers/index.js) escribiremos una función que se encargara de almacenar en el store los datos enviados desde nuestra aplicación. Por lo tanto, la función recibirá el árbol de estado actual y un objeto que contiene los datos a guardar (acción).

Dados los mismos argumentos, la función reducer debería calcular y devolver el siguiente estado. Sin sorpresas. Sin efectos secundarios. Sin llamadas a APIs. Sin mutaciones. Solo cálculos.

La primera vez que la función es ejecutada, el valor de state es undefined, por lo que podemos especificar el valor predeterminado de state desde los parámetros de la función.

Ya que queremos almacenar un objeto “tarea” en un arreglo, nuestro reducer quedaría asi:

const tareas = (state = [], action) => {
switch (action.type) {
case 'AGREGAR_TAREA':
return [
...state,
{
id: action.id,
text: action.text
}
]
default:
return state
}
}

Nota: Cada función reducer está manejando su propia parte del estado global (state.tareas). El parámetro state es diferente por cada reducer, y corresponde con la parte del estado que controla.

Ya que cada función reducer maneja solo una parte del estado, esta función es la única encargada de realizar todas las trasformaciones (creación, actualización y eliminación) sobre el mismo, para poder distinguir la ejecución de cada una de las trasformaciones a realizar, se emplea el atributo type en el action, de esta manera, con un case en el reducer es posible distinguir cada una de estas operaciones.

Y escribimos otro reducer rootReducer, conocido como reducer raiz, que gestiona el estado completo de nuestra aplicación llamando a nuestro reducer tareas con su respectiva state key, de existir mas reducers todos ellos seran llamados por el rootReducer.

src/reducers/index.js

El reducer raíz puede combinar la salida de múltiples reductores en un único árbol de estado.

La API de Redux nos proporciona la funcion combineReducers() para crear el reducer raíz de forma automática. combineReducers() es una utilidad de gran ayuda, sin embargo ten la libertad de escribir tu propio reducer raíz!

Store es el objeto que toma el reducer raíz y proporciona los métodos de acceso y actualización del estado. El store tiene las siguientes responsabilidades:

  • Contiene el estado de la aplicación.
  • Permite el acceso al estado global via store.getState() .
  • Permite que el estado sea actualizado via store.dispatch(action). Es posible invocar astore.dispatch(action) desde cualquier lugar de la aplicación, incluyendo componentes y XHR callbacks, o incluso en intervalos programados.
  • Registra los listeners via store.subscribe(listener). Cada listener registrado será ahora invocado cada vez que el estado global cambie; los listeners podrán invocar store.getState() para obtener el estado global actual de la aplicacion.
  • Maneja la cancelación del registro de los listeners via el retorno de la función de store.subscribe(listener).

En nuestro archivo index.js de la carpeta src (src/index.js), importaremos el reducer raiz, al cual nombraremos como rootReducer y se lo pasaremos a la funcion createStore de la api de Redux.

src/index.js

Al recargar la página del navegador y ver la consola podemos ver que nuestras tareas se han almacenado de forma exitosa en el store.

Las acciones son cargas útiles de información que envían datos desde su aplicación a su tienda. Son la única fuente de información del store. Las acciones describen que algo pasó. Las acciones deben tener un atributo type que indique el tipo de acción que se está realizando.

let nextTodoId = 0
var action = {
type: "AGREGAR_TAREA"
, id: nextTodoId++
, text: "Tarea 1"
}

Los creadores de acciones son funciones que regresan acciones, la ventaja de estas es que es posible crear codigo reutilizable. Es fácil combinar los términos “acción” con “creador de acción”, así que haz lo mejor por usar los términos correctos.

let nextTodoId = 0
function agregarTarea(tarea) {
return {
type: "AGREGAR_TAREA"
, id: nextTodoId++
, text: tarea
}
}

Esto las hace más portables y fáciles de probar. Para efectivamente iniciar un despacho, pasa el creador de acciones a la función dispatch():

store.dispatch(agregarTarea("Tarea 1"))

Alternativamente, puedes crear un creador de acciones fusionado que despache automaticamente:

const fusion_agregarTarea = tarea => store.dispatch(agregarTarea(tarea))

Ahora únicamente hay que llamar al creador de acciones fusionado:

fusion_agregarTarea("Tarea 1")

Sabiendo esto, modificaremos nuestro proyecto; crearemos una carpeta llamada actions, dentro de la cual crearemos un archivo llamado index.js (src/actions/index.js); donde colocaremos todos nuestros creadores de acciones.

src/actions/index.js

En nuestro archivo index.js principal (src/index.js), importaremos nuestra funcion creador de acciones agregarTarea y escribiremos un creador de acciones fusionado.

src/index.js

Conexiones

Hasta este punto, solo hemos probado la API de Redux en el archivo index.js principal sin embargo lo que necesitamos es poder utilizar la función dispach() y getState() desde nuestros componentes de ReatJS, para lograr esto emplearemos un Conector, existen diferentes conectores dependiendo del Framework que estemos utilizando; en este caso particular usaremos react-redux.

React-Redux.

React Redux es el enlace oficial de React para Redux . Permite a sus componentes React leer datos de un store Redux y enviar acciones a la tienda para actualizar los datos.

Instalaremos la última versión de la librería reac-redux con este comando:

npm i react-redux

Provider

React Redux proporciona <Provider />, lo que permite que el store Redux esté disponible para todos los nodos de la aplicación. Por lo que, lo agregáremos en nuestro index.js principal.

src/index.js

Componentes contenedores y de vista

En ReactJS, existen dos tipos de componentes estructurales, los cuales nos permiten organizar nuestra aplicación.

Componentes Visuales

Conocidos en inglés como Presentational Components. Este tipo de componentes solo deben centrase y enfocar sus esfuerzos en cómo debe renderizarse la UI. Este tipo de componentes puede componerse de otros elementos visuales y suele incluir estilos y clases. Todos los datos implicados en su renderización se deben recibir a través de props.

Componentes Contenedor

Conocidos en inglés como Container Components. Estos componentes deben de dejar a un lado la interfaz y encargarse de la parte funcional, simplemente son contenedores de otros componentes y se encargan de gestionar la lógica de interacción, la lógica de los datos y el estado, haciendo las llamadas necesarias a servicios externos. Pueden importar un componente visual o directamente poseer una función que retorne jsx.

connect()

React Redux proporciona una función connect para que conecte su componente contonedor al store.

Crearemos una carpeta en nuestro proyecto llamada containers, donde colocaremos nuestros componentes contenedores que se conectaran al store de Redux.

Dentro de la carpeta containers crearemos el archivo agregarTareaContainer.js (src/containers/agregarTareaContainer.js), en este archivo, importaremos la función connect() de la librería react-redux; la cual espera dos funciones: mapStateToProps() y mapDispatchToProps(), en ese orden.

  • mapStateToProps() es una función que recibe el estado global del store y retorna un objeto que contendrá la key del estado que nos es de interés mostrar en el componente visual.
  • Por su parte,mapDispatchToProps() será una función que recibe como parámetro la función dispatch y devolverá un objeto que contendrá uno o mas de un creador de acciones fusionado. Este objeto será pasado a un componente visual para que pueda enviar sus datos al store.

En este momento solo deseamos tener un componente que nos permita escribir la tarea a realizar en un <input> y que este dato sea enviado al store, por lo que solo usaremos la función mapDispatchToProps().

El componte visual al que enviaremos nuestro creador de accciones fusionado será agregarTareaPresentational.js por lo que lo importaremos desde la carpeta presentationals y se la pasaremos a la función connect().

src/containers/agregarTareaContainer.js

Ahora crearemos la carpeta presentationals en nuestro proyecto además del archivo agregarTareaPresentational.js (src/presentationals/agregarTareaPresentational.js). Este componente visual recibirá un objeto con el creador de acciones fusionado que creamos en el componente contenedor, antes visto.

src/presentationals/agregarTareaPresentational.js

Ahora colocaremos nuestro componente contendor en el archivo App.js (src/App.js), por lo que lo importaremos y colocaremos la etiqueta respectiva después de la etiqueta <header>.

src/App.js

Nota: Al importar y colocar un componente en un return() ya sea de tipo contenedor o visual, es necesario hacerlo con el primer carácter en MAYUSCULA; de lo contrario el componente no se renderizará.

Para visualizar las tareas en nuestra lista, seguiremos los pasos anteriores, pero con un componente al que le pasaremos el estado global de la aplicación del que extraeremos el contenido de la llave tareas (state.tareas).

src/containers/verTareasContainer.js

src/presentationals/verTareasPresentational.js

src/App.js

Recapitulando

Todo el estado de tu aplicación esta almacenado en un único árbol dentro de un único store. La única forma de cambiar el árbol de estado es emitiendo una acción, un objeto describiendo que ocurrió. Para especificar como las acciones transforman el árbol de estado, usas reducers puros. Los componentes contenedores se conectan con el store de Redux y estos a su vez se vinculan con un componente de presentación o visual al que se le envían los datos a mostrar o la función que le permita enviar datos al store.

¡y listo!

El codigo de todo este proyecto se encuentra aquí.

Si has notado algún error en este artículo o sientes que se puedes hacer mejoras, por favor házmelo saber en la sección de comentarios, ¡lo aprecio mucho!

Josue Acevedo Maldonado es ingeniero de software, trabaja actualmente como consultor.

Conectarse en LinkedIn.

¡Gracias por ser parte de la comunidad!
Puede encontrar contenido relacionado en el canal de YouTube, Twitter, Twitch, Spotify, etc, ademas del libro Ensamblador X86.

Finalmente, si ha disfrutado este artículo y siente que ha aprendido algo valioso, por favor comparta para que otros también puedan aprender de él.

¡Gracias por leer!

--

--

Josué Acevedo Maldonado

Amante de la tecnologia y con pasion en resolver problemas interesantes, consultor, y creador del canal de youtube NEOMATRIX. https://linktr.ee/neomatrix