Desarrollar el frontend de una API con React.js

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:
pensar
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:
arquitectura
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.
app1
app2
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.
store
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
types
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.
loaduser
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.
getclients
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)
reducers
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.
reducers4
reducers5
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.
reducers6
reducers7
reducers8
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.
compoentn1
component2
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:
component3
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:
component4
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
component5
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.
component6
component6
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
Comentarios: Pronto!