Vamos a usar Redux y Thunk para crear el store y flujo de acciones de nuestra App
React.js es una librería moderna para el desarrollo de aplicaciones web y móviles
a nivel de frontend. Para aprender el mismo necesitamos conocimientos básicos y sólidos
de javascript, tener el enfoque claro de creación de componentes, entender el flujo de vida
de un componente en React y conocer sobre props y estados.
Este post va ser orientado para aquellas personas que ya han desarrollado algunas cosas en React
pero que quieren profundizar en el mismo, veremos el uso y configuración del store con Redux y el
uso de acciones con Thunk. Si aún no has escrito nada en React te invito de igual forma, si es de tu interés,
en leer e intentar entender algunos elementos.
A nivel de API vamos a usar el que hemos desarrollado en EL POST
ANTERIOR
y pueden clonarlo desde GITHUB
Entrando en Materia, vamos por conceptos:
Componente:
Es una parte de una vista que se puede configurar en base a varios componentes
o en base asi mismo, por ejemplo, un botón simple es un componente en base a si mismo,
no necesita de otros componentes para crearse. En cambio, un formulario es un componente
que necesita de otros componentes para crearse: inputs, buttons, radios, checkbox, entre otros.
Store:
Es el lugar en específico donde se almacena el estado de los componentes de una aplicación.
Estado de un componente:
Es el resultado visible de un componente, es decir, es lo que muestra
un componente en un momento específico al usuario, por ejemplo, una tabla con datos que contiene
paginación, el estado inicial de esta tabla sería mostrar dichos datos en la página uno. El estado de este
componente
puede cambiar cuando el usuario da click al enlace de paginado número dos, ya que así, se ejecuta una acción
que muestra los datos que corresponde con dicho paginado.
Acciones:
Son las tareas que dispara el usuario al usar algún componente, por ejemplo, al hacer
click a un botón de registro en un formulario, la acción del componente va tomar los datos del mismo,
va ejecutar una validación y de ser exitosa va enviar los datos al API, el API da una respuesta
(exitosa o no) y dicha respuesta se muestra en la vista al usuario.
Props:
Es una propiedad que permite pasar datos entre componentes, la idea siempre es mantener una
forma unidireccional de enviar los mismos (de padres a hijos) ya que al crecer la aplicación puede
ser complicado debuguear posibles problemas respecto a los datos si no se usan de esta forma.
La aplicación que vamos a implementar va contener: registro, login, logout, lista de clientes, registro de
clientes,
actualización y eliminación de clientes, lista de usuario y eliminación de usuarios.
Al momento de desarrollar en React con Redux y Thunk debemos tener en cuenta una
forma lógica de pensar a la hora de implementar nuestro frontend, es decir, el cómo
se va comportar nuestro flujo de datos, lo explico a continuación:
El usuario dispara una acción a través de un componente, esta acción llega a un "almacén de acciones"
que a su vez, dependiendo del tipo de acción (registrar, actualizar, editar o eliminar.. por ejemplo) y que a su
vez,
el tipo de acción debe tomarlo de otro almacén (otro archivo) ejecuta un evento y obtiene una respuesta.
La respuesta debe pasarla a un reducer y el reducer va transformar el estado del componente con la respuesta
de la acción. Ese nuevo estado lo devuelve al componente y ese resultado es el que va ver el usuario.
Cabe destacar que los tipos de acciones y las acciones pueden convivir en un mismo almacén (archivo)
pero no es muy práctico cuando la aplicación crece.
Ahora bien, vamos a ver la estructura del proyecto para comprender cómo implementar este concepto:
Como podemos ver he dividido el proyecto en tres partes; actions, components y reducers.
Cada módulo debe contener sus acciones, tipos, componentes y reducers.
La forma en como creamos una estructura en React puede variar, existe libertad para hacerlo
de distintas formas, sin embargo, desde mi punto de vista esta manera es una de las mas adecuadas
porque dividimos de una forma explicita cada proceso y es más fácil a la hora de desarrollar y debuguear.
También, debemos tener en cuenta la configuración de nuestro archivo App.js. Este archivo es una raíz y allí
debemos colocar los procesos que van afectar el comportamiento general de nuestra app.
La configuración de nuestra App refleja el uso del store y de react router.
El store es nuestro almacén de datos que comprende todo el proceso entre acciones, tipos y reducers.
React Router nos permite navegar entre componentes. En este proyecto tenemos un componente
que va afectar toda la aplicación llamado navBar (el menú). También podemos ver en la linea
20 que, al levantar los servicios la app va llamar una acción que se denomina loadUser y le pasa como
parámetro un key que deberiamos tener o no en localstorage, el key indica que es un email.
El proceso lo veremos más adelante en el tutorial pero a simple vista indica que es un proceso
de validación del usuario en sesión.
Vamos a configurar el archivo principal del proceso: store.js, aquí traemos a thunk
y a todos los reducers para configurar nuestro almacén de forma adecuada.
Ya tenemos nuestros archivos principales listos, ahora vamos a ir escribiendo cada proceso.
Primero, los types o tipos de acciones: Aquí vamos a escribir y exportar cada acción
de forma explicita, hacerlo de esta forma nos ayuda en las acciones a definir qué tipo de acción vamos
a ejecutar dependiendo del evento que nos envía el componente. Por ejemplo,
si el usuario en el formulario de registro da click al botón registrar, el tipo de acción
que se va tomar será ADD_USER
Ahora vamos a explicar las acciones de un par de módulos:
authAction.js:
Llamamos los tipos de acciones que necesitamos del almacén para tenerlo disponible
en nuestro proceso. La primera función que veremos será loadUser, aquí identificamos si el usuario
está en sesión o no. Durante la función disparamos varios tipos de acciones. El primero es LOADING_USER
que le indica a la app que está cargando un usuario. Cuando el request es exitoso ejecuta LOADED_USER que indica
que el usuario está en sesión. Caso contrario va disparar dos acciones: returnErrors que va indicar
los tipos de errores que tenemos y AUTH_ERROR que va indicar a la app que no estamos en sesión.
Si recordamos nuestro App.js al crearse dicho componente llama a esta función y le pasa el email, es
porque lo necesitamos para enviarlo al API junto al token.
clientAction.js:
En la linea 8 vemos la función getClients que permite
listar todos los clientes registrados. El proceso funciona de forma
similar. Primero lanzamos el tipo de acción LOADING_CLIENTS que indica a la app que se está
cargando o llamando los clientes y luego, de ser exitoso ejecuta GET_CLIENTS, caso contrario returnErrors.
Al observar estos procesos, nos damos cuenta que las acciones para todos los módulos se configura
de forma similar, dependiendo del instante del proceso se llama un tipo de acción.
En este proyecto, para las rutas que el API protege debemos envíar siempre el token del usuario
en los headers porque así el API lo requiere para indentificar al mismo.
Ahora, siguiendo el flujo de datos del store, recordemos que las acciones van a enviar sus resultados
a los reducers para que estos transformen el viejo estado al nuevo, veamos algunos ejemplos:
authReducer.js:
En los reducers llamamos los tipos de acciones de nuestro almacén, si, los mismos que
llamamos en nuestras acciones. Esto nos permite en este punto de tomar decisiones
y retornar el nuevo estado dependiendo del tipo de acción al componente.
También vamos a configurar nuestro estado inicial. El estado inicial es como comienza
nuestra app a ejecutar los procesos con los datos. Lo ideal es que siempre
el estado inical sea configurado de forma reseteada, es decir, sin datos.
También cabe mencionar que en este caso, nuestro estado inicial va contener el token
dependiendo si el usuario está logueado o no (es decir, si el token está almacenado en nuestro
localstorage o no)
Ahora si, vamos a describir el reducer:
Un reducer no es más que una función que recibe dos parámetros: el estado inicial y la acción
ejecutada por el usuario. Luego, dependiendo el tipo de acción devuelve el nuevo estado del componente.
Explicaremos algunos items:
LOADED_USER:
Identifica que el usuario se ha cargado exitosamente y configura el nuevo estado: Primero, escribimos
nuestra variable de propagación, es decir, el estado anterior (...state) esto nos permite indicar a la app
cómo viene dicho estado y si puede hacer match lógico o no con el nuevo estado. Además
vemos también en la linea 31 que user va recibir action.payload, pero, ¿eso qué quiere decir?
Entonces, nos vamos un momento a la acción que corresponde con dicho reducer (authAction.js) y
vamos a la linea 31 donde se dispara el mismo tipo de acción, vemos, que payload toma los datos
del usuario que me devuelve el API. Es decir, el reducer en su nuevo estado, al parámetro user le inyecta
el valor del usuario en sesión.
LOGIN_SUCCESS, REGISTER_SUCCESS:
En este punto, el reducer va devolver el usuario y el token que el API devuelve al ejecutar la acción.
También notamos que el reducer va guardar en localstorage el token como el email y aquí entendemos el por qué
nuestra app llamaba a loadUser y le pasaba el email almacenado en localstorage al inicio del proceso.
Recordemos el flujo de datos, los reducers van a tomar las acciones y tipos de acciones
y van a devolver el nuevo estado al componente, esto es un proceso casi mecánico, debes ver
siempre qué acción corresponde con cada reducer dependiendo del tipo de acción.
Ahora veamos un par de componentes.
ClientList.jsx:
Este componente muestra la lista de clientes que tenemos registrado en nuestra DB.
Aquí ejecutamos varias acciones. Primero, al cargar el componente ejecutamos la acción getClients()
y lo vemos en la linea 14. Ahora bien, la forma en como lo llama es a través de los props pero, en este punto,
no hemos explicado cómo hacer que una acción quede disponible como prop en un componente para que pueda ser
usado.
Entonces, observemos la linea 5 donde llamamos un par de acciones, getClients y deleteClients y luego vamos a la
linea
59 donde, a través de connect exportamos nuestro componente y lo conectamos con las acciones que queremos que
estén
disponibles. Este proceso, en todos los componentes es igual: Importamos acciones y conectamos con connect.
Luego podemos ver dentro del render del componente que tenemos un objeto clients disponible,
pero, ¿cómo llega allí? entonces vamos hacia atrás un poco y explicaremos un par de cosas:
Todos los reducers (que reciben acciones, tipos de acciones y envían el nuevo estado al componente)
deben ser estar también disponibles por dichos componentes de forma general, es decir, en toda nuestra
aplicación
ya que de esta forma se cumple el ciclo del flujo de datos correctamente. Ahora bien, para ello nosotros debemos
configurar un archivo que va llamar todos los reducers y los va exportar para que estén disponibles.
Ese archivo es el index.js del folder reducers y lo vemos a continuación:
En la linea 8 vemos clients: clientReducer, es decir, al cargar nuestra aplicación vamos a tener
los reducers de los clientes disponibles, dicho reducer es el que llamamos en nuestro componente.
Ahora, vamos al componente y explicaremos un detalle más.
En la linea 55 tenemos mapStateToProps, es una función que convierte los estados disponibles en la app
en props del componente para poder ser usados, se escribe y se conecta a connect en la linea 59 de la siguiente manera:
De esta forma queda el objeto clients (que se obtiene por una acción y se transforma en el reducer además de quedar
disponible en toda la app) accesible como prop dentro de nuestro componente en la linea 22
Al levantar nuestra app en el navegador vamos a imprimir los props
para observar los items que tenemos disponibles: Podemos observar getClients y
deleteClients como funciones accesibles y también el objeto clients que obtenemos
del reducer.
Volviendo al inicio: Componte dispara una acción, la acción toma el tipo de acción y ejecuta
un proceso, la respuesta del mismo se envía al reducer que crea el nuevo estado y lo deja disponible
en toda la app para que algún componente lo use cuando desee.
Con esto creo que queda más que claro cómo se configura Redux y Thunk para manejar acciones y
reducers, cómo acceder a ellos a través de los componentes y qué es lo que debemos siempre
pensar a la hora de desarrollar bajo estos conceptos a través del flujo de datos.
La app en funcionamiento voy a explicarla en un post que desarrollaré luego, donde
vamos a desplegar toda esta estructura: React.js, Express.js y MongoDB a distintos servicios
sin necesidad de usar ni configurar servidores.
He usado a nivel de UI react-bootstrap y reactstrap. Me parece muy fácil de implementar,
si han usado otros no duden en avisar!
Pueden clonar el proyecto AQUÍ
Como recomendación: practiquen mucho estos items si quieren aprenderlos bien, lean repetidamente
este post u otros y pregunten, no es fácil (para mi no lo fué) solo se necesita disciplina.
Espero que les sirva, saludos!
#React.js
#Redux
#Thunk
"Una forma de apoyar la creación de contenido técnico y de calidad es donando la cantidad que
creas conveniente, si quieres que haga un post sobre un tema en específico no dudes en avisarme"