Está en la página 1de 591

Sold to

ceronsanchezisai@gmail.com
Programación
Reactiva con React,
NodeJS & MongoDB

❝Se proactivo ante los cambios, pues una vez que llegan no tendrás tiempo para
reaccionar❞

─ Oscar Blancarte (2018)

Página | 2
Datos del autor:

Ciudad de México

e-mail: oscarblancarte3@gmail.com

Autor y Editor

© Oscar Javier Blancarte Iturralde

Queda expresamente prohibida la reproducción o transmisión, total o parcial, de


este libro por cualquier forma o medio; ya sea impreso, electrónico o mecánico,
incluso la grabación o almacenamiento informáticos sin la previa autorización
escrita del autor.

Composición y redacción:

Oscar Javier Blancarte Iturralde

Edición:

Oscar Javier Blancarte Iturralde

Portada

Arq. Jaime Alberto Blancarte Iturralde

Primera edición

Se publicó por primera vez en Enero del 2018

3 | Página
Acerca del autor
Oscar Blancarte es originario de Sinaloa, México donde estudió la carrera de
Ingeniería en Sistemas Computacionales y rápidamente se mudó a la Ciudad de
México donde actualmente radica.

Oscar Blancarte es un Arquitecto de software con más de 12 años de experiencia


en el desarrollo y arquitectura de software. Certificado como Java Programmer
(Sun microsystems), Análisis y Diseño Orientado a Objetos (IBM) y Oracle IT
Architect (Oracle).

A lo largo de su carrera ha trabajado para diversas empresas del sector de TI,


entre las que destacan su participación en diseños de arquitectura de software y
consultoría para clientes de los sectores de Retail, Telco y Health Care.

Oscar Blancarte es además autor de su propio blog


https://www.oscarblancarteblog.com desde el cual está activamente publicando
temas interesantes sobre Arquitectura de software y temas relacionados con la
Ingeniería de Software en general. Desde su blog ayuda a la comunidad a
resolver dudas y es por este medio que se puede tener una interacción más
directa con el autor.

Página | 4
Otras obras del autor

Introducción a los patrones de diseño – Un enfoque práctico

Hoy en día aprender patrones de diseño no es una


cualidad más, si no una obligación. Y es que estudiar
y comprender los patrones de diseño nos convierte en
un mejor programador/arquitecto y es clave para
conseguir una mejor posición en el mundo laboral.

Este libro fue creado con la intención de enseñar a sus


lectores cómo utilizar los patrones de diseño de una
forma clara y simple desde un enfoque práctico y con
escenarios del mundo real.

Tengo que aceptar que este no es un libro convencional


de patrones de diseño debido, principalmente, a que no
sigue la misma estructura de las primordiales obras
relacionadas con este tema. En su lugar, me quise
enfocar en ofrecer una perspectiva del mundo real,
en donde el lector pueda aprender a utilizar los patrones de diseño en entornos reales
y que puedan ser aplicados a proyectos reales.

Cuando empecé a estudiar sobre patrones de diseño, me di cuenta que siempre se


explicaban en escenarios irracionales que poco o ninguna vez podrías utilizar, como
por ejemplo para aprender a crear figuras geométricas, hacer una pizza o crear una
serie de clases de animales que ladren o maúllen; esos eran los ejemplos que siempre
encontraba, que, si bien, explicaban el concepto, se complicaba entender cómo
llevarlos a escenarios reales.
En este libro trato de ir un poco más allá de los ejemplos típicos para crear cosas
realmente increíbles. Por ejemplo:

• Crear tu propia consola de línea de comandos.


• Crear tu propio lenguaje para realizar consultas SQL sobre un archivo de
Excel.
• Crear aplicaciones que puedan cambiar entre más de una base de datos, por
ejemplo, Oracle y MySQL según las necesidades del usuario.
• Administrar la configuración global de tu aplicación.
• Crear un Pool de ejecuciones para controlar el número de hilos ejecutándose
simultáneamente, protegiendo nuestra aplicación para no agotar los recursos.
• Utilizar proxis para controlar la seguridad de tu aplicación.
• Utilizar estrategias para cambiar la forma en que los usuarios son autenticados
en la aplicación; como podría ser por Base de datos, Webservices, etcétera.
• Crear tu propia máquina de estados para administrar el ciclo de vida de tu
servidor.

5 | Página
Éstos son sólo algunos de los 25 ejemplos que abordaremos en este libro, los
cuales están acompañados, en su totalidad, con el código fuente para que seas
capaz de descargarlos, ejecutarlos y analizarlos desde tu propia computadora.

Adquiérelo en https://reactiveprogramming.io/books/es

Página | 6
Introducción a la arquitectura de software – Un enfoque
práctico

Desde los inicios de la ingeniería software, los


científicos de la computación lucharon por tener
formas más simples de realizar su trabajo, ya que
las cuestiones más simples, cómo imprimir un
documento, guardar un archivo o compilar el
código, era una tarea que podría tardar desde un
día hasta una semana.

Hoy en día, contamos con herramientas que van


indicando en tiempo real si tenemos un error de
sintaxis en nuestro código, pero no solo eso,
además, son los suficientemente inteligentes para
a completar lo que vamos escribiendo, incluso,
nos ayudan a detectar posibles errores en tiempo
de ejecución.

La realidad es que a medida que la tecnología avanza, tenemos cada vez más
herramientas a nuestra disposición, como lenguajes de programación, IDE’s,
editores de código, frameworks, librerías, plataformas en la nube y una gran
cantidad de herramientas que nos hacen la vida cada vez más simple, y por
increíble que parezca, los retos de hoy en día no son compilar el código, imprimir
una hoja, guardar en una base de datos; tareas que antes eran muy difíciles; lo
curioso es que hoy en día hay tantas alternativas para hacer cualquier cosa, que
por increíble que parezca, el reto de un programador hoy en día es decidirse por
qué tecnología irse, eso es increíble, tenemos tantas opciones para hacer lo que
sea, que el reto no es hacer las cosas, si no con que tecnologías la aremos.

Ahora bien, yo quiero hacerte una pregunta, ¿crees que hacer un programa hoy
en días es más fácil que hace años?
Seguramente a todos los que les haga esta pregunta concordarán con que hoy
en día es más fácil, sin embargo, y por increíble que parezca, el hecho de que las
tecnologías sean cada vez más simples, nos trae nuevas problemáticas y justo
aquí donde quería llegar.

A medida que las tecnologías son más simples y más accesibles para todas las
personas del mundo, las aplicaciones se enfrentan a retos que antes no existían,
como la concurrencia, la seguridad, la alta disponibilidad, el performance, la
usabilidad, la reusabilidad, testabilidad, funcionalidad, modificabilidad,
portabilidad, integridad, escalabilidad, etc, etc. Todos estos son conceptos que
prácticamente no existían en el pasado, porque las aplicaciones se desarrollaban
para una audiencia muy reducida y con altos conocimientos técnicos, además, se
ejecutaban en un Mainframe, lo que reducía drásticamente los problemas de
conectividad o intermitencia, pues todo se ejecuta desde el mismo servidor.
Adquiérelo en https://reactiveprogramming.io/books/es

7 | Página
Página | 8
Agradecimientos
Este libro tiene una especial dedicación a mi esposa Liliana y mi hijo Oscar,
quienes son la principal motivación y fuerza para seguir adelante todos los días,
por su cariño y comprensión, pero sobre todo por apoyarme y darme esperanzas
para escribir este libro.

A mis padres, quien con esfuerzo lograron sacarnos adelante, darnos una
educación y hacerme la persona que hoy soy.

A todos mis lectores anónimos de mi blog y todas aquellas personas que, de


buena fe, compraron y recomendaron mi primer libro (Introducción a los patrones
de diseño) y fueron en gran medida lo que me inspiro en escribir este segundo
libro.

Finalmente, quiero agradecerte a ti, por confiar en mí y ser tu copiloto en esta


gran aventura para aprender React, NodeJS, MongoDB y muchas cosas más. :)

9 | Página
Prefacio
Cada día, nacen nuevas tecnologías que ayudan a construir aplicaciones web más
complejas y elaboras, con relativamente menos esfuerzo, ayudando a que casi
cualquiera persona con conocimientos básicos de computación puede realizar una
página web. Sin embargo, no todo es felicidad, pues realizar un trabajo
profesional, requiere de combinar muchas tecnologías para entregar un producto
de calidad.

Puede que aprender React o NodeJS no sea un reto para las personas que ya
tiene un tiempo en la industria, pues ya están familiarizados con HTML,
JavaScript, CSS y JSON, por lo que solo deberá complementar sus conocimientos
con una o dos tecnologías adicionales, sin embargo, para las nuevas
generaciones de programadores o futuros programadores, aprender React o
NodeJS puede implicar un reto aun mayor, pues se necesita aprender primero
las bases, antes de poder programar en un stack de más alto nivel.

Cuando yo empecé a estudiar el stack completo de React + NodeJS + MongoDB,


fue necesario tener que leer 3 libros completos, pues cada uno enseñaba de
forma separada una parte de la historia. Cada libro me enseñó a utilizar las
tecnologías de forma individual, lo cual era fantástico, sin embargo, cuando llego
el momento de trabajar con todas las tecnologías juntas, comenzaron los
problemas, pues nadie te enseñaba como unir todo en un solo proyecto,
optimizar y pasar a producción como una aplicación completa. Todo esto, sin
mencionar los cientos o quizás miles de foros y blogs que analice para aprender
los trucos más avanzados.

Este libro pretende evitarte ese dolor de cabeza que yo tuve por mucho tiempo,
pues a lo largo de este libro aprenderemos a utilizar React + NodeJS con Express
+ MongoDB y aderezaremos todo esto con Redux, uno de los módulos más
populares y avanzados para el desarrollo de aplicaciones Web profesionales.
Finalmente aprenderemos a crear un API REST completo con NodeJS y utilizando
el Estándar de Autenticación JSON Web Tokens.

El objetivo final de este libro es que aprendas a crear aplicaciones reactivas con
React, apoyado de las mejores tecnologías disponibles. Es por este motivo que,
durante la lectura de este libro, trabajaremos en un único proyecto que irá
evolucionando hasta terminarlo por completo. Este proyecto será, una réplica de
la red social Twitter, en la cual podremos crear usuarios, autenticarnos, publicar
Tweets, seguir a otros usuarios y ver las publicaciones de los demás en nuestro
home.

Página | 10
Cómo utilizar este libro
Este libro es en lo general fácil de leer y digerir, pues tratamos de enseñar todos
los conceptos de la forma más simple y asumiendo que el lector tiene poco o
nada de conocimiento del tema, así, sin importar quien lo lea, todos podamos
aprender fácilmente.

Como parte de la dinámica de este libro, hemos agregado una serie de tipos de
letras que hará más fácil distinguir entre los conceptos importantes, código,
referencias a código y citas. También hemos agregado pequeñas secciones de
tips, nuevos conceptos, advertencias y peligros, los cuales mostramos mediante
una serie de íconos agradables para resaltar a la vista.

Texto normal:

Es el texto que utilizaremos durante todo el libro, el cual no enfatiza nada en


particular.

Negritas:

El texto en negritas es utilizado para enfatizar un texto, de tal forma que


buscamos atraer tu atención, que es algo importante.

Cursiva:
Es texto lo utilizamos para hacer referencia a fragmentos de código como
una variable, método, objeto o instrucciones de líneas de comandos. Pero
también es utilizada para resaltar ciertas palabras técnicas.

Código

Para el código utilizamos un formato especial, el cual permite colorear ciertas


palabras especiales, crear el efecto entre líneas y agregar el número de línea que
ayude a referencia el texto con el código.

1. ReactDOM.render(
2. <h1>Hello, world!</h1>,
3. document.getElementById('root')
4. );

El texto con fondo verdes, lo utilizaremos para indicar líneas que se agregan al
código existente.

Mientras que el rojo y tachado, es para indicar código que se elimina de un


archivo existente.

11 | Página
Por otra parte, tenemos los íconos, que nos ayudan para resaltar algunas cosas:

Nuevo concepto: <concepto>

Cuando mencionamos un nuevo concepto o termino


que vale la pena resaltar, es explicado mediante una
caja como esta.

Tip

Esta caja la utilizamos para dar un tip o sugerencia


que nos puede ser de gran utilidad.

Importante

Esta caja se utiliza para mencionar algo muy


importante.

Error común

Esta caja se utiliza para mencionar errores muy


comunes que pueden ser verdadero dolor de cabeza o
para mencionar algo que puede prevenir futuros
problemas.

Documentación

Esta caja se muestra cada vez que utilizamos un nuevo


servicio del API REST, la cual tiene la intención de
indicar al lector donde puede encontrar la
documentación del servicio, como es la URL,
parámetros de entrada/salida y restricciones de
seguridad.

Página | 12
Código fuente
Todo el código fuente de este libro está disponible en GitHub y dividido de tal
forma que, cada capítulo tenga un Branch independiente. El código fuente lo
puedes encontrar en:

https://github.com/oscarjb1/books-reactiveprogramming.git

Para ver el código de cada capítulo, solo es necesario dirigirse al listado de


branches y seleccionar el capitulo deseado:

Fig 1 - Git branches

La segunda y más recomendable opción es, utilizar un cliente de Git, como lo es


Source Tree y clonar el repositorio, lo que te permitirá tener acceso a todos los
branches desde tu disco duro.

Fig 2 - Source Tree

13 | Página
Requisitos previos
Este libro está diseñado para que cualquier persona con conocimientos básicos
de programación web, puedo entender la totalidad de este libro, sin embargo,
debido a la naturaleza de React y NodeJS, es necesario conocer los fundamentos
de JavaScript, pues será el lenguaje que utilizaremos a lo largo de todo este libro.

También será interesante contar con conocimientos básicos de CSS3, al menos


para entender como declarar selectores y aplicarlos a los elementos de HTML.

Página | 14
INTRODUCCIÓN
Muy lejos han quedado los tiempos en los que Tim Berners Lee, conocido como
el padre de la WEB; estableció la primera comunicación entre un cliente y un
servidor, utilizando el protocolo HTTP (noviembre de 1989). Desde entonces, se
ha iniciado una guerra entre las principales compañías tecnológicas por dominar
el internet. El caso más claro, es la llamada guerra de los navegadores,
protagonizado por Netscape Communicator y Microsoft, los cuales buscaban que
sus respectivos navegadores (Internet Explorer y Netscape) fueran el principal
software para navegar en internet.

Durante esta guerra, las dos compañías lucharon ferozmente, Microsoft luchaba
por alcanzar a Netscape, quien entonces le llevaba la delantera y Netscape
luchaba para no ser alcanzados por Microsoft. Como hemos de imaginar,
Microsoft intento comprar a Netscape y terminar de una vez por todas con esta
guerra, sin embargo, Netscape se negó reiteradamente a la venta, por lo que
Microsoft inicio una de las más descaradas estrategias; amenazo con copiar
absolutamente todo lo que hiciera Netscape si no accedían a la venta.

Económicamente hablando, Microsoft era un gigante luchando contra una


hormiga, el cual pudo contratar y poner a su plantilla a cuento ingeniero fuera
necesarios para derrotar a Netscape.

En esta carrera por alcanzar a Netscape, ambas empresas empezaron a lanzar


releaces prácticamente cada semana, y semana con semana, los navegadores
iban agregando más y más funcionalidad, lo cual podría resultar interesante, pero
era todo lo contrario. Debido a la guerra sucia, ninguna empresa se apegaba a
estándares, que además eran prácticamente inexistentes en aquel entonces. Lo
que provocaba que los programadores tuvieran que escribir dos versiones de una
misma página, una que funcionara en Netscape y otra que funcionara para
Internet Explorer, pues las diferencia que existían entre ambos navegadores era
tan grande, que era imposible hacer una misma página compatible para los dos
navegadores.

Para no hacer muy larga esta historia, y como ya sabrás, Microsoft termino
ganando la guerra de los navegadores, al proporcionar Internet Explorer
totalmente gratis y preinstalado en el sistema operativo Windows.

Hasta este punto te preguntarás, ¿qué tiene que ver toda esta historia con React
y NodeJS?, pues la verdad es que mucho, pues durante la guerra de los
navegadores Netscape invento JavaScript. Aunque en aquel momento, no era un
lenguaje de programación completo, sino más bien un lenguaje de utilidad, que
permitía realizar cosas muy simples, como validar formularios, lanzar alertas y
realizar algunos cálculos. Es por este motivo que debemos recordar a Netscape,
pues fue el gran legado que nos dejó.

Finalmente, Netscape antes de terminar de morir, abre su código fuente y es


cuando nace Mozilla Firefox, pero esa es otra historia…

15 | Página
Retornando a JavaScript, este lenguaje ha venido evolucionando de una manera
impresionante, de tal forma que hoy en día es un lenguaje de programación
completo, como Java o C#. Tan fuerte ha sido su evolución y aceptación que hoy
en día podemos encontrar a JavaScript del lado del servidor, como es el caso de
NodeJS, creado por Ryan Dahl en 2009. De la misma forma, Facebook desarrollo
la librería React basada en JavaScript.

Tanto React como NodeJS funcionan en su totalidad con JavaScript, React se


ejecuta desde el navegador, mientras que NodeJS se ejecuta desde un servidor
remoto, el cual ejecuta código JavaScript.

JavaScript es un lenguaje de programación interpretado, lo que quieres decir que


el navegador va ejecutando el código a medida que es analizado, evitando
procesos de compilación. Además, JavaScript es un lenguaje regido por el
Estándar ECMAScript el cual, al momento de escribir estas líneas, se encuentra
en la versión 6 estable y se está desarrollando la versión 7.

Nuevo concepto: ECMAScript

ECMAScript es una especificación de lenguaje de


programación propuesta como estándar por NetScape
para el desarrollo de JavaScript.

Comprender la historia y la funcionalidad de JavaScript es fundamental para


aprender React y NodeJS, pues ambos trabajan 100% con este lenguaje. Aunque
en el caso de React, existe un lenguaje propio llamado JSX, que al final se
convierte en JavaScript. Pero eso lo veremos en su momento.

Página | 16
Índice

Acerca del autor..................................................................................................................................... 4

Otras obras del autor ............................................................................................................................. 5

Agradecimientos .................................................................................................................................... 9

Prefacio ............................................................................................................................................... 10

Cómo utilizar este libro ........................................................................................................................ 11

Código fuente ...................................................................................................................................... 13

Requisitos previos................................................................................................................................ 14

INTRODUCCIÓN ................................................................................................................................... 15

Índice ................................................................................................................................................... 17

Por dónde empezar ............................................................................................................................. 25


Introducción a React .............................................................................................................................. 27
Server Side Apps vs Single Page Apps ............................................................................................... 28
Introducción a NodeJS ........................................................................................................................... 30
NodeJS y la arquitectura de Micro Servicios ..................................................................................... 31
Introducción a MongoDB ....................................................................................................................... 32
Bases de datos Relacionales VS MongoDB ....................................................................................... 32
La relación entre React, NodeJs & MongoDB ........................................................................................ 34
Resumen ................................................................................................................................................ 35

Preparando el ambiente de desarrollo ................................................................................................. 36


Instalando Visual Studio Code ............................................................................................................... 36
Instalación de Visual Studio Code ..................................................................................................... 37
Instalar PlugIns .................................................................................................................................. 38
Instalando NodeJS & npm ...................................................................................................................... 39
Creando mi primer proyecto de React ................................................................................................... 40
Estructura base de un proyecto ........................................................................................................ 40
Creación un proyecto paso a paso .................................................................................................... 41
Creación del proyecto con utilerías .................................................................................................. 50
Descargar el proyecto desde el repositorio ...................................................................................... 51
Gestión de dependencias con npm................................................................................................... 52
Micro-modularización ....................................................................................................................... 56
Introducción a WebPack ........................................................................................................................ 57
Instalando Webpack ......................................................................................................................... 58
El archivo webpack.config.js ............................................................................................................. 59
React Developer Tools ........................................................................................................................... 60
Resumen ................................................................................................................................................ 63

17 | Página
Introducción al desarrollo con React .................................................................................................... 64
Programación con JavaScript XML (JSX) ................................................................................................ 64
Diferencia entre JSX, HTML y XML .................................................................................................... 65
Contenido dinámico y condicional .................................................................................................... 72
JSX Control Statements ..................................................................................................................... 75
Transpilación ..................................................................................................................................... 79
Programación con JavaScript puro. ....................................................................................................... 80
Element Factorys .............................................................................................................................. 81
Element Factory Personalizados ....................................................................................................... 82
Resumen ................................................................................................................................................ 84

Introducción a los Componentes.......................................................................................................... 85


La relación entre Components y Web Component ................................................................................ 85
Componentes con estado y sin estado ................................................................................................... 87
Componentes sin estado .................................................................................................................. 88
Componentes con estado ................................................................................................................. 90
Jerarquía de componentes ..................................................................................................................... 93
Propiedades (Props) ............................................................................................................................... 95
PropTypes .............................................................................................................................................. 98
Validaciones avanzadas .................................................................................................................. 100
DefaultProps ........................................................................................................................................ 102
Refs ...................................................................................................................................................... 102
Referencias con String .................................................................................................................... 104
Referencias con Hooks .................................................................................................................... 104
Alcance los Refs .............................................................................................................................. 105
Keys ...................................................................................................................................................... 105
Las 4 formas de crear un Componente ................................................................................................ 108
ECMAScript 5 – createClass (Obsoleta)........................................................................................... 108
ECMAScript 6 - React.Component .................................................................................................. 108
ECMAScript 6 - Function Component ............................................................................................. 109
ECMAScript 7 - React.Component .................................................................................................. 110
Resumen .............................................................................................................................................. 111

Introducción al proyecto Mini Twitter ............................................................................................... 112


Un vistazo rápido al proyecto .............................................................................................................. 112
Página de inicio ............................................................................................................................... 113
Perfil de usuario .............................................................................................................................. 113
Editar perfil de usuario ................................................................................................................... 114
Página de seguidores ...................................................................................................................... 115
Detalle del Tweet ............................................................................................................................ 115
Inicio de sesión (Login) .................................................................................................................... 116
Registro de usuarios (Signup) ......................................................................................................... 116
Análisis al prototipo del proyecto ........................................................................................................ 117
Componente TwitterDashboard ..................................................................................................... 118
Componente TweetsContainer ....................................................................................................... 119
Componente UserPage ................................................................................................................... 120
Componente Signup ....................................................................................................................... 121
Componente Login .......................................................................................................................... 122
Jerarquía de los componentes del proyecto......................................................................................... 123

Página | 18
El enfoque Top-down & Bottom-up ..................................................................................................... 124
Top-down ........................................................................................................................................ 124
Bottom-up ....................................................................................................................................... 125
El enfoque utilizado y porque ......................................................................................................... 125
El API REST del proyecto Mini Twitter .................................................................................................. 126
Invocando el API REST desde React ..................................................................................................... 129
Mejorando la clase APIInvoker ....................................................................................................... 131
El componente TweetsContainer ......................................................................................................... 134
El componente Tweet .......................................................................................................................... 137
Paginando los Tweets .......................................................................................................................... 144
Resumen .............................................................................................................................................. 147

Introducción al Shadow DOM y los Estados ....................................................................................... 148


Introducción a los Estados ................................................................................................................... 148
Establecer el estado de Componente ................................................................................................... 149
Actualizando el estado de un Componente ......................................................................................... 151
La librería immutability-helper ....................................................................................................... 153
El Shadow DOM de React .................................................................................................................... 155
Atributos de los elementos: ............................................................................................................ 156
Eventos ........................................................................................................................................... 157
Resumen .............................................................................................................................................. 158

Trabajando con Formularios .............................................................................................................. 159


Controlled Components ....................................................................................................................... 159
Uncontrolled Components ................................................................................................................... 162
Enviar el formulario ............................................................................................................................. 163
Mini Twitter (Continuación 1) .............................................................................................................. 165
El componente Signup .................................................................................................................... 165
El componente login ....................................................................................................................... 174
Resumen .............................................................................................................................................. 180

Ciclo de vida de los componentes ...................................................................................................... 181


El Constructor ...................................................................................................................................... 184
Function getDerivedStateFromProps ................................................................................................... 186
Function componentWillMount (deprecated) ..................................................................................... 187
Function render.................................................................................................................................... 187
Function getSnapshotBeforeUpdate .................................................................................................... 189
Function componentDidMount ............................................................................................................ 190
Function componentWillReceiveProps (deprecated) ........................................................................... 190
Function shouldComponentUpdate ..................................................................................................... 191
Function forceUpdate .......................................................................................................................... 192
Function componentWillUpdate (deprecated) .................................................................................... 193
Function componentDidUpdate ........................................................................................................... 194

19 | Página
Function componentWillUnmount....................................................................................................... 194
Flujos de montado de un componente ................................................................................................ 195
Flujos de actualización ......................................................................................................................... 196
Flujos de desmontaje de un componente ............................................................................................ 197
Mini Twitter (Continuación 2) .............................................................................................................. 198
Configuración inicial ........................................................................................................................ 198
El componente TwitterApp ............................................................................................................. 199
El componente TwitterDashboard .................................................................................................. 203
El componente Profile .................................................................................................................... 205
El componente SuggestedUsers ..................................................................................................... 209
El componente Reply ...................................................................................................................... 213
Resumen .............................................................................................................................................. 230

React Routing .................................................................................................................................... 231


Single page Application ....................................................................................................................... 232
Router & Route .................................................................................................................................... 233
Switch .................................................................................................................................................. 234
Link....................................................................................................................................................... 235
NavLink ................................................................................................................................................ 236
Redirect ................................................................................................................................................ 237
URL params.......................................................................................................................................... 237
Mini Twitter (Continuación 3) .............................................................................................................. 239
Implementando Routing en nuestro proyecto ............................................................................... 239
El componente AuthRouter ............................................................................................................ 240
El componente Toolbar ................................................................................................................... 241
Toques finales al componente Login .............................................................................................. 246
Toques finales al componente Signup ............................................................................................ 247
El componente UserPage ................................................................................................................ 250
El componente MyTweets .............................................................................................................. 268
Actualizar el componente Tweet .................................................................................................... 273
Resumen .............................................................................................................................................. 275

Interfaces interactivas ....................................................................................................................... 276


Qué son las transiciones ...................................................................................................................... 276
Qué son las animaciones ..................................................................................................................... 277
Introducción a CSSTranstion ................................................................................................................ 279
Mini Twitter (Continuación 4) .............................................................................................................. 281
El componente UserCard ................................................................................................................ 281
El componente Followings .............................................................................................................. 284
El componente Followers ............................................................................................................... 287
Resumen .............................................................................................................................................. 291

Componentes modales ...................................................................................................................... 292


Algunas librerías existentes ................................................................................................................. 292
Implementando modal de forma nativa .............................................................................................. 293
React Portal ......................................................................................................................................... 295

Página | 20
Mini Twitter (Continuación 5) .............................................................................................................. 296
El componente TweetDetail............................................................................................................ 296
El componente Modal ..................................................................................................................... 299
Últimos retoques al proyecto ......................................................................................................... 305
Resumen .............................................................................................................................................. 307

Context .............................................................................................................................................. 308


createContext ...................................................................................................................................... 309
Provider................................................................................................................................................ 310
Consumer ............................................................................................................................................. 312
contextType ......................................................................................................................................... 314
Mini Twitter (Continuación 6) .............................................................................................................. 315
Creado el UserContext .................................................................................................................... 315
Implementando muestro primer Provider...................................................................................... 315
Implementando nuestros Consumers............................................................................................. 316
Conclusiones ........................................................................................................................................ 321

Hooks................................................................................................................................................. 322
Introducción a los Hooks ...................................................................................................................... 326
Estado ............................................................................................................................................. 327
Ciclo de vida .................................................................................................................................... 330
Creando nuestros propios Hooks .................................................................................................... 334
Utilizando el Context con los Hooks ............................................................................................... 335
El futuro de las clases ...................................................................................................................... 336
Mini Twitter (Continuación 7) .............................................................................................................. 337
Migrando el componente Followers ............................................................................................... 337
Migrando el componente Followings ............................................................................................. 339
Migrando el componente SuggestedUser ...................................................................................... 340
Migrando el componente Toolbar .................................................................................................. 341
Migrando el componente TwitterDashboard ................................................................................. 343
Migrando el componente TwitterApp ............................................................................................ 343
Migrando el componente UserPage ............................................................................................... 346
Conclusiones ........................................................................................................................................ 352

Redux ................................................................................................................................................ 353


Introducción a Redux ........................................................................................................................... 354
Como funciona Redux ..................................................................................................................... 355
Los tres principios de Redux ........................................................................................................... 366
Redux sin hooks ................................................................................................................................... 368
Mini Twitter (Continuación 8) .............................................................................................................. 370
Instalando las dependencias ........................................................................................................... 370
Creando el Store y los reducers ...................................................................................................... 371
Implementando el Provider ............................................................................................................ 375
Redux logger ................................................................................................................................... 376
Redux DevTool ................................................................................................................................ 378
Migrando el componente UserPage ............................................................................................... 380
Migrando el componente Followers ............................................................................................... 387
Migrando el componente Followings ............................................................................................. 389
Migrando el componente TweetsContainer ................................................................................... 390
Migrando el componente TwitterDashboard ................................................................................. 392

21 | Página
Resumen .............................................................................................................................................. 394

Introducción a NodeJS ....................................................................................................................... 395


Porque es importante aprender NodeJS .............................................................................................. 395
El Rol de NodeJS en una aplicación ...................................................................................................... 396
NodeJS es un mundo ............................................................................................................................ 397
Introducción a Express ......................................................................................................................... 397
Instalando Express .......................................................................................................................... 398
El archivo package.json ................................................................................................................... 398
Node Mudules................................................................................................................................. 401
Creando un servidor de Express ..................................................................................................... 402
Express Verbs ....................................................................................................................................... 404
Método GET .................................................................................................................................... 404
Método POST .................................................................................................................................. 404
Método PUT .................................................................................................................................... 405
Método DELETE............................................................................................................................... 405
Consideraciones adicionales. .......................................................................................................... 405
Implementemos algunos métodos. ................................................................................................ 405
Trabajando con parámetros ................................................................................................................ 409
Query params ................................................................................................................................. 409
URL params ..................................................................................................................................... 410
Body params ................................................................................................................................... 411
Middleware .......................................................................................................................................... 414
Middleware de nivel de aplicación ................................................................................................. 416
Middleware de nivel direccionador ................................................................................................ 416
Middleware de terceros.................................................................................................................. 417
Middleware incorporado ................................................................................................................ 417
Error Handler ....................................................................................................................................... 418
Resumen .............................................................................................................................................. 420

Introducción a MongoDB ................................................................................................................... 421


Porque es importante aprender MongoDB .......................................................................................... 421
El rol de MongoDB en una aplicación .................................................................................................. 423
Instalando MongoDB ........................................................................................................................... 424
Habilitando el acceso por IP............................................................................................................ 428
Instalando Compass, el cliente de MongoDB ................................................................................. 430
Que son los Schemas de Mongoose ............................................................................................... 434
Schemas avanzados ........................................................................................................................ 435
NodeJS y MongoDB .............................................................................................................................. 438
Estableciendo conexión desde NodeJS ........................................................................................... 438
Como funciona MongoDB .................................................................................................................... 440
Que son las Colecciones .................................................................................................................. 441
Que son los Documentos ................................................................................................................ 442
Operaciones básicas con Compass ................................................................................................. 442
Aprender a realizar consultas .............................................................................................................. 448
Filter ................................................................................................................................................ 448
Project ............................................................................................................................................. 459
Sort.................................................................................................................................................. 462
Paginación con Skip y Limit ............................................................................................................. 463

Página | 22
Schemas del proyecto Mini Twitter ..................................................................................................... 465
Tweet Scheme................................................................................................................................. 465
Profile Scheme ................................................................................................................................ 466
Ejecutar operaciones básicas .......................................................................................................... 468
Resumen .............................................................................................................................................. 475

Desarrollo de API REST con NodeJS .................................................................................................... 476


¿Qué es REST y RESTful? ...................................................................................................................... 476
REST vs SOA ......................................................................................................................................... 477
Preparar nuestro servidor REST ........................................................................................................... 478
Configuración inicial del servidor .................................................................................................... 480
Establecer conexión con MongoDB ................................................................................................ 482
Creando un subdominio para nuestra API ...................................................................................... 483
Desarrollo del API REST ........................................................................................................................ 493
Mejores prácticas para crear URI’s ................................................................................................. 493
Códigos de respuesta ...................................................................................................................... 494
Migrando a nuestra API REST.......................................................................................................... 495
Implementar los servicios REST ............................................................................................................ 495
Antes de comenzar ......................................................................................................................... 495
Servicio - usernameValidate ........................................................................................................... 496
Servicio - Signup .............................................................................................................................. 500
Autenticación con JSON Web Token (JWT) ..................................................................................... 502
Servicio - Login ................................................................................................................................ 505
Servicio - Relogin ............................................................................................................................. 509
Servicio - Consultar los últimos Tweets .......................................................................................... 512
Servicio - Consultar se usuarios sugeridos ...................................................................................... 516
Servicio – Consulta de perfiles de usuario ...................................................................................... 519
Servicio – Consulta de Tweets por usuario ..................................................................................... 523
Servicio – Actualización del perfil de usuario ................................................................................. 526
Servicio – Consulta de personas que sigo ....................................................................................... 528
Servicio – Consulta de seguidores .................................................................................................. 530
Servicio – Seguir .............................................................................................................................. 532
Servicio – Crear un nuevo Tweet .................................................................................................... 535
Servicio – Like.................................................................................................................................. 539
Servicio – Consultar el detalle de un Tweet .................................................................................... 542
Agregando validaciones a los servicios ................................................................................................ 545
Validando el servicio signin ............................................................................................................. 548
Validando el servicio updateProfile ................................................................................................ 550
Validando el servicio updateProfile ................................................................................................ 551
Documentando el API REST.................................................................................................................. 552
Introducción al motor de plantillas Pug .......................................................................................... 552
API home ......................................................................................................................................... 557
Service catalog ................................................................................................................................ 559
Service documentation ................................................................................................................... 564
Algunas observaciones o mejoras al API.............................................................................................. 567
Aprovisionamiento de imágenes .................................................................................................... 567
Guardar la configuración en base de datos .................................................................................... 569
Documentar el API por base de datos ............................................................................................ 569
Resumen .............................................................................................................................................. 570

Producción ......................................................................................................................................... 571

23 | Página
Producción vs desarrollo ...................................................................................................................... 571
Alta disponibilidad ............................................................................................................................... 572
Cluster ............................................................................................................................................. 572
Puertos ................................................................................................................................................. 577
Comunicación segura........................................................................................................................... 578
Certificados emitidos por autoridades ........................................................................................... 579
Certificados auto firmados .............................................................................................................. 580
Instalando un certificado en nuestro servidor ................................................................................ 581
Habilitar el modo producción .............................................................................................................. 585
Hosting y dominios .............................................................................................................................. 588
Resumen .............................................................................................................................................. 590

CONCLUSIONES .................................................................................................................................. 591

Página | 24
Por dónde empezar
Capítulo 1

Hoy en día existe una gran cantidad de propuestas para desarrollar aplicaciones
web, y cada lenguaje ofrece sus propios frameworks que prometen ser los
mejores, aunque la verdad es que nada de esto está cercas de la realidad, pues
el mejor framework dependerá de lo que buscas construir y la habilidad que ya
tengas sobre un lenguaje determinado.

Algunas de las propuestas más interesantes para el desarrollo web son, Angular,
Laravel, Vue.JS, Ember.js, Polymer, React.js entre un gran número de etcéteras.
Lo que puede complicar la decisión sobre qué lenguaje, librería o framework
debemos utilizar.

Desde luego, en este libro no tratamos de convencerte de utilizar React, sino más
bien, buscamos enseñarte su potencial para que seas tú mismo quien pueda
tomar esa decisión.

Solo como referencia, me gustaría listarte algunas de las empresas que


actualmente utilizan React, para que puedas ver que no se trata de una librería
para hacer solo trabajos caseros o de baja carga:

• Udemy
• Bitbucket
• Anypoint (MuleSoft)
• Facebook
• Courcera
• Airbnb
• American Express
• Atlassian
• Docker
• Dropbox
• Instagram
• Reddit
• Twitter

25 | Página
Son solo una parte de una inmensa lista de empresas y páginas que utilizan React
como parte medular de sus desarrollos. Puedes ver la lista completa de páginas
que usan React aquí: https://github.com/facebook/react/wiki/sites-using-react.
Esta lista debería ser una evidencia tangible de que React es sin duda una librería
madura y probada.

Un dato que debemos de tener en cuenta, es que React es desarrollado por


Facebook, pero no solo eso, sino que es utilizado realmente por ellos, algo muy
diferente con Angular, que, a pesar de ser mantenido por Google, no es utilizando
por ellos para ningún sitio crítico. Esto demuestra la fe que tiene Facebook sobre
React, pero sobre todo garantiza la constante evolución de esta gran librería.

Página | 26
Introducción a React

React es sin duda una de las tecnologías web más revolucionarias de la


actualidad, pues proporciona todo un mecanismo de desarrollo de aplicaciones
totalmente desacoplado del backend y ejecutado 100% del lado del cliente.

React fue lanzado por primera vez en 2013 por Facebook y es actualmente
mantenido por ellos mismo y la comunidad de código abierto, la cual se extiende
alrededor del mundo. React, a diferencia de muchas tecnologías del desarrollo
web, es una librería, lo que lo hace mucho más fácil de implementar en muchos
desarrollos, ya que se encarga exclusivamente de la interface gráfica del
usuario y consume los datos a través de API que por lo general son REST.
El nombre de React proviene de su capacidad de crear interfaces de usuario
reactivas, la cual es la capacidad de una aplicación para actualizar toda la
interface gráfica en cadena, como si se tratara de una formula en Excel, donde
al cambiar el valor de una celda automáticamente actualiza todas las celdas que
depende del valor actualizado y esto se repite con las celdas que a la vez
dependía de estas últimas. De esta misma forma, React reacciona a los cambios
y actualiza en cascada toda la interface gráfica.

Uno de los datos interesantes de React es que, se ejecutado del lado del
cliente (navegador), y no requiere de peticiones GET para cambiar de una
página a otra, pues toda la aplicación es empaquetada en un solo archivo
JavaScript (bundle.js) que es descargado por el cliente cuando entra por primera
vez a la página. De esta forma, la aplicación solo requerirá del backend para
recuperar y actualizar los datos.

Fig 3 - Comunicación cliente-servidor

React suele ser llamado React.js o ReactJS dado que es una librería de
JavaScript, por lo tanto, el archivo descargable tiene la extensión .js, sin
embargo, el nombre real es simplemente React.

27 | Página
Server Side Apps vs Single Page Apps

Para comprender como trabaja React es necesario entender dos conceptos


claves, los cuales son Server side app (Aplicaciones del lado del servidor) y Single
page app (Aplicaciones de una sola página)

Server side app

Las aplicaciones del lado del servidor, son aquellas en las que el código fuente
de la aplicación está en un servidor y cuando un cliente accede a la aplicación, el
servidor solo le manda el HTML de la página a mostrar en pantalla, de esta
manera, cada vez que el usuario navega hacia una nueva sección de la página,
el navegador lanza una petición GET al servidor y este le regresa la nueva página.

Esto implica que cada vez que el usuario de click en una sección, se tendrá que
comunicar con el servidor para que le regresa la nueva página, creado N
solicitudes GET para N cambios de página. En una página del lado del servidor,
cada petición retorna tanto el HTML para mostrar la página, como los datos que
va a mostrar.

Fig 4 - Server side apps Architecture

Como vemos en la imagen, el cliente lanza un GET para obtener la nueva página,
el servidor tiene que hacer un procesamiento para generar la nueva página y
tiene que ir a la base de datos para obtener la información asociada a la página
de respuesta. La nueva página es enviada al cliente y este solo la muestra en
pantalla. En esta arquitectura todo el trabajo lo hace el servidor y el cliente
solo se limita a mostrar las páginas que el server le envía.

Página | 28
Single page app

Las aplicaciones de una sola página se diferencian de las aplicaciones del lado del
servidor debido a que, gran parte del procesamiento y la generación de las
vistas las realiza directamente el cliente (navegador). Por otro lado, el
servidor solo expone un API mediante el cual, la aplicación puede consumir datos
y realizar operaciones transaccionales.

En este tipo de arquitectura, se libera al servidor de una gran carga, pues no


requiere tener que estar generando vistas para todos los usuarios conectados ni
mantener en memoria todos los datos asociados a la sesión.

Fig 5 - Single page apps Architecture.

Como vemos en esta nueva imagen, el cliente es el encargado de realizar


las vistas y realizar algunos procesamientos, mientras que el servidor solo
expone un API para acceder a los datos.

React es Single page app

Es muy importante resalta que React sigue la


arquitectura de Single page app, por lo que las vistas
son generadas del lado del cliente y solo se comunica
al backend para obtener y guardar datos.

Si bien, React funciona originalmente como SPA, existe técnicas para realizar el
renderizado del lado del servidor, como es el caso de NextJS, un poderos
framework que permite el renderizado del lado del servidor, sin embargo, esto
querá fuera del alcance de este libro.

29 | Página
Introducción a NodeJS

NodeJS es sin duda una de las tecnologías que más rápido está creciendo, y que
ya hoy en día es indispensable para cubrir posiciones de trabajo. NodeJS ha sido
revolucionario en todos los aspectos, desde la forma de trabajar hasta que
ejecuta JavaScript del lado del servidor.

NodeJS es básicamente un entorno de ejecución JavaScript del lado del


servidor. Puede sonar extraño, ¿JavaScript del lado del servidor? ¿Pero que no
JavaScript se ejecuta en el navegador del lado del cliente? Como ya lo platicamos,
JavaScript nace inicialmente en la década de los noventas por los desarrolladores
de Netscape, el cual fue creado para resolver problemas simples como validación
de formularios, alertas y alguna que otra pequeña lógica de programación, nada
complicado, sin embargo, JavaScript ha venido evolucionando hasta convertirse
en un lenguaje de programación completo. NodeJS es creado por Ryan Lienhart
Dahl, quien tuvo la gran idea de tomar el motor de JavaScript de Google Chrome
llamado V8, y montarlo como el Core de NodeJS.

Fig. 1 - Arquitectura de NodeJS

Como puedes ver en la imagen, NodeJS es en realidad el motor de JavaScript V8


con una capa superior de librerías de NodeJS, las cuales se encargan de la
comunicación entre el API de NodeJS y el Motor V8. Adicionalmente, se apoya de
la librería Libuv la cual es utilizada para el procesamiento de entradas y salidas
asíncronas.

Cabe mencionar que NodeJS es Open Source y multiplataforma, lo que le ha


ayudado a ganar terrenos rápidamente.

Que es NodeJS (Según su creador):

Node.js® es un entorno de ejecución para JavaScript construido con el motor de


JavaScript V8 de Chrome. Node.js usa un modelo de operaciones E/S sin bloqueo

Página | 30
y orientado a eventos, que lo hace liviano y eficiente. El ecosistema de paquetes
de Node.js, npm, es el ecosistema más grande de librerías de código abierto en
el mundo.

NodeJS y la arquitectura de Micro Servicios

Cuando hablamos de NodeJS es imposible no hablar de la arquitectura de


Microservicios, ya que es una de las arquitecturas más utilizadas y que están de
moda en la actualidad. La cual consiste en separar una aplicación en pequeños
módulos que corren en contenedores totalmente aislados, en lugar de tener
aplicaciones monolíticas que conglomeran toda la funcionalidad un mismo
proceso.

Fig 6 - Arquitectura monolítica vs Microservices

En una arquitectura monolítica todas las aplicaciones escalan de forma


monolítica, es decir, todos los módulos son replicados en cada nodo, aunque no
se requiera de todo ese poder de procesamiento para un determinado módulo,
en cambio, en una arquitectura de microservicios, los módulos se escalan a como
sea requerido.

Todo esto viene al caso, debido a que NodeJS se ha convertido en unos de los
servidores por excelencia para implementar microservicios, ya que es muy ligero
y puede ser montado en servidores virtuales con muy pocos recursos, algo que
es imposible con servidores de aplicaciones tradiciones como Wildfy, Websphere,
Glashfish, IIS, etc.

31 | Página
Hoy en día es posible rentar un servidor virtual por 5 USD al mes con 1GB de
RAM y montar una aplicación con NodeJS, algo realmente increíble y es por eso
mi insistencia en que NodeJS es una de las tecnologías más prometedoras
actualmente. Por ejemplo, yo suelo utilizar Digital Ocean, pues me permite rentar
servidores desde 5 usd al mes.

Introducción a MongoDB

MongoDB es la base de datos NoSQL líder del marcado, ya que ha demostrado


ser lo bastante madura para dar vida a aplicaciones completas. MongoDB nace
con la idea de crear aplicaciones agiles, que permita realizar cambios a los
modelos de datos si realizar grandes cambios en la aplicación. Esto es gracias a
que permite almacenar su información en documentos en formato BSon (Binary
JSON), el cual es básicamente un JSON enriquecido con una mayor cantidad de
tipo de datos.

Además, MongoDB es escalable, tiene buen rendimiento y permite alta


disponibilidad, escalando de un servidor único a arquitecturas complejas de
centros de datos. MongoDB es mucho más rápido de lo que podrías imaginas,
pues potencia la computación en memoria.

Uno de los principales retos al trabajar con MongoDB es entender cómo funciona
el paradigma NoSQL y abrir la mente para dejar a un lado las tablas y las
columnas, para pasar un nuevo modelo de datos de colecciones y documentos,
los cuales no son más que estructuras de datos en formato JSON.

Actualmente existe una satanización contra MongoDB y de todas las bases de


datos NoSQL en general, ya que los programadores habituales temen salir de su
zona de confort y experimentar con nuevos paradigmas. Esta satanización se
debe en parte a dos grandes causas: el desconocimiento y la incorrecta
implementación. Me explico, el desconocimiento se debe a que los
programadores no logran salir del concepto de tablas y columnas, por lo que no
encuentra la forma de realizar las operaciones que normalmente hacen con una
base de datos tradicional, como es hacer un Join o crear procedimientos
almacenados. Y Finalmente, la incorrecta implementación se debe a que los
programadores inexpertos o ignorantes utilizan MongoDB para solucionar
problemas para los que no fue diseñado, llevando el proyecto directo al fracaso.

Bases de datos Relacionales VS MongoDB

Probablemente lo que más nos cueste entender cuando trabajamos con


MongoDB, es que no utiliza tablas ni columnas, y en su lugar, la información se
almacena en objetos completos en formato BSON, a los que llamamos
documentos. Un documento se utiliza para representar mucha información

Página | 32
contenida en un solo objeto, que, en una base de datos relacional, probablemente
guardaríamos en más de una tabla. Un documento MongoDB suele ser un objeto
muy grande, que se asemejan a un árbol, y dicho árbol suele tener varios niveles
de profundidad, debido a esto, MongoDB NO requiere de realizar joins para
armar toda la información, pues un documento por sí solo, contiene toda la
información requerida, en pocas palabras, MongoDB permite grabar objetos
completos con todo y sus relaciones.

Otros de los aspectos importantes de utilizar documentos es que, no existe una


estructura rígida que nos obligue a crear objetos de una determinada estructura,
a diferencia una base de datos SQL, en la cual, las columnas determinan la
estructura de la información. En MongoDB no existe el concepto de columnas,
por lo que los dos siguientes objetos podrían ser guardados sin restricción alguna:

1. { 6. {
2. "name": "Juan Perez", 7. "name": "Juan Perez",
3. "age": 20, 8. "age": 20,
4. "tel": "1234567890" 9. "tels": [
5. } 10. "1234567890",
11. "0987654321"
12. ]
13. }

Observemos que los dos objetos son relativamente similares, y tiene los mismos
campos, sin embargo, uno tiene un campo para el teléfono, mientras que el
segundo objeto, tiene una lista de teléfonos. Es evidente que aquí tenemos dos
incongruencias con un modelo de bases de datos relacional, el primero, es que
tenemos campos diferentes para el teléfono, ya que uno se llama tel y el otro
tels. La segunda incongruencia es que, la propiedad tels del segundo objeto es
en realidad un arreglo, lo cual en una DB relacional, sería una tabla secundaria
unida con un Foreign Key. El segundo objeto se vería de la siguiente manera
modelado en una DB relacional:

Fig 7 - Modelo relacional (SQL)

Como ya nos podemos dar una idea, en un DB relacional, es necesario tener


estructuras definidas y relacionadas entre sí, mientras que en MongoDB, se
guardan documentos completos, los cuales contiene en sí mismo todas las
relaciones requeridas. Puede sonar algo loco todo esto, pero cuando entremos a
desarrollar nuestra API REST veremos cómo utilizar correctamente MongoDB.

33 | Página
La relación entre React, NodeJs & MongoDB

Hasta este momento, ya hemos hablado de estas tres tecnologías de forma


individual, sin embargo, no hemos analizado como es que estas se combinan
para crear proyectos profesionales.

Resumiendo un poco lo ya platicado, React se encargará de la interfaz


gráfica de usuario (UI) y será la encargada de solicitar información al Backend
a medida que el usuario navega por la página. Por otra parte, tenemos NodeJS,
al cual utilizaremos para programar la lógica de negocio y la comunicación con
la base de datos, mediante NodeJS expondremos un API REST que luego React
consumirá. Finalmente, MongoDB es la base de datos en donde almacenamos
la información.

Fig 8 - MERN Stack

Como vemos en la imagen, React está del lado del FrontEnd, lo que significa que
su único rol es la representación de los datos y la apariencia gráfica. En el
BackEnd tenemos a NodeJS, quien es el intermediario entre React y MongoDB.
MongoDB también está en el Backend, pero este no suele ser accedido de forma
directa por temas de seguridad.

Cuando una serie de tecnologías es utilizada en conjunto, se le llama Stack, el


cual hace referencia a todas las tecnologías implementadas. A lo largo de este
libro, utilizaremos el Stack MERN, el cual es el acrónimo de MongoDB, Express,
React & NodeJS, aunque estas tecnologías suelen acompañarse con Webpack y
Redux.

Página | 34
Resumen

En este capítulo hemos dado un vistazo rápido a la historia de JavaScript y como


es que ha evolucionado hasta convertirse en un lenguaje de programación
completo. De la misma forma, hemos analizado como es que JavaScript es la
base para tecnologías tan importantes como React y NodeJS.

También hemos analizado de forma rápida a React, NodeJS y MongoDB para


dejar claro los conceptos y como es que estas 3 tecnologías se acoplan para dar
soluciones de software robustas.

Por ahora no te preocupes si algún concepto no quedo claro o no entiendes como


es que estas 3 tecnologías se combinan, ya que más adelante entraremos mucho
más a detalle.

35 | Página
Preparando el ambiente de
desarrollo
Capítulo 2

Este capítulo lo reservaremos exclusivamente para la instalación de todas las


herramientas que utilizaremos a lo largo de este libro. También lo
aprovecharemos para analizar algunas de sus características y crearemos
nuestro primer proyecto con React.

Instalando Visual Studio Code

Visual Studio Code es un editor de código Open Source lanzado por Microsoft en
abril del 2015, como una estrategia para competir contra los principales editores
de código que existen en el mercado, como es el caso de Atom, Brackets, Sublime
Text, entre otros.

Es importante notar la diferencia entre Visual Studio y Visual Studio Code, los
cuales tienen una diferencia enorme, por un lado, Visual Studio es un IDE de
paga con licenciamiento privado (también tiene una versión community) y que
fue desarrollado para dar soporte principalmente a las tecnologías Microsoft, por
otro lado, Visual Studio Code es un editor de código minimalista, Open Source y
multipropósito que se ha popularizado entre los desarrolladores web.

Cabe resaltar que, Visual Studio Code no es un IDE como tal, sino más bien, un
editor de código, lo cual lo hace una herramienta robusta pero no tan sofisticada
como un IDE de programación completo, como sería Webstorm, Eclipse o Visual
Studio. En este libro nos hemos inclinado por Visual Studio Code, debido a que
es una herramienta ampliamente utilizada y es open source, lo que te permitirá
descargarlo e instalarlo sin pagar una licencia. Sin embargo, si tú te sientes
cómodo en otro editor o IDE, eres libre de utilizarlo.

Página | 36
Instalación de Visual Studio Code

Instalar Visual Studio Code es tan simple como descargarlo de la página oficial.
Y existe una versión compatible para Windows, Linux y Mac, por lo que no
deberías de tener problemas con tu sistema operativo favorito.

La instalación tan simple, como seguir los clásicos pasos de siguiente, siguiente
y finalizar, por lo que no te aburriré haciendo un tutorial de como instalarlo.

En las siguientes ligas encontrarás el procedimiento de instalación actualizado


por sistema operativo:

• Windows: https://code.visualstudio.com/docs/setup/windows
• Mac: https://code.visualstudio.com/docs/setup/mac
• Linux: https://code.visualstudio.com/docs/setup/linux

Fig 9 - Vista previa de Visual Studio Code

Una vez finalizada la instalación, aparecerá el ícono de Visual Studio Code en el


escritorio o lanzadores. Lo ejecutamos y nos deberá aparecer una pantalla como
la siguiente:

37 | Página
Fig 10 - Pantalla de bienvenida de Visual Studio Code

Instalar PlugIns

Una de las principales ventajas de utilizar Visual Studio Code es la gran cantidad
de PlugIns disponibles por la comunidad, las cuales van desde soportar nuevos
lenguajes, hasta formatear el código. Es realmente sorprendente la gran cantidad
de plugins que nos podemos encontrar para hacer prácticamente cualquier cosa
que se nos ocurra.

Si bien no es necesario ningún PlugIn para desarrollar con React, NodeJS o


MongoDB, sí que hay algunos que nos ayudarán a trabajar mejor, los cuales
procederemos a instalar, para esto, nos dirigiremos a View > Extensions.

Fig 11 - Visual Studio Code

Página | 38
Ya en esta nueva pantalla utilizaremos la barra de búsqueda que nos sale del
lado izquierdo para buscar los siguientes plugins:

• Import Cost: Permite visualizar cuánto cuesta el espacio cada import


que realicemos en React, lo cual nos permite crear bundles más
pequeños.
• Sass/Less/Typescript/jade/Pug Compile Hero: Permite dar
formato a diferentes tipos de archivos, entre ellos Pug, el motor de
plantillas que utilizaremos con NodeJS.

Si todo salió bien, deberás de poder ver los plugins de la siguiente manera:

Fig. 2 - Atom plugins

Estos son los dos plugins que, recomiendo para empezar, pero puedes navegar
un poco para ver toda la gran lista de plugins disponibles, y la gran mayoría
escritos por contribuidores de código libre.

Instalando NodeJS & npm

Instalar NodeJS es también realmente simple, pues tan solo será necesario
descargarlo de su página oficial https://nodejs.org, asegurándonos de tomar la
versión correcta para nuestro sistema operativo. En la siguiente página
encontraras la guía de instalación para los diferentes sistemas operativos:
https://nodejs.org/es/download/package-manager/

Una vez descargado, nuevamente seguimos el procedimiento habitual, siguiente,


siguiente, finalizar. Con esto, habremos instalado el Runtime de NodeJS y el
Gestor de Paquetes de Node o NPM por sus siglas en inglés.

39 | Página
Para asegurarnos de que todo salió bien, deberemos entrar a la terminal de
comandos y ejecutar el comando “node -v” y “npm -v”, esto nos deberá arrojar
la versión de NodeJS instalada.

Fig 12 - Comprobando la instalación de NodeJS.

Esto será todo lo que tendremos que hacer por el momento con NodeJS. Más
adelante veremos cómo ejecutarlo y descargar módulos con NPM.

Creando mi primer proyecto de React

Estas es sin duda la sección más esperada para todos, pues por fin empezaremos
a programar con React. Puede que de momento hagamos algo muy simple, pero
a medida que avancemos en el libro iremos avanzando en un proyecto final, el
cual, contemplará todo el conocimiento de este libro. Entonces, sin más
preámbulos, comencemos.

Estructura base de un proyecto

Existen básicamente dos formas para crear un proyecto en React, crear


manualmente todos los archivos necesarios o utilizar utilerías que nos ayudan a
inicializar el proyecto de forma automática.

Puede que resulte obvio que la mejor forma es mediante las utilerías, pero como
en este punto queremos aprender a utilizar React, entonces tendremos que
iniciar de la forma difícil, es decir, crea a mano cada archivo del proyecto.

Página | 40
Creación un proyecto paso a paso

Los que viene de trabajar de entornos de IDE’s, es muy probable que estén
acostumbrados a crear proyectos mediante Wizzards, los cuales ya nos crean
todo el proyecto y sus archivos, pero en esta sección aprenderemos a crearlo de
forma manual.

Lo primero que tendremos que hace es crear una carpeta sobre la que estaremos
trabajando, puede estar en cualquier dirección del disco duro, en este caso,
crearemos una carpeta llamada TwitterApp y nos ubicaremos en esta carpeta por
medio de la consola:

Fig 13 - Creando el directorio del proyecto

En mi caso, yo estoy creando mi proyecto en el Path


C://Libros/React/TwitterApp y me dirijo a la carpeta mediante el comando cd.

Una vez ubicados en la carpeta, deberemos ejecutar el comando, npm init, lo


que iniciará un proceso de inicialización de un proyecto, en el cual nos pedirá los
siguientes datos:

41 | Página
• name: nombre del proyecto, no permite camel case, por lo que tendrá
que ser todo en minúsculas. En este caso ponemos twitter-app.
• version: versión actual del proyecto, por default es 1.0.0, por lo que
solo presionamos enter sin escribir nada.
• description: una breve descripción del proyecto, en nuestro caso
podemos poner Aplicación de redes sociales, o cualquiera otra
descripción, al final, es meramente descriptiva.
• entry point: indica el archivo principal del proyecto, en nuestro caso no
nos servirá de nada, así que presionamos enter para tomar el valore por
default.
• test command: nos permite definir un comando de prueba,
generalmente se imprime algo en pantalla para ver que todo anda bien.
En nuestro caso, solo presionamos enter y dejamos el valor por default.
• git repository: si nuestro proyecto está asociado a un repositorio de git,
aquí podemos poner la URL, por el momento, solo presionamos enter.
• keywords: como su nombre lo dice, palabras clave que describen el
proyecto. Nuevamente tomamos el valor por defecto.
• author: permite establecer el nombre del autor del proyecto, puede ser
el nombre de una persona o empresa. En mi caso pongo mi nombre
Oscar Blancarte, pero tú puedes poner tu nombre.
• license: se utiliza en proyectos que tienen una determinada licencia,
para prevenir a los que utilicen el código de tu proyecto. En nuestro
caso, dejamos los valores por defecto.

Finalmente, el asistente nos arrojara un resumen de los datos capturados, así


como una vista previa del archivo package.json, el cual analizaremos más
adelante. Terminamos el asistente capturando yes.

Página | 42
Fig 14 - Inicialización del proyecto

Una vez finalizado todos los pasos, podremos ver que, en la carpeta del proyecto,
se habrá creado un nuevo archivo llamado package.json, el cual se verá de la
siguiente manera:

1. {
2. "name": "twitter-app",
3. "version": "1.0.0",
4. "description": "Aplicación de redes sociales",
5. "main": "index.js",
6. "scripts": {
7. "test": "echo \"Error: no test specified\" && exit 1"
8. },
9. "author": "Oscar Blancarte",
10. "license": "ISC"
11. }

Como puedes ver, el contenido del archivo, es exactamente lo que escribimos


por consola, por lo que tu podrías haber creado a mano el archivo y el resultado
hubiera sido el mismo.

El archivo package.json es muy importante, ya que desde aquí se administrarán


las dependencias del proyecto. Pero tampoco es un archivo muy complejo al que
debamos prestar especial atención, al menos por el momento.

43 | Página
Index.html

El siguiente paso será crear una página de inicio, la cual por lo general solo
importa un script de JavaScript y una hoja de estilos. Este archivo lo llamaremos
index.html y lo crearemos en la raíz del proyecto, el cual se verá de la siguiente
manera:

1. <!DOCTYPE html>
2. <html>
3. <head>
4. <title>Mini Twitter</title>
5. <link rel="stylesheet" href="/public/resources/css/styles.css">
6. </head>
7. <body>
8. <div id="root"></div>
9. <script type="text/javascript" src="/public/bundle.js"></script>
10. </body>
11. </html>

Un dato curioso de React, es que este será la única página HTML que tendremos
en todo el proyecto, pues como ya lo hablamos, las aplicaciones en React se
empaquetan en un solo archivo JavaScript, que denominaremos bundle.js.
Cuando el usuario accede a la página, iniciará la descarga del Script, una vez
descargado se ejecutará y remplazará el div con id=root, por la aplicación
contenida en bundle.js.

webpack.config.js

El siguiente paso será crear el archivo de configuración de Webpack. Este archivo


lo analizaremos más adelante, pero por lo pronto, lo crearemos en la raíz del
proyecto con el nombre webpack.config.js y nos limitaremos a copiar el archivo
para que quede de la siguiente manera:

1. module.exports = {
2. mode: "development", //development
3. entry: [
4. __dirname + "/app/App.js",
5. ],
6. output: {
7. path: __dirname + "/public",
8. filename: "bundle.js",
9. publicPath: "/public"
10. },
11. module: {
12. rules: [{
13. test: /\.jsx?$/,
14. exclude: [/node_modules/],
15. loader: 'babel-loader',
16. options: {
17. presets: ["@babel/preset-env", "@babel/preset-react"],
18. plugins: [
19. "@babel/plugin-proposal-class-properties",
20. "@babel/plugin-proposal-export-default-from",
21. "react-hot-loader/babel"
22. ]
23. }
24. }]
25. }

Página | 44
26. };

Recuerda que no importa si no entiendas nada en este archivo, más adelante lo


retomaremos en la sección de Webpack.

Dependencias del proyecto

Ahora regresaremos al archivo package.json y agregaremos las dependencias


necesarias para poder ejecutar Webpack y React. Nuevamente, no te preocupes
por entender esta parte, ya que las dependencias las analizaremos más adelante,
en este mismo capítulo.

1. {
2. "name": "twitter-app",
3. "version": "1.0.0",
4. "description": "Aplicación de redes sociales",
5. "main": "index.js",
6. "scripts": {
7. "test": "echo \"Error: no test specified\" && exit 1"
8. },
9. "author": "Oscar Blancarte",
10. "license": "ISC",
11. "devDependencies": {
12. "@babel/cli": "^7.8.4",
13. "@babel/core": "^7.9.0",
14. "@babel/plugin-proposal-class-properties": "^7.8.3",
15. "@babel/plugin-proposal-export-default-from": "^7.8.3",
16. "@babel/preset-env": "^7.9.0",
17. "@babel/preset-react": "^7.9.4",
18. "babel-core": "^6.26.3",
19. "babel-eslint": "^10.1.0",
20. "babel-jest": "^25.2.6",
21. "babel-loader": "^8.1.0",
22. "react-hot-loader": "^4.12.20",
23. "webpack": "^4.42.1",
24. "webpack-cli": "^3.3.11",
25. "webpack-dev-middleware": "^1.10.2",
26. "webpack-dev-server": "^3.10.3"
27. },
28. "dependencies": {
29. "core-js": "^3.6.4",
30. "react": "^16.13.1",
31. "react-dom": "^16.13.1"
32. }
33. }

Para finalizar la configuración del archivo package.json, deberemos agregar las


secciones devDependencies y dependencies, los cuales son secciones especiales
para referenciar las dependencias del proyecto.

Una vez aplicados estos últimos cambios, será necesario ejecutar le comando npm
install sobre la carpeta del proyecto para descargar las dependencias.

45 | Página
Fig 15 - Instalando dependencias con npm install

Instalar actualizaciones

Cada vez que se agregue o modifique una dependencia


del archivo package.json, será necesario instalar los
nuevos módulos mediante el comando npm install, el
cual se deberá ejecutar en la raíz del proyecto. De lo
contrario, los módulos no estarán disponibles en
tiempo de ejecución.

styles.css

Dado que todas las páginas web deben de verse atractivas, es necesario crear al
menos un archivo de estilos, en el cual iremos declarando las clases de estilo que
utilizaremos a lo largo del libro. Para esto, será necesario crear la siguiente
estructura de carpetas iniciando desde la raíz del proyecto.
/public/resources/css, es importante respetar correctamente el path, ya que de
lo contrario, nuestra página no cargara los estilos. Dentro de la carpeta css,
crearemos un archivo llamado styles.css, el cual se verá de la siguiente manera:

1. body{
2. background-color: #F5F8FA;
3. }

Por el momento, solo estableceremos el color de fondo del body en un gris suave,
y más adelante iremos complementando los estilos.

Observemos que el path del archivo de estilos, corresponde con el path definido
en el archivo index.html

1. <link rel="stylesheet" href="/public/resources/css/styles.css">

Un punto importante, es que podemos cambiar el path del archivo styles.css


dentro del proyecto, sin embargo, hay dos puntos a considerar, el archivo debe

Página | 46
de estar dentro de la carpeta public, pues hemos configurado webpack para
exponer los archivos en ese path, lo segundo a considerar, es que debemos
actualizar el archivo index.html para apuntar a la nueva URL.

App.js

Ha llegado el momento de entrar en React. App,js es el archivo principal del


proyecto, pues es el punto de entrada a la aplicación. De tal forma, que cuando
el usuario acceda a la página, este será el archivo que se ejecute.

Lo primero que debemos de hacer es crear una carpeta llamada app en la raíz del
proyecto. Dentro de esta carpeta, crearemos el archivo App.js el cual se verá de
la siguiente manera:

1. import React from 'react'


2. import { render } from 'react-dom'
3.
4. class App extends React.Component{
5.
6. render(){
7. return(
8. <h1>Hello World</h1>
9. )
10. }
11. }
12. render(<App/>, document.getElementById('root'));

Por ahora no entraremos en los detalles de React, pues el objetivo de este


capítulo es crear nuestra primera aplicación funcional. Sin embargo, nos vamos
a adelantar un poco para tratar de entender que está pasando.

En las primeras líneas del archivo, importamos las librerías de React (Líneas 1 y
2), por un lado, importamos React del módulo ‘react’ y después importamos la
función render del módulo ‘react-dom’.

Lo que sigues, es la declaración de una nueva clase llamada App, la cual extiende
de React.Component. La clase App tiene un método llamado render, el cual es el
encargado de generar la vista del componente, en este caso, está retornando el
elemento <h1>Hello Word</h1>. Finalmente, utilizamos la función render, para
remplazar el elemento root por el nuevo componente.

NOTA: recordemos que el elemento root está definido en el archivo index.html,


y su única función es servir como punto de montaje para React.

Configurando Script de ejecución

Ya estamos casi listo para ejecutar nuestra primera aplicación con React, solo
nos queda un paso más. Regresamos al archivo package.json y agregamos la
sección scripts, tal como lo vemos a continuación:

47 | Página
1. {
2. "name": "twitter-app",
3. "version": "1.0.0",
4. "description": "Aplicación de redes sociales",
5. "main": "index.js",
6. "scripts": {
7. "start": "node_modules/.bin/webpack-dev-server --progress"
8. },
9. "author": "Oscar Blancarte",
10. "license": "ISC",
11. "devDependencies": {
12. "@babel/cli": "^7.8.4",
13. "@babel/core": "^7.9.0",
14. "@babel/plugin-proposal-class-properties": "^7.8.3",
15. "@babel/plugin-proposal-export-default-from": "^7.8.3",
16. "@babel/preset-env": "^7.9.0",
17. "@babel/preset-react": "^7.9.4",
18. "babel-core": "^6.26.3",
19. "babel-eslint": "^10.1.0",
20. "babel-jest": "^25.2.6",
21. "babel-loader": "^8.1.0",
22. "react-hot-loader": "^4.12.20",
23. "webpack": "^4.42.1",
24. "webpack-cli": "^3.3.11",
25. "webpack-dev-middleware": "^1.10.2",
26. "webpack-dev-server": "^3.10.3"
27. },
28. "dependencies": {
29. "core-js": "^3.6.4",
30. "react": "^16.13.1",
31. "react-dom": "^16.13.1"
32. }
33. }

La sección Script nos permitirá definir una serie de comandos pre definidos, para
compilar, ejecutar pruebas, empaquetar y ejecutar la aplicación. Estos scripts
son ejecutados con ayuda de npm.

Hello Word!!

Finalmente ha llegado el momento de ejecutar nuestra primera aplicación con


React, por lo que nos dirigiremos a la consola y ejecutaremos el comando npm
start, el cual corresponde con el nombre del script definido en package.json.

Fig 16 - Ejecutando nuestra primera aplicación.

Página | 48
La ejecución de este comando lanza una gran cantidad de texto en la consola,
pero ahora nos centraremos en ver el mensaje “webpack: Compiled
successfully”. Si vemos este mensaje, es que todo salió bien y la aplicación ya
debería de esta disponible en la URL http://localhost:8080/.

Fig 17 - Hello World con React

Si al entrar a la URL puedes ver “Helllo World” quieres decir que has hecho
perfectamente bien todos los pasos, de lo contrario, será necesario que regreses
a los pasos anteriores y análisis donde está el problema. Muchas veces una coma,
un punto o una letra de más puede hacer que no funcione, así que no te
desanimes, ya que es raro que a alguien le salga bien a la primera. Recuerda que
puedes bajar la versión del repositorio para comparar tu código.

Estructura del proyecto:

Tras haber realizados todos los pasos, el proyecto debería de quedar con la
siguiente estructura:

Fig 18 - Estructura de un proyecto React

NOTA: es posible que adicional veas la carpeta node_modules, pero no te


preocupes por ella en este momento, simplemente deja como esta y más
adelante la analizaremos.

49 | Página
Creación del proyecto con utilerías

Como verás, configurar un proyecto a mano, puede ser bastante complicado,


sobre todo, si es que no conoces todos los pasos y archivos necesarios para el
correcto funcionamiento. Es por esta razón que la comunidad desarrollo una
utilidad que nos ayuda a crear proyectos mucho más rápido, mediante unos
simples comandos de npm.

Esta utilidad es un módulo de npm llamado create-react-app. Para utilizarla


tendremos que instalarla utilizando el siguiente comando, npx create-react-app
twitter-app.

Fig 19 - Instalando create-react-app

Tras finalizar el comando, veremos un resultado como el anterior, y ya solo


restará acceder a la carpeta del proyecto cd twitter-app y ejecutar el comando
npm start.

Fig 20 - Hello word con create-react-app

En este caso, la aplicación se iniciará en el puerto 3000, por lo que la URL será
http://localhost:3000/.

Página | 50
La estructura del proyecto se vería de la siguiente manera:

Fig 21 - Estructura de un proyecto con create-react-app

Esta alternativa es muy buena si solo vas a crear una aplicación sin el BackEnd,
pero si ya requerimos utilizar NodeJS + Express, entonces sería bueno crear el
proyecto paso a paso.

En este libro trabajaremos con la estructura del proyecto paso a paso, ya que
necesitaremos más control sobre el proyecto y para eso utilizaremos Webpack.

Descargar el proyecto desde el repositorio

Si has tenido problemas creando el proyecto de React y quieres avanzar más


rápido, puedes simplemente descargar el proyecto ya listo para ser ejecutado
desde el repositorio del libro.

https://github.com/oscarjb1/books-reactiveprogramming.git

Y navegamos al branch “Capitulo-02-Preparando_ambiente_desarrollo” del


repositorio.

51 | Página
Gestión de dependencias con npm

En este punto ya hemos configurado un proyecto React con todo y sus


dependencias, pero hasta el momento no nos habíamos detenido a analizar cómo
es que npm administra las dependencias, y este es precisamente el objetivo de
esta sección.

Primero que nada, npm es la abreviatura de Node Package Manager o


Administrador de paquetes de Node, el cual sirve precisamente para gestionar
los paquetes o módulos en NodeJS. npm nace como una necesidad de
estandarizar la forma en que los desarrolladores gestionaban las dependencias,
pues antes de npm había herramientas como bower, browserify, gulp, grunt, etc.
que, entre sus funciones, estaba la de gestionar paquetes, lo que provocaba que
cada proyecto, y según su desarrollador, configurara de forma diferente las
dependencias. Esto desde luego provoca el gran problema de que no existe un
repositorio central, en el cual, se almacenen todos los módulos. Npm, además de
ser una herramienta, proporciona un repositorio en la nube para que cualquier
persona puede subir sus módulos y hacerlos disponibles para toda la comunidad.
Puedes dar un vistazo a https://www.npmjs.com/ para obtener más información.

Todas las dependencias de npm se almacenan en el archivo package.json, el cual


ya tuvimos la oportunidad de revisar. Sin este archivo, npm no reconocerá el
proyecto y no podremos agregar dependencias.

Inicializar un proyecto con npm

Recuerda que puedes utilizar el comando npm init


para inicializar un proyecto con npm, el cual creará el
archivo package.json.

Con npm es posible agregar 3 tipos de librerías, las cuales utilizaremos en


diferentes contextos, los tipos son:

1. Librerías locales: son librerías indispensables para la ejecución de un


proyecto, sin las cuales sería imposible ejecutar la aplicación. Estas están
disponibles únicamente para el proyecto al que fueron definidas
2. Librerías globales: son librerías que se instalan a nivel global, es decir,
no se requiere declarar la librería en cada proyecto, sino que es accesible
para todos los proyectos sin necesidad de importarlas.
3. Librerías de desarrollo: son librerías que se utilizan solamente en la
fase de desarrollo, las cuales son utilizadas para compilar, minificar o
comprimir archivos, también proporcionan utilidades en tiempo de
ejecución para debuger y muchas cosas más.

Página | 52
Instalando librerías locales

Solo será necesario introducir el comando:

npm install --save <module_name>

El comando install lo utilizaremos en los 3 tipos de dependencias y solo para


indicar que queremos instalar una dependencia. --save le dice a npm que
queremos la librería se guarde en el archivo package.json, si no lo ponemos, la
librería si se descargará, pero no quedará registra en archivo. <module_name> es
el nombre del módulo, se divide en dos secciones <name>@<version> name es el
nombre del módulo y versión, la versión del módulo que requerimos, en caso
de no declarar la versión, se instalará la última versión estable.

Cuando una dependencia es instalada con el parámetro --save, esta se


almacenará en la sección "dependencies" del archivo package.json.

Instalando librerías globales

Instalar librerías globales es exactamente lo mismo que las locales, con la


diferencia de que debemos de agregar el parámetro -g y el parámetro --save no
es requerido.

npm install -g <module_name>

Como ya lo hablamos, las librerías globales están disponibles para todos los
proyectos si la necesidad de instarlos en cada proyecto.

Un ejemplo de esta librería es la de create-react-app que utilizamos para crear


un proyecto de forma automática con npm, si recordamos el comando create-
react-app twitter-app, veremos que no requerimos poner npm, ya que creaete-
creact-app se ha convertido en un comando a nivel global.

Librerías globales

Hay que tener cuidado con las librerías que instalamos


de forma global, pues podrían entrar en conflicto con
las librerías locales. Por lo que solo hay que instalar
de forma global las que tienen un motivo justificable.

Instalando librerías de desarrollo

El tercer tipo de librerías son las de desarrollo, las cuales son utilizadas para la
fase de desarrollo. La importancia de separar estas las librerías de desarrollo, es

53 | Página
no llevárnoslas a un ambiente de producción, pues puede hacer mucho más
pesada la página de lo que debería.

La instalación es muy similar a las locales, con la diferencia que le agregamos -


D como parámetro. La otra diferencia es que las dependencias se guardan en la
sección “devDependencies” del archivo package.json.

npm install --save -D <module_name>

Librerías de desarrollo

Es muy importante identificar que librerías será


utilizadas únicamente durante el desarrollo, pues
exportarlas a producción puede dar problemas de
rendimiento, además de una experiencia desagradable
para el usuario, pues tendrá que descargar un archivo
mucho más grande.

Otra forma de ver las librerías de desarrollo es, verlas como librerías del Backend,
es decir, son librerías que seguramente utilizará node para ejecutar el API o
transpilar el código, pero no se llevarán al archivo bundle.js.

Análisis de las dependencias actuales

Una vez que ya hemos visto como npm gestiona las dependencias, ha llegado el
momento de retomar el archivo package.json para analizar las dependencias que
ya hemos instalado. Empecemos con las dependencias de desarrollo:

1. "devDependencies": {
2. "@babel/cli": "^7.8.4",
3. "@babel/core": "^7.9.0",
4. "@babel/plugin-proposal-class-properties": "^7.8.3",
5. "@babel/plugin-proposal-export-default-from": "^7.8.3",
6. "@babel/preset-env": "^7.9.0",
7. "@babel/preset-react": "^7.9.4",
8. "babel-core": "^6.26.3",
9. "babel-eslint": "^10.1.0",
10. "babel-jest": "^25.2.6",
11. "babel-loader": "^8.1.0",
12. "react-hot-loader": "^4.12.20",
13. "webpack": "^4.42.1",
14. "webpack-cli": "^3.3.11",
15. "webpack-dev-middleware": "^1.10.2",
16. "webpack-dev-server": "^3.10.3"
17. }

Página | 54
• Babel: Todas las librerías que comienzan con @babel/xxx o babel-xxx
son las liberarías necesarias para hacer el proceso de transpilación, es
decir, convertir todos los archivos JavaScript escritos en JSX a un
JavaScript que el navegador pueda interpretar.
• Webpack: Todas las librerías que comienza con webpack-xxx son las
necesarias para crear el famoso archivo bundle.js a partir de los
archivos transpilados por Bebel.
• React-hot-loader: Esta librería habilita el Hot Deployment con Webpack,
el cual permite que todos los cambios realizados en el código se
actualicen en tiempo real en el navegador.

Continuamos con las dependencias locales:

1. "dependencies": {
2. "core-js": "^3.6.4",
3. "react": "^16.13.1",
4. "react-dom": "^16.13.1"
5. }

• react: obviamente es la librería de principal de Runtime de React.


• react-dom: librería para trabajar con el DOM, originalmente era parte
del módulo React, pero se decidió separar.
• core-js: Librería que permite explotar todas las características de las
promesas y async/await de JavaScript.

Desinstalando librerías

Para desinstalar un paquete ya instalado se realiza un procedimiento muy similar


al de instalación, sin embargo, en lugar de usar el comando install, utilizaremos
uninstall.

npm uninstall <options> <module_name>

mediante el comando uninstall le indicamos a npm nuestra intención de


desinstalar el paquete con el nombre <module_name>. Adicional, podemos
definir algunas opciones adicionales, como son:

-S o --save: borra la dependencia de la sección dependencies

-D o --save-dev: borra la dependencia de la sección devDependencies

--no-save: no borra la dependencia en el archivo package.json

55 | Página
Micro-modularización

Algo a tomar en cuenta, es que NodeJS al igual que todos los módulos disponibles
en NPM (incluyendo React) son micro modulares, lo que quiere decir que son
librerías muy pequeñas, diseñadas para realizar una tarea muy específica, a
diferencia de los lenguajes de programación tradiciones, como el JDK de Java o
framework de .NET, los cuales pasan años antes de liberar una nueva versión, y
las versiones suelen tener grandes cambios.

Con NodeJS, cada proyecto es independiente, lo que permite que cada módulo
evolucione a su propio ritmo, lo cual es muy bueno, pues en el caso de Java o
NET, tenemos que esperar años, antes de tener mejoras en el lenguaje o las
librerías proporcionadas.

Esta micromodularización tiene grandes ventajas, pero también puede ser una
trampa para programadores inexpertos, pues la gran mayoría de los
programadores siempre buscan la última versión de un módulo, sin importar que
agrega o que compatibilidad rompe.

Instalar siempre la última versión de un módulo, no siempre es la mejor opción,


pues los paquetes de NPM evolucionan tan rápido, que con frecuencia rompen
compatibilidad con los demás paquetes que estamos utilizando. Es por eso que,
mi recomendación, es siempre instalar las versiones que aquí mencionamos,
incluso si hay nuevas versiones y tiene cambios interesantes. Lo mejor será que
entiendas los ejemplos tal cual se plantean y una vez que te sientas seguro,
puedes migrar a versiones más recientes.

Incluso, es muy probable que varios de los módulos que utilizamos en este libro,
liberen nuevas versiones mientras escribimos este libro, pero eso no quiere decir
que estamos desactualizados, ya que muchos de los features que agregan los
módulos, ni siquiera los utilizamos. Es por eso que mi recomendación es siempre
evaluar que nuevas cosas trae un módulo antes de actualizarlo. Como regla, las
versiones que corrigen bug siempre es bueno actualizarlas, las versiones
menores es importante investigar que nuevas cosas tiene y las mayores hay que
tratarlas con mucho cuidado, pues con frecuencia rompen compatibilidad.

Error común

Un error de novatos es querer tener siempre la última


actualización de todos los módulos, por lo que siempre
es importante evaluar si realmente es necesario hacer
el upgrade, ya que es muy común que nuevas
versiones rompan compatibilidad con versiones
anteriores.

Página | 56
Introducción a WebPack

Como su nombre lo dice, Webpack es un empaquetador, eso quiere decir que su


trabajo es obtener todos los archivos de nuestro proyecto, aplicar un
procesamiento y arrojarnos una versión mucho más compacta. Puedo no sonar
la gran cosa, pero la realidad es que facilita el trabajo como no tienes una idea.

Fig 22 - Funcionamiento de Webpack

Como puedes ver en la imagen, Webpack tomará todos los archivos de nuestro
proyecto, los compilará, empaquetará, comprimirá y finalmente los minificara,
todo esto sin que nosotros tengamos que hacer prácticamente nada.

Uno de los aspectos más interesantes de Webpack, son los cargadores o loaders,
los cuales nos permite procesar diferentes tipos de archivos y arrojarnos un
resultado, un ejemplo de estos son los procesadores de SASS y LESS, que nos
permite compilar los archivos y arrojarnos CSS, también está el loader Babel,
que permite que el código JavaScript en formato ECMAScript 7 sea compatible
con todos los navegadores. También podemos configurar a Webpack para que
aplique compresiones a las imágenes y minificar el código (compactar).

Nuevo concepto: Minificar

Proceso por el cual un archivo como JavaScript, CSS,


HTML, etc. es compactado al máximo, con la finalidad
de reducir su tamaño y ser descargado más rápido por
el navegador.

Por si esto fuera poco, Webpack nos proporciona un servidor de desarrollo


integrado, el cual es listo para ser utilizado. Webpack se podría ver como la
evolución de herramientas como Grunt, browserify, Gulp, etc, pues permite hacer
lo que ya hacían estas herramientas y algunas cosas extras.

57 | Página
Instalando Webpack

Instalar Webpack es mucho más simple de lo que creeríamos, pues solo falta
instalar la dependencia con npm como ya lo hemos visto antes.

npm install --save-dev webpack@4.42.1

Este comando habilitará el uso de Webpack para el proyecto en cuestión. Pero


también es posible instalar Webpack a nivel global agregando el parámetro -g
como ya vimos.

Otra de las herramientas que nos proporciona Webpack es el servidor de


desarrollo, el cual podemos instalar de la siguiente manera:

npm install --save-dev webpack-dev-server@3.10.3

Si prestaste atención, seguramente te habrás dado cuenta que el comando start


definido en package.json, utiliza el módulo webpack-dev-server. Veamos
nuevamente el comando para refrescar la memoria:

1. "scripts": {
2. "start": "node_modules/.bin/webpack-dev-server --progress"
3. }

Este script permite que cuando ejecutemos npm start, inicie una nueva instancia
del server webpack-dev-server, y el parámetro --progress es solo para ver el
avance a medida que compila los archivos.

Webpack-dev-server es solo para desarrollo

Este módulo es de gran ayuda únicamente en la fase


de desarrollo, pues permite levantar un servidor
fácilmente y automatizar la transpilación de los
archivos. Sin embargo, no es apropiado para un
ambiente productivo, más adelante veremos cómo
crear nuestro propio servidor para producción.

Sin este módulo, tendríamos que crear un servidor con NodeJS antes de poder
ejecutar un Hello World en React.

Puedes buscar más información de Webpack en su página oficial:


https://webpack.js.org/

Página | 58
El archivo webpack.config.js

Como ya hemos visto, webpack se configura a través del archivo


webpack.config.js y desde el cual podremos definir la forma en que empaquetará
nuestra aplicación.

Retomemos el archivo webpack.config.js que ya tenemos para analizarlo:

1. module.exports = {
2. mode: "development", //development
3. entry: [
4. __dirname + "/app/App.js",
5. ],
6. output: {
7. path: __dirname + "/public",
8. filename: "bundle.js",
9. publicPath: "/public"
10. },
11. module: {
12. rules: [{
13. test: /\.jsx?$/,
14. exclude: [/node_modules/],
15. loader: 'babel-loader',
16. options: {
17. presets: ["@babel/preset-env", "@babel/preset-react"],
18. plugins: [
19. "@babel/plugin-proposal-class-properties",
20. "@babel/plugin-proposal-export-default-from",
21. "react-hot-loader/babel"
22. ]
23. }
24. }]
25. }
26. };

Este archivo simplemente exporta un objeto, el cual contiene toda la


configuración necesaria para el funcionamiento de Webpack. Las secciones del
archivo son:

• Mode: Indica el modo de empaquetamiento, básicamente de puede


definir dos valores, development y production. El modo indica el nivel de
optimización con que el bundle será generado.
• Entry: permite definir los puntos de entrada de la aplicación, es decir, la
página de inicio o cuantas páginas sea requeridas. En el caso de React,
solo es necesario tener un punto de montaje, por lo que hemos
referencia al archivo App.js.
• Output: en esta sección se define como es que los archivos procesados
serán guardados y finalmente, en que URL estarán disponibles. las
propiedades utilizadas son:
o Path: path o URL en la que los archivos procesados serán guardados.
o Filename: indica el nombre del archivo empaquetado, es decir, todos
los archivos JavaScript que encuentre los empaquetará en un solo
archivo llamado bundle.js.

59 | Página
o publicPath: con esta propiedad le indicamos a webpack-dev-server la
URL en la que estarán disponibles los archivos empaquetados.
Recuerda que en el archivo index.html hacemos referencia al archivo
bundles.js y styles.css iniciando con /public en el path.
• Module > Rules: Permite definir las reglas de cómo será generado el
bundle.js, es decir, que archivos deberán ser tomas en cuenta, como
procesarlos y si hay algún plugin.
o Test: se define la expresión regular que se utilizará validar si un
archivo debe de ser procesado por el loader, en este caso, indicamos
que tome los archivos *.js | *.jsx.
o Exclude: indicamos archivos o path que deben de ser ignorados por
el loader. En este caso, le decimos que ignore todos los módulos de
Node (node_modules).
o Loader: el loader corresponde al paquete que será utilizado para
procesar los archivos, en este caso, le indicamos que utilice babel para
el procesamiento de los archivos JavaScript.
o Presets y plugins: En su conjunto son librerías que permite realizar
la transpilación y agregar funcionalidad al lenguaje mediante plugins.
No entraré mucho en detalle sobre esta sección porque muy avanzada.

Nuevo concepto: Transpilación

Hasta este momento, hemos utilizado incorrectamente


la palabra compilar, para referirnos al proceso por el
cual, convertimos los archivos de React a JavaScript
puro, sin embargo, el proceso por el cual se lleva a
cabo esto es Transpilación, que no es precisamente
una compilación, si no la conversión del código de
React a JavaScript compatible con todos los
navegadores.

Webpack puede parecer simple, pero es mucho más complejo de lo que parece,
tiene una gran cantidad de plugins y configuraciones que pueden ser requeridas
en cualquier momento. Puedes darle una revisada a la documentación oficial para
que te des una idea: https://webpack.github.io/docs/

React Developer Tools

Otra de las herramientas indispensables para debugear una aplicación


desarrollada en React, es el plugin React Developer Tools para Google Chrome
(también disponible para Firefox). El cual nos permitirá ver la estructura de la
aplicación, así como su estado y propiedades (veremos los estados y propiedades
más adelante).

A lo largo de este libro utilizaremos Chrome, pues es el navegador más utilizado


en la actualidad y con el mejor soporte. Por ello, accedemos a la siguiente URL

Página | 60
desde Chrome https://chrome.google.com/webstore y en la barra de búsqueda
poner ‘react developer tools’, he instalamos el siguiente plugin:

Fig 23 - React developer tools

Una vez instalado probablemente tengas que reiniciar el navegador. Hecho esto,
deberá aparecer el ícono de React a un lado de la barra de búsqueda. Por default
este ícono se ve gris, lo cual indica que la página en la que estamos, no utiliza
React. Para probar el funcionamiento del plugin, nos iremos a Facebook y
veremos que el ícono pasa a tener color azul. Esto nos indica que la página utiliza
React y es posible debugearla con el plugin.

Una vez que estemos en Facebook, daremos click derecho en cualquier parte de
la página y presionaremos la opción Inspeccionar. Una vez allí, nos vamos al tab
Components:

61 | Página
Fig. 3 - Probando el plugin React developer tolos

Desde esta sección, es posible ver los componentes de React y saber en tiempo
real, el valor de su estado y propiedades. Por ahora no entraremos en los
detalles, pues primero necesitamos aprender los conceptos básicos como estados
y propiedades, antes de poder entender lo que nos arroja el plugin. Más adelante
retomaremos el plugin para analizar las páginas.

Página | 62
Resumen

Hemos concluido uno de los capítulos más complicados, pues nos tuvimos que
enfrentar a varias tecnologías, aprendimos nuevos conceptos y echamos a andar
nuestra primera aplicación con React, lo cual es un enorme avance.

Hasta este punto hemos aprendido a instalar React, NodeJS, y configurar una
base de datos Mongo en la nube, también aprendimos a gestionar dependencias
con npm, para finalmente introducirnos en Webpack.

De aquí en adelante, esto se pondrá mucho mejor, pues ya entraremos de lleno


a React y empezaremos a programar nuestros primeros componentes.

63 | Página
Introducción al desarrollo con
React
Capítulo 3

Ya en este punto, hemos creado nuestra primera aplicación, sin embargo, no


entramos en los detalles como para comprender todavía como programar con
React, es por ello, que en este capítulo buscaremos aprender los conceptos más
básicos para poder programar con React.

Antes de entrar de lleno a la programación, quiero contarte que React soporte


dos formas de trabajar, la primera es crear todos los elementos de una página
con JavaScript puro, y la segunda es mediante una sintaxis propia de React
llamada JSX.

Programación con JavaScript XML (JSX)

Una de las grandes innovaciones que proporciona React, es su propio lenguaje


llamado JSX (JavaScript XML), el cual es una mescla entre HTML y XML que
permite crear vistas de una forma muy elegante y eficiente.

Si recordamos nuestra clase App.js, teníamos una función llamada render, la cual
tiene como finalidad crear una vista, esta función debe de retornar la página que
finalmente el usuario verá en pantalla. Veamos la función para recordar:

1. render(){
2. return(
3. <h1>Hello World</h1>
4. )
5. }

Observemos que la función regresa una etiqueta <h1>, la cual corresponde con la
etiqueta <h1> que podemos ver en el navegador:

Página | 64
Fig. 4 - Inspector de elementos de Google <h1>

Inspector de elementos

Todos los navegadores modernos nos permiten


inspeccionar una página para ver los elementos que la
componen. En el caso de Chrome, solo requieres dar
click derecho sobre la página y presionar
Inspeccionar.

Aunque la etiqueta <h1> pueda parecer HTML, la realidad es que no lo es, en


realidad ya estamos utilizando JSX. Uno de los éxitos de JSX es que es
extremadamente similar a programar en HTML, pero con las reglas de generación
de un XML.

Diferencia entre JSX, HTML y XML

Dado que JSX y HTML pueden ser muy similares, es muy fácil confundirnos y no
entender cuáles son sus diferencias, provocando errores de compilación o incluso
en tiempo de ejecución. Vamos a analizar las diferencias que existen entre JSX,
HTML y XML

65 | Página
Elementos balanceados

Cuando trabajamos con HTML es común encontrarnos con elementos self-


closing o que no requieres una etiqueta de cierre, como el caso de la etiqueta
<img>. En HTML podríamos declarar una imagen de la siguiente manera:

1. <img src='/images/img1.png' alt='mi imagen'>

Notemos que no tiene una etiqueta de cierre </img> ni termina en />, esto sería
totalmente válido en HTML, sin embargo, en JSX no lo es, ya que JSX utiliza las
reglas de XML, por lo que todos los elementos deben de cerrarse, incluso si en
HTML no es requerido.

Nuevo concepto: Self-closing

Son etiquetas de HTML que no requieren una etiqueta


de cierre o que se cierre en la misma declaración con
/>.

Hagamos una prueba, regresemos al archivo App.js y editemos la función render


para que se vea de la siguiente manera:

1. render(){
2. return(
3. <img src="/images/img1.png" alt="mi imagen">
4. )
5. }

Guardemos los cambios y veremos que Webpack detectará los cambios y tratará
de transpilar los cambios, dando un error en el proceso:

Fig. 5 - Error de elementos no balanceados.

Página | 66
Para corregir este error, es necesario cerrar el elemento, y lo podemos hacer de
dos formas:

Cerrando el elemento inmediatamente después de abrirlo con />.

1. <img src="https://facebook.github.io/react/img/logo.svg" alt="mi imagen"/>

O crear su etiqueta de cierre correspondiente:

1. <img src="https://facebook.github.io/react/img/logo.svg"></img>

Puedes utilizar el método que más te agrade, al final el resultado será el mismo.

Elemento raíz único

Una de las principales reglas que tiene JSX es que solo podemos regresar un solo
elemento raíz. Esto quiere decir que no podemos retornas dos o más elementos
a nivel de la función return. Por ejemplo, en el componente App.js eliminamos
el <h1> para agregar una etiqueta <img>, pero ¿qué pasaría si quiero retornar las
dos al mismo tiempo?, bueno podría hacer lo siguiente:

1. render(){
2. return(
3. <h1>Hello World</h1>
4. <img src="https://facebook.github.io/react/img/logo.svg"></img>
5. )
6. }

Observemos que tanto <h1> como <img> están declarados al mismo nivel, lo que
quiere decir que tenemos dos elementos raíz, lo que es inválido para JSX.
Guardemos los cambios ver qué sucede:

Fig. 6 - Error de elemento raíz único

67 | Página
Como podemos ver, nuevamente sale un error al transpilar el archivo. ¿Esto
quieres decir que tengo que crear una clase para cada elemento?, la respuesta
es no, tan solo es necesario encapsular los dos elementos dentro de otro, como
podría ser un <div>. veamos cómo quedaría:

1. render(){
2. return(
3. <div>
4. <h1>Hello World</h1>
5. <img src="https://facebook.github.io/react/img/logo.svg"/>
6. </div>
7. )
8. }

Esta nueva estructura ya cumple con la regla de un elemento raíz único, en donde
el <div> sería el elemento raíz. Dentro del <div> ya es posible incluir cualquier
tipo de estructura sin restricciones.

Fig. 7 - Elemento raíz válido

Nuevo concepto: Elemento raíz

Se le conoce como elemento raíz el primero elemento


de un documento XML, el cual deberá contener todos
los demás elementos y ningún otro elemento podrá
estar a la misma altura que él.

Página | 68
Fragments

Como acabamos de ver, mediante JSX solo podemos regresar un elemento raíz,
sin embargo, existe ocasiones en las que es necesario retornar más de un solo
elemento sin tener un elemento padre, para esto React incorpora los Fragments,
los cuales son elementos dummy, lo que quiere decir que nos permite agregarlos
como padre de una serie de elementos, pero al momento de realizar el render
del componente, son ignorados.

Veamos el ejemplo anterior utilizando Fragments:

1. import React from 'react'


2. import { render } from 'react-dom'
3.
4. class App extends React.Component{
5.
6. render(){
7. return(
8. <React.Fragment>
9. <h1>Hello World</h1>
10. <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React
-icon.svg/210px-React-icon.svg.png"></img>
11. </React.Fragment>
12. )
13. }
14. }
15. render(<App/>, document.getElementById('root'));

Para utilizar un Fragment solo es necesario crear un elemento Fragment que se


encuentra dentro de la clase React <React.Fragment>.

El resultado que veremos en el navegador es el siguiente:

1. <html>
2. <head>
3. <title>Mini Twitter</title>
4. <link rel="stylesheet" href="/public/resources/css/styles.css">
5. </head>
6. <body>
7. <div id="root">
8. <h1>Hello World</h1>
9. <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-
icon.svg/210px-React-icon.svg.png">
10. </div>
11. <script type="text/javascript" src="/public/bundle.js"></script>
12. </body>
13. </html>

Puedes observar que los elementos <h1> y <img> quedaron al mismo nivel dentro
del elementos root, pero ya no tiene un div adicional que los encapsule.

Otra alternativa más simple es utilizar una par de etiquetas basias <></> como
podemo ver a continuación:

16. import React from 'react'

69 | Página
17. import { render } from 'react-dom'
18.
19. class App extends React.Component{
20.
21. render(){
22. return(
23. <>
24. <h1>Hello World</h1>
25. <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React
-icon.svg/210px-React-icon.svg.png"></img>
26. </>
27. )
28. }
29. }
30. render(<App/>, document.getElementById('root'));

El resultado de este método es exactamente igual al anterior pero mucho más


simple.

Fragments a partir de React 16.2.0

Los Fragments fueron agregados a partir de la versión


16.2.0, por lo que es necesario actualizar las
dependencias de los módulos react y react-dom.

npm install react@16.x react-dom@16.x

Camel Case en atributos

Otra de las características de JSX es que los atributos deben ser escritos en Camel
Case, esto quiere decir que debemos utilizar MAYUSCULAS entre cada palabra.

Nuevo concepto: Camel Case

CamelCase es un estilo de escritura que se aplica a


frases o palabras compuestas. El nombre se debe a
que las mayúsculas a lo largo de una palabra en
CamelCase se asemejan a las jorobas de un camello.

Un ejemplo muy claro de esto es, el evento onclick que tiene todos los elementos
de HTML. En JSX onclick no es correcto, por lo que tendría que escribirse como
onClick, notemos que tenemos la C mayúscula. Veamos qué pasa si poner de
forma incorrecta el atributo:

1. return(
2. <div>
3. <h1>Hello World</h1>

Página | 70
4. <img src="https://facebook.github.io/react/img/logo.svg"/>
5. <br/>
6. <button onclick={()=>alert('Hello World')}>Hello!!</button>
7. </div>
8. )

Si guardamos los cambios, no veremos un error como tal en la consola al


momento de realizar la transpilación, en su lugar obtendremos un warning en
tiempo de ejecución, el cual lo podremos ver en el inspector.

Fig. 8 – Error de Camel Case

Adicional a esto, si presionamos el botón, no pasará nada, pues React no sabrá


qué hacer con eso.

Si intentamos nuevamente con el atributo en Camel Case, podremos ver una


gran diferencia, pues ya no nos aparecerá el error y si damos click en el botón,
este nos mandará el mensaje Hello World.

71 | Página
Fig. 9 - Hello World con Camel Case

De la misma forma que tenemos el onClick, tenemos onFocus, onChange,


onSubmit, onBlur, etc.

Contenido dinámico y condicional

En esta sección analizaremos cómo es posible agregar contenido dinámico en


React, así como mostrar valores por medio de una variable o de forma
condicional.

Contenido dinámico

Lo primero que debemos de saber, es que React permite definir valores dinámicos
en prácticamente cualquier lugar. Estos valores pueden ser una variable, el
resultado de un servicio o incluso parámetros enviados a los componentes. Para
ellos es necesario agregar los valores entre un par de llaves { }, tal como lo
vimos hace un momento con el evento onClick.

1. <button onclick={()=>alert('Hello World')}>Hello!!</button>

Veamos que la alerta está dentro de unas llaves, de lo contrario, React no sabrá
cómo interpretar el valor introducido.

Página | 72
Variables

Otra forma de representar contenido dinámico es por medio de variables. Veamos


el siguiente ejemplo:

1. render(){
2. let variable = {
3. message: "Hello World desde una variable"
4. }
5.
6. return(
7. <div>
8. <h1>{variable.message}</h1>
9. <img src="https://facebook.github.io/react/img/logo.svg"/>
10. <br/>
11. <button onClick={()=>alert('Hello World')}>Hello!!</button>
12. </div>
13. )
14. }

En este ejemplo, hemos definido una variable (línea 2) y luego la hemos utilizado
dentro de un bloque {}. Esto hace que React lo interprete como código.

Fig. 10 - Hello World con variables

Valores condicionales

Además de mostrar variables, existen ocasiones donde el valor a mostrar está


condicionado, por tal motivo, debemos hacer una validación y dependiendo el
resultado, será el valor que mostremos en pantalla.

Veamos un ejemplo muy simple, supongamos que debo saludar al usuario según
su sexo. Por lo que antes de mostrar un mensaje, deberá validar el sexo. Para
hacer esta prueba, vamos a retomar el ejemplo pasado, donde saludamos con
una variable, pero agregaremos datos adicionales para saludar con un mensaje
diferente según el sexo.

73 | Página
La primera forma, es mediante expresiones ternarias:

1. render(){
2. let variable = {
3. sexo: "woman",
4. man: "Hola Amigo",
5. woman: "Hola Amiga"
6. }
7. return(
8. <div>
9. <h1>{variable.sexo === 'man' ? variable.man : variable.woman}</h1>
10. <img src="https://facebook.github.io/react/img/logo.svg"/>
11. <br/>
12. <button onClick={()=>alert('Hello World')}>Hello!!</button>
13. </div>
14. )
15. }

Observemos nuevamente la variable, hemos agregado una propiedad sexo, la


cual nos indicará el sexo del usuario, y las propiedades man y woman, que
utilizaremos para saludar según el sexo del usuario.

Fig. 11 - Hello Amiga

Prueba tú mismo a cambar el valor de la propiedad sexo, de woman a man y observa


los resultados.

Este método es bastante efectivo cuando solo tenemos dos posibles valores, pues
las expresiones ternarias nos regresan dos valores posibles. Pero qué pasa
cuando existen más de 2 posibles resultados, una sería anidar expresiones
ternarías, lo cual es posible, pero se crearía un código muy complicado y verboso.
La otra opción es trabajar la condición por fuera, de esta forma, podemos realizar
todas las validaciones que requiramos y al final solo imprimimos el resultado por
medio de una variable.

Retomando el ejemplo anterior, que pasaría si el sexo del usuario es indefinido,


es decir, no lo conocemos. En este caso, tendríamos que lanzar un mensaje más
genérico. Veamos cómo quedaría:

Página | 74
1. render(){
2. let variable = {
3. sexo: "",
4. man: "Hola Amigo",
5. woman: "Hola Amiga",
6. other: "Hola Amig@"
7. }
8. let message = null
9. if(variable.sexo === 'man'){
10. message = variable.man
11. }else if(variable.sexo === 'woman'){
12. message = variable.woman
13. }else{
14. message = variable.other
15. }
16. return(
17. <div>
18. <h1>{message}</h1>
19. <img src="https://facebook.github.io/react/img/logo.svg"/>
20. <br/>
21. <button onClick={()=>alert('Hello World')}>Hello!!</button>
22. </div>
23. )
24. }

Primero que nada, veamos que el sexo está en blanco, también que hemos
agregado una nueva propiedad other, la cual utilizaremos para saludar si no
conocemos el sexo del usuario. Por otra parte, observa la secuencia de
if..elseif..else, en ella, validamos el sexo del empleado, y según el sexo,
escribimos un valor diferente en la variable message. Finalmente, en el <h1> solo
imprimimos el valor de la variable message.

Observa que antes de hacer el return tenemos un bloque donde podemos escribir
cualquier instrucción Javascript, por lo que podríamos hacer prácticamente
cualquier cosa, llamadas a métodos, declarar variables, etc.

JSX Control Statements

A medida que un proyecto se va complicando, es muy común encontrarnos con


muchos if…elseif..else por todos los componentes, provocando que el proyecto
se vuelva cada vez más difícil de entender y de seguir. Es por ese motivo que
existe un plugin para Babel que permite extender el lenguaje JSX para agregar
condiciones mediante <tags>, lo cual facilita muchísimo el desarrollo, haciendo
que el código que escribamos sea mucho más simple y fácil de entender.

Instalar jsx-control-statements

Primero que nada, será necesario instalar el módulo mediante npm con el
siguiente comando:

npm install -D --save jsx-control-statements

75 | Página
Una vez terminada la instalación, se nos agregará la dependencia en el archivo
package.json en la sección de librerías de desarrollo.

Jsx-control-statement extiende al lenguaje

Jsx-control-statements no es una librería que


debamos importarla para utilizarla, sino más bien,
extiende al lenguaje JSX agregando nueva
funcionalidad.

El siguiente paso será incluir el módulo en el archivo webpack.config.js:

1. module.exports = {
2. mode: "development", //development
3. entry: [
4. __dirname + "/app/App.js",
5. ],
6. output: {
7. path: __dirname + "/public",
8. filename: "bundle.js",
9. publicPath: "/public"
10. },
11. module: {
12. rules: [{
13. test: /\.jsx?$/,
14. exclude: [/node_modules/],
15. loader: 'babel-loader',
16. options: {
17. presets: ["@babel/preset-env", "@babel/preset-react"],
18. plugins: [
19. "@babel/plugin-proposal-class-properties",
20. "@babel/plugin-proposal-export-default-from",
21. "react-hot-loader/babel",
22. "module:jsx-control-statements"
23. ]
24. }
25. }]
26. }
27. }

Solo hemos agregado el plugin en la línea 22, con esto Webpack extenderá el
lenguaje de JSX para soportar un set de estructuras de control, las cuales
analizaremos a continuación.

If

Anteriormente vimos cómo era necesario crear una expresión ternaria o crear
una estructura de if…else para saludar a nuestro usuario según el sexo, pero
ahora con el plugin jsx-control-statements el lenguaje se ha ampliado,
permitiéndonos crear la etiqueta <If>, la cual solo tiene un atributo llamado
condition, que deberá tener una expresión que se resuelva en booleano. Si la

Página | 76
expresión es true, entonces todo lo que este dentro de la etiqueta se mostrará.
Veamos cómo quedaría el ejemplo anterior:

1. render(){
2. let variable = {
3. sexo: "woman",
4. man: "Hola Amigo",
5. woman: "Hola Amiga",
6. other: "Hola Amig@"
7. }
8.
9. return(
10. <div>
11. <img src="https://facebook.github.io/react/img/logo.svg"/>
12. <br/>
13. <button onClick={()=>alert('Hello World')}>Hello!!</button>
14. <If condition={variable.sexo === 'man' }>
15. <h1>{variable.man}</h1>
16. </If>
17. <If condition={variable.sexo === 'woman' }>
18. <h1>{variable.woman}</h1>
19. </If>
20. </div>
21. )
22. }

Veamos que esta vez en lugar de crear una variable y luego asignarle el valor
mediante una serie de if…elseif…else, lo hacemos directamente sobre el JSX.

Un dato importante de <If> es que no nos permite poner <else> o <elseif>, por
lo que solo nos sirve cuando tenemos una expresión a evaluar.

Choose

La estructura de control Choose, nos permite crear una serie de condiciones que
se evalúan una tras otra, permitiendo tener un caso por default, exactamente lo
mismo que hacer un if…elseif…else.

En el ejemplo anterior pusimos un <If> para cada sexo, pero no tuvimos la


oportunidad de definir qué pasaría, si el sexo fuera diferente de man y woman,
y es aquí donde Choose encaja a la perfección. Veamos cómo quedaría este
ejemplo:

1. render(){
2. let variable = {
3. sexo: "",
4. man: "Hola Amigo",
5. woman: "Hola Amiga",
6. other: "Hola Amig@"
7. }
8. return(
9. <div>
10. <img src="https://facebook.github.io/react/img/logo.svg"/>
11. <br/>

77 | Página
12. <button onClick={()=>alert('Hello World')}>Hello!!</button>
13. <Choose>
14. <When condition={variable.sexo === 'man' }>
15. <h1>{variable.man}</h1>
16. </When>
17. <When condition={variable.sexo === 'woman'}>
18. <h1>{variable.woman}</h1>
19. </When>
20. <Otherwise>
21. <h1>{variable.other}</h1>
22. </Otherwise>
23. </Choose>
24. </div>
25. )
26. }

Observemos que Choose, permite definir una serie de <When>, donde cada una
tendrá una condición a evaluarse, si la condición de un <When> se cumple,
entonces su contenido se mostrará. En caso de que ningún <When> se cumpla, se
mostrará el valor que hay en <Otherwise> el cual no requiere de ningún atributo,
pues sería el valor por default.

For

La etiqueta <For> nos permite iterar una array con la finalidad de arrojar un
resultado para cada elemento de la colección. Hasta el momento no hemos visto
como imprimir un arreglo, por lo que iniciaremos con un ejemplo sin utilizar la
etiqueta <For> para poder comparar los resultados.

Lo que haremos será imprimir una lista de usuarios que tenemos en un array sin
utilizar jsx-control-statements:

1. render(){
2. let usuarios = [
3. 'Oscar Blancarte',
4. 'Juan Perez',
5. 'Manuel Juarez',
6. 'Juan Castro'
7. ]
8.
9. let userList = usuarios.map(user => {
10. return (<li>{user}</li>)
11. })
12.
13. return(
14. <div>
15. <ul>
16. {userList}
17. </ul>
18. </div>
19. )
20. }

Primero que nada, veamos la lista de usuarios (línea 2), la cual tiene 4 nombres,
seguido, lo que hacemos es iterar el array mediante el método map, esto nos

Página | 78
permitirá obtener el nombre individual de cada usuario en la variable user,
seguido, regresamos el nombre del usuario dentro de un tag <li> para
mostrarlos en una lista. Finalmente, en la respuesta del método render,
retornamos la lista de usuarios dentro de una lista <lu>.

Ya con este precedente, podemos comprobar cómo quedaría con <For>:

1. render(){
2. let usuarios = [
3. 'Oscar Blancarte',
4. 'Juan Perez',
5. 'Manuel Juarez',
6. 'Juan Castro'
7. ]
8.
9. return(
10. <div>
11. <For each="user" index="index" of={ usuarios }>
12. <li>{user}</li>
13. </For>
14. </div>
15. )
16. }

En este ejemplo se pueden apreciar mucho mejor los beneficios, pues hemos
eliminado la necesidad de una variable secundaria.

Los parámetros que requiere <For> son:

4. of: array al que queremos iterar.


5. each: variable en donde se guardará cada elemento iterado.
6. index: valor numérico referente al index del objeto dentro del array.

Te comparto la liga a la documentación de jsx-control-statements si quieres


adentrarte y conocer más: https://www.npmjs.com/package/jsx-control-
statements.

Transpilación

En el pasado ya hemos hablado un poco acerca del proceso de transpilación, sin


embargo, no hemos dicho quien se encarga de hacerlo y como lo hace. Es por
ello que quise hacer una pequeña sección para explicarlo.

Uno de los problemas cuando utilizamos JSX es que el navegador no es capaz


de interpretarlo, pues este solo sabe de HTML, CSS y JavaScript. Para que el
navegador sea capaz de entender nuestro código escrito en JSX es necesario
pasarlo por el proceso de transpilación, este proceso lo hace un módulo llamado

79 | Página
Babel, el cual toma los archivos en JSX y los convierte en JavaScript nativo, para
que, de esta forma, el navegador pueda comprenderlo y ejecutarlo.

Con Webpack es posible automatizar este proceso mediante un loader especial


para Babel, el cual ya lo hemos estado utilizando, pero no lo habíamos explicado.
A continuación, un pequeño fragmento del archivo webpack.config.js:

1. module.exports = {
2. mode: "development", //development
3. entry: [
4. __dirname + "/app/App.js",
5. ],
6. output: {
7. path: __dirname + "/public",
8. filename: "bundle.js",
9. publicPath: "/public"
10. },
11. module: {
12. rules: [{
13. test: /\.jsx?$/,
14. exclude: [/node_modules/],
15. loader: 'babel-loader',
16. options: {
17. presets: ["@babel/preset-env", "@babel/preset-react"],
18. plugins: [
19. "@babel/plugin-proposal-class-properties",
20. "@babel/plugin-proposal-export-default-from",
21. "react-hot-loader/babel",
22. "module:jsx-control-statements"
23. ]
24. }
25. }]
26. }
27. }

Podemos observar que estamos utilizando un loader llamado babel, el cual


procesará todos los archivos con extensión *.js y *.jsx. Adicional, podemos ver
que le estamos agregando el plugin jsx-control-statements para ampliar el
lenguaje.

Esto ha sido una corta explicación acerca de lo que es Babel, pues era importante
entender que, es Babel y no Webpack, el que transpila los archivos. Pero es a
través de los loaders que Webpack que se puede automatizar el proceso de
transpilación por medio de Babel.

Programación con JavaScript puro.

Aunque JSX cubre casi todas las necesidades para crear componentes, existen
ocasiones, en las que es necesario crear elementos mediante JavaScript puro.
No es muy normal ver aplicación que utilicen esta forma de programar, pero
pueden existir ocasiones que lo ameriten.

Página | 80
Mediante JavaScript es posible crear elementos al vuelo mediante la función
React.createElement, la cual recibe 3 parámetros:

1. Type: le indica el tipo de elemento a crear, por ejemplo, input, div,


button, etc.
2. Props: se le envía un arreglo de propiedades. Hasta el momento no
hemos visto propiedades, pero ahora solo imaginemos que son una serie
de parámetros.
3. Childs: se le envía los elementos hijos del elemento, el cual pueden ser
otros elementos o una cadena de texto.

Veamos cómo quedaría la clase App habilitándola para usar JavaScript Nativo en
lugar de JSX:

1. import React from 'react'


2. import { render } from 'react-dom'
3.
4. class App extends React.Component{
5.
6. render(){
7. let helloWorld = React.createElement('h1',null,'Hello World')
8. let img = React.createElement('img',
9. {src:'https://facebook.github.io/react/img/logo.svg'}, null)
10. let div = React.createElement('div',null,[helloWorld,img])
11. return div
12. }
13. }
14. render(<App/>, document.getElementById('root'));

Primero que nada, veamos que estamos creando un <h1> (línea 7), el primer
parámetro es el tipo de elemento (h1), el segundo parámetro es null, pues no
tiene ninguna propiedad, y como tercer parámetro, le mandamos el texto ‘Hello
World’.

En segundo lugar, creamos un elemento <img>, al cual le mandamos la propiedad


src, que corresponde a la URL de la imagen y null como tercer parámetro, pues
no tiene un contenido.

En tercer lugar, creamos un elemento <div>, el cual contendrá a los dos


anteriores, para esto, le enviamos en un array el <h1> y el <img> como tercer
parámetro.

Finalmente, le retornamos el <div> para ser mostrado en pantalla.

Element Factorys

Como ya vimos, crear elementos con JavaScript es mucho más fácil de lo que
parece, pero por suerte, es posible crear los elementos de una forma mucho más
fácil mediante los Element Factory.

81 | Página
Los Element Factory, son utilidades que ya trae React para facilitarnos la creación
de elementos de HTML, y utilizarlos es tan fácil como hacer lo siguiente:

React.DOM.<element>

Donde <element> es el nombre de una etiqueta HTML válida. Veamos


nuevamente el ejemplo anterior utilizando Element Factory:

1. import React from 'react'


2. import { render } from 'react-dom'
3.
4. class App extends React.Component{
5.
6. render(){
7. return React.DOM.div(null,
8. React.DOM.h1(null,'Hello World'),
9. React.DOM.img(
10. {src: 'https://facebook.github.io/react/img/logo.svg'},null)
11.
12. )
13. }
14. }
15. render(<App/>, document.getElementById('root'));

El ejemplo es bastante parecido al anterior, solo que cambiamos la función


createElement por los Element Factory. Otra diferencia, es que ya no requiere el
tipo de elemento, pues ya viene implícito.

Element Factory Personalizados

Como ya lo platicamos, los Element Factory solo sirve para etiquetas HTML que
existen, por lo que cuando queremos utilizar un Element Factory para un
componente personalizado como sería App, no sería posible, es por este motivo
que existe los Element Factory Personalizados.

Veamos cómo quedaría nuestra aplicación, creando un Element Factory


personalizado para nuestro componente App.

1. import React from 'react'


2. import { render } from 'react-dom'
3.
4. class App extends React.Component{
5.
6. render(){
7. return React.DOM.div(null,
8. React.DOM.h1(null,'Hello World'),
9. React.DOM.img(
10. {src: 'https://facebook.github.io/react/img/logo.svg'},null)
11. )
12. }
13. }
14.
15. let appFactory = React.createFactory(App);
16. render(appFactory(null,null), document.getElementById('root'));

Página | 82
Esta vez el protagonista no es el método render, si no las dos últimas líneas, en
las cuales creo un Factory para el componente App (línea 15) mediante la función
React.createFactory. El Factory se almacena en la variable appFactory, que es
utilizados después (línea 16) para mostrar el elemento en pantalla.

83 | Página
Resumen

En este capítulo hemos aprendido los más esencial e importante del trabajo con
React, pues hemos aprendido a utilizar el lenguaje JSX que nos servirá durante
todo el libro.

También hemos aprendido como introducir contenido dinámico a nuestros


componentes y mostrarlos según una serie de condiciones. Y como dejar pasar
por alto el plugin jsx-control-statements que será indispensable para crear
aplicaciones más limpias y fáciles de mantener.

Hemos abordado el proceso de transpilación, por medio del cual es posible


convertir el lenguaje JSX a JavaScript nativo y que pueda ser interpretado por
cualquier navegador.

Hasta el momento hemos trabajado con el componente App, pero no hemos


entrado en detalles acerca de lo que es un componente, por lo que en el siguiente
capítulo entraremos de lleno al desarrollo de componentes, los cuales son una
parte esencial e innovadoras de React.

Página | 84
Introducción a los Componentes
Capítulo 4

Cuando desarrollamos aplicaciones Web o de escritorio, es normal tratar de


separar nuestra aplicación en pequeños archivos que luego vamos incluyendo
dentro de otros más grande. Todo esto con múltiple finalidad, como separar las
responsabilidades de cada componente, reducir la complexidad y reutilizarlos al
máximo.

En React, es exactamente lo mismo, los componentes nos permiten crear


pequeños fragmentos de interface gráfica, que luego vamos uniendo como si
fuera un rompecabezas.

La relación entre Components y Web


Component

Para comprender que es un Component, es importante entender antes el


concepto de Web Components, para lo cual voy a citar su definición de Mozilla:

“Web Components consiste en distintas tecnologías


independientes. Puedes pensar en Web Components como en
widgets de interfaz de usuario reusables que son creados usando
tecnología Web abierta. Son parte del navegador, y por lo tanto
no necesitan bibliotecas externas como jQuery o Dojo. Un Web
Component puede ser usado sin escribir código, simplemente
añadiendo una sentencia para importarlo en una página HTML.
Web Components usa capacidades estándar, nuevas o aún en
desarrollo, del navegador.”

-- mozilla.org

85 | Página
Web Components es una tecnología
experimental

A pesar que el desarrollo web ya está apuntando al


desarrollo de aplicaciones con Web Components, la
realidad es que todavía está en una fase experimental
o en desarrollo.

Como vimos, los Web Componentes son pequeños widgets que podemos
simplemente ir añadiendo a nuestra página con tan solo importarlos y no requiere
de programación.

Fig. 12 - Desarrollo de aplicaciones con Web Components

En la imagen podemos ver la típica estructura de una página, la cual está


construida mediante una serie de componentes que son importados para crear
una página más compleja, la cual se convierte en un nuevo Componente más
complejo.

Puede que esta imagen no impresione mucho, pues todas las tecnologías Web
nos permiten crear archivos separados y luego simplemente incluirlos o
importarlos en nuestra página, pero existe una diferencia fundamental, los Web
componentes viven del lado del cliente y no del servidor. Además, en las
tecnologías tradicionales, el servidor no envía al navegador un Web Component,
por ejemplo, un tag <App>, si no que más bien hace la traducción del archivo
incluido a HTML, por lo que al final, lo que recibe el navegador es HTML puro.

Con los Web Componentes pasa algo diferente, pues el navegador si conoce de
Web Components y es posible enviarle un tag personalizado como <App>.

En React, si bien los componentes no son como tal Web Components, es posible
simular su comportamiento, ya que es posible crear etiquetas personalizadas que
simplemente utilizamos en conjunto con etiquetas HTML, sin embargo, React no
regresa al navegador las etiquetas custom, si no que las traduce a HTML para

Página | 86
que el navegador las pueda interpretar, con la gran diferencia que esto lo hace
del lado del cliente.

Fig. 13 - React Components Transpilation

Es posible que, en el futuro, cuando los navegadores soporte Web Components


en su totalidad, React pueda evolucionar para hora si, crear Web Components
reales.

Componentes con estado y sin estado

Una característica de los componentes, es su estado, el cual determina tanto la


información que se muestras hasta como se representa, de esta forma, el estado
puede cambiar la apariencia gráfica de un componente hasta la forma en que se
muestra la información. Un ejemplo básico de un estado, es, por ejemplo, un
formulario que puede pasar de modo lectura a escritura:

Fig. 14 - Cambio de estado en un componente

Como vemos en la imagen, una componente puede pasar de un formulario que


no permite la edición a otro donde los valores se muestran en <input> para que
el usuario pueda cambiar sus valores. Al guardar los cambios, el componente
puede regresar a su estado inicial.

87 | Página
No vamos a entrar en los detalles de lo que es un estado, ni cómo es posible
modificarlo, pues más adelante tenemos una sección especialmente para ello,
por ahora, solo quiero que tengas una idea de lo que es el estado y cómo puede
afectar la forma en que se muestra un componente.

Componentes sin estado

Este tipo de componentes son los más simples, pues solo se utilizan para
representar la información que les llega como parámetros. En algunas ocasiones,
estos componentes pueden transformar la información con el único objetivo de
mostrarla en un formato más amigable, sin embargo, estos compontes no
consumen datos de fuentes externas ni modifican la información que se les envía.

Nuevo concepto: Componentes sin estado

Los componentes sin estado también son llamados


componentes de presentación, pero son mejor
conocidos como Stateless Component por su nombre
en inglés.

Para ejemplificar mejor este tipo de componentes vamos a crear un nuevo


archivo en nuestro proyecto llamado ItemList.js en la carpeta app, el cual
representará un ítem de una lista de productos:

1. import React from 'react'


2.
3. class ItemList extends React.Component{
4.
5. constructor(props){
6. super(props)
7. }
8.
9. render(){
10. return(
11. <li>{this.props.product.name} - {this.props.product.price}</li>
12. )
13. }
14. }
15. export default ItemList

Este componte utiliza algo que hasta ahora no habíamos utilizado, las
propiedades (Props), las cuales son parámetros que son enviados durante la
creación del componente. En este caso, se le envía una propiedad llamada
product, la cual debe de tener un nombre (name) y un precio (price).

Página | 88
Nuevo concepto: Propiedades (Props)

Las propiedades o simplemente props, son parámetros


que se le envían a un componente durante su creación.
Los props pueden ser datos para mostrar o información
para inicializar el estado. Como regla general, las props
son inmutables, lo que quieres decir, que son de solo
lectura.

Obtener las propiedades (props)

Para obtener las propiedades de un componente es


necesario obtenerlas mediante el prefijo this.props,
seguido del nombre de la propiedad.

Observemos que el componente ItemList solo muestra las propiedades que


recibe como parámetro, sin realizar ninguna actualización sobre ella.

Para completar este ejemplo, modificaremos el componente App para que quede
de la siguiente manera:

1. import React from 'react'


2. import { render } from 'react-dom'
3. import ItemList from './ItemList'
4.
5. class App extends React.Component{
6.
7. render(){
8. let items = [{
9. name: 'Item 1',
10. price: 100
11. }, {
12. name: 'Item 2',
13. price: 200
14. }]
15.
16. return (
17. <ul>
18. <For each="item" index='index' of={ items }>
19. <ItemList product={item}/>
20. </For>
21. </ul>
22. )
23. }
24. }
25.
26. render(<App/>, document.getElementById('root'));

Veamos que hemos creado un array de ítems (línea 8), los cuales cuentan con
un nombre y un precio. Seguido, iteramos los ítems (línea 18) para crear un
componente <ItemList> por cada ítem de la lista, también le mandamos los datos

89 | Página
del producto mediante la propiedad product, la cual podrá ser accedida por el
componente <ItemList> utilizando la instrucción this.props.product.

Otra cosa importante a notar, es la línea 3, pues en ella se realiza la importación


del nuevo componente para poder ser utilizando.

Fig. 15 - Componentes sin estado.

Componentes con estado

Los componentes con estado se distinguen de los anteriores, debido a que estos
tienen un estado asociado al componente, el cual manipulan a mediad que el
usuario interactúa con la aplicación. Este tipo de componentes en ocasiones
consumen servicios externos para recuperar o modificar la información.

Un ejemplo típico de componentes con estados, son los formularios, pues es


necesario ligar cada campo a una propiedad del estado, el cual, al modificar los
campos afecta directamente al estado. Veamos cómo quedaría un componente
de este tipo.

Regresaremos al componente App y lo dejaremos de la siguiente manera:

1. import React from 'react'


2. import { render } from 'react-dom'
3. import ItemItem from './ItemList'
4.
5. class App extends React.Component{
6.
7. constructor(){
8. super(...arguments)
9. this.state = {
10. firstName: '',
11. lastName: '',
12. age: ''
13. }
14. }
15.
16. handleChanges(e){

Página | 90
17. let newState = Object.assign(this.state, {[e.target.id]: e.target.value})
18. this.setState(newState)
19. }
20.
21. render(){
22.
23. return (
24. <form>
25. <label htmlFor='firstName'>Nombre</label>
26. <input id='firstName' type='text' value={this.state.firstName}
27. onChange={this.handleChanges.bind(this)}/>
28. <br/>
29. <label htmlFor='lastName'>Apellido</label>
30. <input id='lastName' type='text' value={this.state.lastName}
31. onChange={this.handleChanges.bind(this)}/>
32. <br/>
33. <label htmlFor='age'>Edad</label>
34. <input id='age' type='number' value={this.state.age}
35. onChange={this.handleChanges.bind(this)}/>
36. </form>
37. )
38. }
39. }
40.
41. render(<App/>, document.getElementById('root'));

No vamos a entrar en detalle acerca de los estados, ni cómo se actualizan, ya


que más adelante lo veremos, por lo que solo te cuento rápido que está pasando.

Primero que nada, vemos que en la línea 9 se establece el estado inicial del
componente, el cual tiene un firstName (nombre), lastName (apellido) y ege
(edad). Los cuales están inicialmente en blanco.

Seguido, en la línea 16, tenemos la función genérica handleChanges que modifica


el estado a medida que vamos capturando valores en los campos de texto.

Y finalmente en el método render, retornamos 3 campos, correspondientes a las


3 propiedades del estado, adicional, cada campo está ligado a una propiedad del
estado mediante la propiedad value. Cuando el usuario captura valores en los
campos, se dispara la función handleHanges para actualizar el estado.

91 | Página
Fig. 16 - Componente con estado

Seguramente al ver esta imagen, no quede muy claro que está pasando con el
estado, es por eso que utilizaremos el plugin React Developer Tools que
instalamos en el segundo capítulo para analizar mejor como es que el estado se
actualiza. Para esto, nos iremos al inspector, luego seleccionaremos el Tab
Components:

Fig. 17 - Inspeccionando un componente con estado

Una vez en el tab Components, seleccionamos el tag <App> y del lado izquierdo
veremos las propiedades y el estado. Con el inspector abierto, actualiza los
campos de texto y verás cómo el estado también cambia.

Página | 92
Nuevo concepto: Componentes con estado

Se le conoce como componente con estado, a aquellos


componentes que tiene estado y que adicional,
manipulan el estado a medida que el usuario
interactúa con ellos. Estos componentes también son
conocidos como Componentes Contenedores, pero es
más común llamarlos Stateless Components, por su
nombre en inglés.

Jerarquía de componentes

Dada la naturaleza de React, es normal que en una aplicación tengamos muchos


componentes y muchos de estos importarán otros compontes dentro de ellos,
que, a su vez, contendrá más componentes. Ha esta característica de crear
nuevos componentes utilizando otros, se le conoce como Jerarquía de compones.
Esta jerarquía puede verse como un árbol, donde la raíz sería el componente que
abarque a todos los demás.

Fig. 18 - Jerarquía de componentes

La imagen anterior es particularmente interesante, pues muestra como una


aplicación se visualiza (lado izquierdo) vs como es que los componentes se
organizan de forma jerárquica (lado derecho) para lograr la representación que
vemos en pantalla.

En React tan solo se requiere importar el componente dentro del componente en


el que se quiere utilizar, una vez importado, el componente se puede utilizar
como un <tag> en JSX. Para importarlo utilizamos el formato import <COMPONENT>
from <FILE_PATH>, donde:

1. component: es el nombre del componente.


2. file_path: es el path al archivo que contiene el componente.

93 | Página
Un detalle super importante es que para que un componente pueda ser utilizado,
es necesario que se exporte (línea 15), de lo contrario no será posible utilizarlo
y marcar error al momento de querer utilizarlo.

1. import React from 'react'


2.
3. class ItemList extends React.Component{
4.
5. constructor(props){
6. super(props)
7. }
8.
9. render(){
10. return(
11. <li>{this.props.product.name} - {this.props.product.price}</li>
12. )
13. }
14. }
15. export default ItemList

Referenciar correctamente el componente

Un error común cuando empezamos a programar en


React, es querer llamar al componente por el nombre
de la clase, sin embargo, el nombre del componente
será el que le pongamos en la instrucción export
default. Por lo que se aconseja que siempre lo
exportemos con el mismo nombre de la clase.

Una vez exportado, lo podemos utilizar en otro componte, como veremos a


continuación:

1. import React from 'react'


2. import { render } from 'react-dom'
3. import ItemList from './ItemList'
4.
5. class App extends React.Component{
6.
7. render(){
8. return (
9. <ItemList product={item}/>
10. )
11. }
12. }
13.
14. render(<App/>, document.getElementById('root'));

El componente anterior realiza una importación al componente ItemList (línea


3) y después crea una instancia del componente (línea 9).

Página | 94
Propiedades (Props)

Las propiedades son la forma que tiene React para pasar parámetros de un
componente padre a los hijos. Es normal que un componente pase datos a
los componentes hijos, sin embargo, no es lo único que se puede pasar, si no
que existe ocasiones en las que los padres mandar funciones a los hijos, para
que estos ejecuten operaciones de los padres, puede sonar extraño, pero ya
veremos cómo funciona.

Los props se envían a los componentes al momento de crearlos, y es tan fácil


como mandar las propiedades como un atributo del <tag> del componente, por
ejemplo, si retomamos el ejemplo del componente <ItemList>, recordarás que
le mandamos una propiedad product, la cual contenía un nombre y un precio:

1. <ItemList product={item}/>

Mandar objetos es una forma simple y limpia de mandar propiedades, pero


también lo podríamos hacer en varias propiedades, por ejemplo:

1. <ItemList productName={product.name} productPrice={product.price}/>

La única diferencia entre estos dos métodos será la forma de recuperar las
propiedades. Ya habíamos hablado que para recuperar una propiedad es
necesario usar el prefijo, this.props, por lo que en el primer ejemplo, el ítem se
recupera como this.props.product, y en el segundo ejemplo, sería
this.props.productName para el nombre y this.props.productPrice para el
precio.

Las propiedades son inmutables

La propiedad tiene la característica de ser inmutables,


es decir, no debemos de modificarlas o actualizarlas,
pues es considerada una mala práctica, solo las
debemos utilizar de modo lectura.

Regresemos al ejemplo del formulario de la sección componentes con estado y


sin estado. En este ejemplo, creamos el componente App y sobre este montamos
un formulario de registro de empleados. Ahora bien, ¿qué pasaría si quisiéramos
que la acción de guardar la controlara el componente padre? La solución es
simple, el componente padre tendría que enviar como props la función de
guardar, para que el hijo la ejecute. Veamos cómo quedaría esto:

95 | Página
Lo primero será pasar el formulario a un componente externo, por lo cual,
crearemos un nuevo componente llamado EmployeeForm sobre la carpeta app, el
cual se verá como a continuación:

1. import React from 'react'


2.
3. class EmployeeForm extends React.Component{
4.
5. constructor(){
6. super(...arguments)
7. this.state = {
8. firstName: '',
9. lastName: '',
10. age: ''
11. }
12. }
13.
14. handleChanges(e){
15. let newState = Object.assign(this.state,
16. {[e.target.id]: e.target.value})
17. this.setState(newState)
18. }
19.
20. saveEmployee(e){
21. this.props.save(this.state)
22. }
23.
24. render(){
25. return (
26. <form>
27. <label htmlFor='firstName'>Nombre</label>
28. <input id='firstName' type='text' value={this.state.firstName}
29. onChange={this.handleChanges.bind(this)}/>
30. <br/>
31. <label htmlFor='lastName'>Apellido</label>
32. <input id='lastName' type='text' value={this.state.lastName}
33. onChange={this.handleChanges.bind(this)}/>
34. <br/>
35. <label htmlFor='age'>Edad</label>
36. <input id='age' type='number' value={this.state.age}
37. onChange={this.handleChanges.bind(this)}/>
38. <br/>
39. <button onClick={this.saveEmployee.bind(this)}>Guardar</button>
40. </form>
41. )
42. }
43. }
44. export default EmployeeForm

Este componente es casi idéntico al primer formulario que creamos, pero hemos
agregado dos cosas, lo primero es que en la línea 39 agregamos un botón, el
cual, al ser presionado, ejecutar la función saveEmployee declarada en este mismo
componente, el segundo cambios, la función saveEmployee que declaramos en la
línea 20, el cual lo único que hace es ejecutar la función save enviada por el
parent como prop.

Por otra parte, tenemos al componente App que será el padre del componente
anterior:

1. import React from 'react'

Página | 96
2. import { render } from 'react-dom'
3. import EmployeeForm from './EmployeeForm'
4.
5. class App extends React.Component{
6.
7. save(employee){
8. alert(JSON.stringify(employee))
9. }
10.
11. render(){
12. return (
13. <EmployeeForm save={ this.save.bind(this) }/>
14. )
15. }
16. }
17.
18. render(<App/>, document.getElementById('root'));

Podemos ver que al momento de crear el componente EmployeeForm, este le envía


como una prop la función save. De esta forma, cuando el componente
EmployeeForm, ejecute la propiedad, se lanzará una alerta con los datos
capturados (línea 8).

Fig. 19 - Funciones como props

Binding functions

Cuando es necesario “bindear” una función con una


prop o queremos utilizar una función en un evento
como onClick, onChange, etc. es necesario siempre
empezar con this, y finalizar con binding(this) como
se ve a continuación this.<function>.bind(this).

97 | Página
PropTypes

Debido a que los componentes no tienen el control sobre las props que se le
envía, y el tipo de datos, React proporciona un mecanismo que nos ayuda a
validar este tipo de aspectos. Mediante PropTypes es posible definir las
propiedades que debe de recibir un componente, el tipo de datos, estructura e
incluso si son requeridas o no. Definir los PropTypes es tan simple cómo:

1. <component>.propTypes = {
2. <propName>: <propType>
3. ...
4. }

Donde:

• <Component>: nombre de la clase


• <propName>: nombre de la propiedad a validar
• <propType>: regla de validación

Adicional, tenemos que importar la clase PropTypes de la siguiente manera:

import PropTypes from 'prop-types'.

Ahora bien, regresaremos al ejemplo de la lista de productos, para definir el


PropType para validar la estructura del producto:

1. import React from 'react'


2. import PropTypes from 'prop-types'
3.
4. class ItemList extends React.Component{
5.
6. constructor(props){
7. super(props)
8. }
9.
10. render(){
11. return(
12. <li>{this.props.product.name} - {this.props.product.price}</li>
13. )
14. }
15. }
16.
17. ItemList.propTypes = {
18. product: PropTypes.shape({
19. name: PropTypes.string.isRequired,
20. price: PropTypes.number.isRequired
21. }).isRequired
22. }
23.
24. export default ItemList

Página | 98
En este ejemplo, el componte ItemList espera una propiedad llamada product,
la cual está definida con una estructura (shape) que debe de tener name de tipo
String y es obligatoria (isRequired), también debe de tener un price de tipo
numérico (number) y también es obligatoria (isRequired).

isRequired es opcional

Si marcamos una propiedad como isRequired, React


validará que el campo haya sido enviado durante la
creación del componente, sin embargo, si no lo pones,
le indicamos que es un parámetro esperado, pero no
es obligatorio.

Por otra parte, el componente App debe de mandar la propiedad product con la
estructura exacta que se está solicitando, respetando los nombre y los tipos de
datos.

1. import React from 'react'


2. import { render } from 'react-dom'
3. import ItemList from './ItemList'
4.
5. class App extends React.Component{
6.
7. render(){
8. return (
9. <ItemList product={ {name: 100, price: 100}} />
10. )
11. }
12. }
13.
14. render(<App/>, document.getElementById('root'));

Observemos que he enviado un 100 en el campo name, el cual es inválido, ya que


se espera un string. Veamos qué pasa cuando ejecutamos la aplicación.

99 | Página
Fig. 20 - Probando los shape propsTypes

Observemos que si bien, la página se muestra correctamente, se lanza un error


en el log que indica que el campo product.name es inválido.

Validación con propTypes

Un error común es creer que con tener propTypes nos


aseguramos de que el usuario siempre mande la
información correcta, lo cual no es correcto, ya que
los propTypes nos sirve como primera línea de defensa
al indicarnos cuando hemos mandado un valor
incorrecto, sin embargo, no puede impedir que
manden valores en tipo o formato incorrecto.

Validaciones avanzadas

Ya hemos visto que es posible validar las propiedades de un componente, para


ayudar a que las personas que utilicen nuestros componentes, envíen las
propiedades correctas, es por ello que React ofrece un sistema muy completo de
validaciones que nos permitirán definir estructuras robustas que validen a la
perfección las propiedades.

Tipos de datos soportados:

La siguiente tabla muestra todos los tipos de datos que es posible validar con
PropTypes.

Tipo de datos Descripción

Página | 100
PropTypes.string Valida que la propiedad sea tipo String
Eje: {name: PropTypes.string}
PropTypes.number Valida que la propiedad sea numérica
Eje: {price: PropTypes.number}
PropTypes.bool Valida que la propiedad sea booleana
Eje: {checked: PropTypes.bool}
PropTypes.object Valida que la propiedad sea un objeto con cualquier
estructura
Eje: {product: PropTypes.object}
PropTypes.objectOf Valida que la propiedad sea un objeto con propiedades
de un determinado tipo
Eje: {tels: PropTypes.objectOf(PropType.string)}
PropTypes.shape Valida que la propiedad sea un objeto de una
estructura determinada
Eje:
{product: PropTypes.shape({
name: PropTypes.string,
price: PropTypes.number
})}
PropTypes.array Valida que la propiedad sea un arreglo
Eje: {tels: PropTypes.array}
PropTypes.arrayOf Valida que la propiedad sea un arreglo de un
terminado tipo de dato
Eje: {tels: PropTypes.arrayOf(PropType.string)}
PropTypes.oneOfType Valida que la propiedad sea de cualquier de los tipos
de datos especificado (es decir, puede ser de uno o de
otro tipo)
Eje:
{tel: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.arrayOf(PropTypes.string)
])}
PropTypes.func Valida que la propiedad sea una función
Eje: {save: PropTypes.func}
PropTypes.node Valida que la propiedad sea cualquier valor que pueda
ser renderizado en pantalla.
Eje: {node: PropType.node}
PropTypes.element Valida que la propiedad sea cualquier elemento de
React
Eje: {element: PropType.element}
PropTypes.instanceOf Valida que la propiedad sea la instancia de una clase
determinada
Eje: { product: PropType.instanceOf(Product) }
PropTypes.oneOf Valida que el valor de la propiedad este dentro de una
lista de valores permitidos (Igual que una
Enumeración)
Eje:
{status: PropType.oneOf([‘ACTIVO’,’INACTIVO’])}
PropTypes.any Le indica a React que la propiedad puede ser de
cualquier tipo
Eje: {object: PropType.any }

101 | Página
DefaultProps

Los DefaultProps son el mecanismo que tiene React para establecer un valor por
default a las propiedades que no fueron definidas en la creación del componente,
de esta forma, podemos establecer un valor y no dejar la propiedad en null.

Imaginemos que el componente <ItemList> que vimos anteriormente, no


recibiera el precio de un producto, se vería muy mal que al usuario le
mostraremos un null, por lo que entonces podríamos definir un valor de cero por
default. Vamos como quedaría:

1. import React from 'react'


2. import PropTypes from 'prop-types'
3.
4. class ItemList extends React.Component{
5.
6. constructor(props){
7. super(props)
8. }
9.
10. render(){
11. return(
12. <li>{this.props.productName} - {this.props.productPrice}</li>
13. )
14. }
15. }
16.
17. ItemList.propTypes = {
18. productName: PropTypes.string.isRequired,
19. productPrice: PropTypes.number
20. }
21.
22. ItemList.defaultProps = {
23. productPrice: 0
24. }
25.
26. export default ItemList

Definir valores por defecto mediante los defaults props, no tiene gran ciencia,
solo es establecer el nombre de la propiedad con el valor por default, solo
recordemos cumplir con la estructura definida propTypes.

Refs

Los Refs o referencias, son la forma que tiene React para hacer referencia a un
elemento de forma rápida, muy parecido a realizar una búsqueda por medio del

Página | 102
método getElementById de JavaScript. Mediante los Ref, es posible agregarles un
identificador único a los elementos para después accederlos fácilmente.

Regresemos al ejemplo del formulario de registro de empleados. Cuando la


pantalla carga, no hay ningún campo con el foco, por lo que queremos que
cuando inicie la página, el campo del nombre de empleado obtenga el foco.
Veamos cómo quedaría:

1. import React from 'react'


2.
3. class EmployeeForm extends React.Component{
4.
5. constructor(){
6. super(...arguments)
7. this.state = {
8. firstName: '',
9. lastName: '',
10. age: ''
11. }
12. }
13.
14. componentDidMount(){
15. this.firstName.focus()
16. }
17.
18. handleChanges(e){
19. let newState = Object.assign(
20. this.state, {[e.target.id]: e.target.value})
21. this.setState(newState)
22. }
23.
24. saveEmployee(e){
25. this.props.save(this.state)
26. }
27.
28. render(){
29. return (
30. <form>
31. <label htmlFor='firstName'>Nombre</label>
32. <input ref={self => this.firstName = self} id='firstName'
33. type='text' value={this.state.firstName}
34. onChange={this.handleChanges.bind(this)}/>
35. <br/>
36. <label htmlFor='lastName'>Apellido</label>
37. <input id='lastName' type='text' value={this.state.lastName}
38. onChange={this.handleChanges.bind(this)}/>
39. <br/>
40. <label htmlFor='age'>Edad</label>
41. <input id='age' type='number' value={this.state.age}
42. onChange={this.handleChanges.bind(this)}/>
43. <br/>
44. <button onClick={this.saveEmployee.bind(this)}>Guardar</button>
45. </form>
46. )
47. }
48. }
49. export default EmployeeForm

103 | Página
Veamos que al campo firstName le hemos agregado el atributo ref (línea 32), el
cual sirve para establecer la referencia al elemento, lo segundo importante es la
línea 14, pues declaramos el método componentDidMount, este método no tiene
un nombre al azar, si no que corresponde con uno de los métodos del ciclo de
vida de React, por lo que el método se ejecuta de forma automática cuando el
componente es mostrado en pantalla. Más adelante analizaremos el ciclo de vida
de un componente, pero por ahora, esta breve explicación deberá ser suficiente.

Cuando el método componentDidMount se ejecuta, obtiene la referencia al campo


productName mediante la instrucción this.firstName y le establece el foco (línea
15).

Por si no te quedo claro que está pasando en la línea 32, te explico, lo que
estamos haciendo con self => this.firstName = self, es definir un Arrow
function, la cual recibe como parámetro la variable self. Cuando React llame
esta arrow function, le mandará como parámetro el elemento que queremos
referenciar, es decir, al mismo elemento (de allí el nombre de la variable self),
luego en el cuerpo de la función, definimos que this.firtName = self, con esto,
estamos creado un variable a nivel de clase llamada firtName y como valor tendrá
self. Finalmente, cuando en la línea 15 queremos recuperar el elemento, nos
vamos sobre la variable de clase this.firtName la cual ya tiene la referencia
directa al elemento.

Referencias con String

En las primeras versiones de React las referencias se creaban mediante String,


es decir, en lugar de hacer el arrow Function, definíamos directamente el nombre
de la referencia, por ejemplo ref=”firtName” y luego para recuperar una
referencia se utilizaba la expresión this.refs.xxx, donde xxx correspondía al
nombre de la referencia, en este caso, this.refs.firtName, sin embargo, este
método ha quedado desaconsejado por el mismo equipo de React, el cual indica
que es posible que termine desapareciendo en el futuro.

Puedes leer más sobre esté método de creación de referencias desde la página
oficial de React, sin embargo, yo solo lo menciono a modo de anécdota, ya que
es funcionalidad que no deberíamos de utilizar más.

Referencias con Hooks

Los hooks es uno de los últimos features que se agregaron a React, los cuales
permiten potenciar los componentes sin estado o de función, sin embargo, hemos
decidido explicar todo lo referente a los hooks en la unidad 13, donde
explicaremos todo con detalle. Dicho lo anterior, cuando trabajo con Hooks las
referencias se utilizan de otra forma, pero las analizaremos cuando llegues a esa
unidad.

Página | 104
Alcance los Refs

Las Refs por medio de String (ref=”firtName” (desaconsejadas) ) tiene un alcance


global en un proyecto y pueden ser accedidos desde cualquier componente sin
importar su posición en la jerarquía, por lo que es común utilizarse para hacer
referencia a elementos globales de una aplicación u elementos que pueden ser
afectados desde diversos puntos de la aplicación.

Por otro lado, las referencia mediante arrow function solo podrán ser accedidas
si tenemos la referencia a la variable a la cual se asignó el elemento, en otro
palabras, this.firtName solo podrá ser accedida por los elementos a los que se
las pase explícitamente esa referencia.

Utilizar referencias con prudencia

Las referencias son una excelente herramienta si se


utilizan con delicadeza. Ya que abusar en el uso de
referencias, puede hacer nuestra aplicación muy
complicada, pues desde donde sea podría afectar
nuestros componentes si tener una referencia clara de
quien los está afectando.

Keys

Los Keys pueden ser un tema avanzado para nosotros en este momento, pues
está relacionado con la optimización de React, sin embargo, quisiera explicarlos
en este momento, pues es un concepto muy simple y es fundamental utilizarlos.

Los keys son utilizados por React para identificar de forma más eficiente los
elementos que han cambiado, agregados o eliminados dentro de la aplicación,
los keys son utilizados únicamente cuando queremos mostrar los datos de un
arreglo, como una lista. En el ejemplo de la lista de productos, iterábamos un
array para mostrar todos los productos dentro de una lista <li>.

Cuando un elemento de React, muestra más de un mismo tipo de elemento, debe


poder identificar qué elementos es cual, para poder actualizarlo correctamente.
El Key debe de ser un valor único en la colección y se utiliza por lo general el ID
o un campo de los ítems que sea único. Veamos cómo quedaría nuestra lista de
productos con Keys:

1. import React from 'react'


2. import { render } from 'react-dom'
3. import ItemList from './ItemList'
4.
5. class App extends React.Component{
6.

105 | Página
7. render(){
8. let items = [{
9. name: 'Item 1',
10. price: 100
11. }, {
12. name: 'Item 2',
13. price: 200
14. }]
15.
16. return (
17. <ul>
18. <For each="item" index='index' of={ items }>
19. <ItemList product={item} key={ ítem.name } />
20. </For>
21. </ul>
22. )
23. }
24. }
25.
26. render(<App/>, document.getElementById('root'));

Como vez, es tan simple como añadir el atributo key y asignarle un valor único
en la colección. Si nosotros ejecutamos este ejemplo, veremos la lista de
siempre, por lo que no hay nada que mostrar, lo interesante se genera, cuando
quitamos el key:

Fig. 21 - Analizando la importancia de los keys

Como podemos ver en la imagen, nos está solicitando un key para cada elemento
de la lista

Qué hacer si no tenemos un campo único para el


Key

Existe ocasiones donde el array no tiene un campo que


identifique a los elementos, en esas ocasiones,
podemos utilizar el Index del array como Key o

Página | 106
podemos utilizar alguna librería que nos genere un
UUID dinámico como la siguiente:
https://www.npmjs.com/package/uuid.

107 | Página
Las 4 formas de crear un Componente

Debido a que React está creado bajo el lenguaje de programación JavaScript,


este también depende de su sintaxis para construir y declarar sus componentes
y es por ello que existen actualmente 4 formas de crear componentes, las cuales
obedecen a las distintas versiones del Estándar ECMAScript.

ECMAScript 5 – createClass (Obsoleta)

Este fue el primer método que existió para crear componentes en React y aun
que ya está bastante anticuado, la realidad es que puedes encontrar mucho
material en internet que todavía utiliza este método. Es muy probable que ya no
te toque trabajar con este método, sin embargo, no está de más mencionarlo y
si te lo llegaras a encontrar, sepas de que están hablando.

Para crear componentes en ECMAScript 5 es necesario utilizar el método


createClass de la clase React, Veamos un ejemplo muy rápido:

1. var miComponent = React.createClass({


2. propTypes: {...},
3. getDefaultProps: {...},
4. getInitialState: {...},
5. render: function(){...}
6. })

Lo primero que podemos ver, es que la clase se crea mediante la función


React.createClass, el cual recibe un objeto que debe de tener al menos la función
render, adicional, se puede declarar los propTypes, defaultProps y el estado
inicial del componente.

ECMAScript 6 - React.Component

Este es el método que hemos estado utilizando hasta ahora, en la cual los
componentes se cran mediante clases que extienden de React.Component. vemos
un ejemplo rápido:

1. import React from 'react'


2.
3. class ECMAScript6Class extends React.Component{
4.
5. constructor(props){
6. super(props)
7. state = {...}
8. }
9.
10. render(){
11. return (
12. ...

Página | 108
13. )
14. }
15. }
16.
17. ECMAScript6Class.propTypes = {
18. ...
19. }
20.
21. ECMAScript6Class.defaultProps = {
22. ...
23. }

Cuando declaramos una clase, es necesario crear el constructor que reciba los
props, para después enviarlos a la superclase, de esta forma iniciamos
correctamente el componente. Los propsTypes y defaultsProps son declarados
fuera de la clase.

ECMAScript 6 - Function Component

La siguiente forma que soporta ECMAScript 6, es la creación de componentes


mediante funciones, y es recomendado únicamente para componentes sin estado
(Stateless). Veamos cómo quedaría:

1. cont MiComponent = ({prop1, prop2, prop2}) => (


2. ...
3. return(
4. ...
5. )
6. )
7. MiComponent.propTypes = {
8. ...
9. }
10. MiComponent.defaultProps = {
11. ...
12. }

Observemos que el componente se reduce a una arrow function, la cual recibe


como parámetros las props. El cuerpo de la función es como el método render,
podremos crear variables, poner un poco de lógica y retornar un JSX. Adicional,
se puede definir los propTypes y defaultProps por fuera de la función.

A partir de la versión 16.8 de React se agregó una nueva funcionalidad llamada


Hooks, los cuales es un tipo de componente de función que permite agregar
estado, sin embargo, este tipo de componentes los analizaremos más adelante y
tendremos una sección especial para ellos.

109 | Página
ECMAScript 7 - React.Component

La última forma disponible, es utilizar las nuevas sintaxis de ECMAScript 7.


Aunque todo apunta a que esta sea la mejor forma de declarar componentes, no
es muy utilizada aun, debido a que ECMAScript 7 todavía se encuentra en
desarrollo. Este método es muy parecido a la creación de componentes mediante
React.Componentes ES6. Veamos cómo quedaría:

1. class MiComponent extends React.Component {


2.
3. static propTypes = {
4. ...
5. }
6.
7. static defaultProps = {
8. ...
9. }
10.
11. state = { ... }
12.
13. constructor(props){
14. super(props)
15. }
16.
17. render(){
18. ...
19. }
20. }

Este método es prácticamente igual que crear una clase en ES6, con la diferencia
que es posible declarar el estado como una propiedad, adicional, podemos
declarar los defaultProps y propTypes dentro de la clase y marcarlos como static
para poder ser accedidos desde fuera sin necesidad de crear una instancia.

Página | 110
Resumen

Este capítulo junto con el anterior, serán claves para el resto de tu aprendizaje
en React, pues hemos visto casi todas las características de React y serán la
base para construir componentes más complejos.

En los siguientes capítulos ya empezaremos de lleno con nuestro proyecto Mini


Twitter, una aplicación muy completa que nos permitirá utilizar gran parte de
todo lo que nos tiene por ofrecer React.

Tener claros todos los conceptos que hemos aprendido hasta ahora, será
claves, por lo que te recomiendo que, si todavía tienes dudas en algunas cosas,
sería buen momento para repasarlos.

111 | Página
Introducción al proyecto Mini
Twitter
Capítulo 5

Hasta este punto, ya hemos visto muchas de las características de React y ya


estamos listo para empezar a desarrollar el proyecto Mini Twitter, el cual es una
réplica de la famosa red social Twitter. Esta app no busca ser una copia de la
aplicación, sino un ejemplo educativo que nos lleve de la mano para construir
una aplicación totalmente funcional utilizando las tecnologías de React, NodeJS
y MongoDB.

Debido a que la aplicación Mini Twitter abarca el desarrollo del FrontEnd y el


BackEnd, organizaremos el libro de tal forma que, primero veremos toda la parte
del FrontEnd, en donde aprenderemos React y utilizaremos Bootstrap como
framework para hacer nuestra aplicación responsiva. Una vez terminada la
aplicación, tendremos un capítulo especial para estudiar Redux y cómo
implementarlo en nuestro proyecto. En segundo lugar, veremos el desarrollo de
un API REST, utilizando NodeJS + Express, el popular framework de NodeJS para
desarrollo web. Adicional, veremos todo lo referente a MongoDB, como crear los
modelos, conectarnos, realizar consultar y actualizar los datos.

Un vistazo rápido al proyecto

Antes de iniciar con el desarrollo del proyecto Mini Twitter, es importante


entender lo que vamos a estar desarrollando, y es por eso que, este capítulo está
especialmente dedicado a ello. Lo primero que haremos será dar un tour por la
aplicación terminada, luego regresaremos para analizar cómo está compuesta, y
finalmente iniciar con el desarrollo.

Página | 112
Página de inicio

La siguiente página corresponde a la página de inicio de Mini Twitter, en la cual


podemos ver el menú bar en la parte superior, los datos del usuario del lado
izquierdo, en el centro tenemos los tweets y del lado derecho, tenemos una lista
de usuarios sugeridos.

Fig. 22 - Página de inicio de Mini Twitter

Perfil de usuario

La siguiente imagen corresponde a la página de perfil de los usuarios, en la cual


puede visualizar un banner, la foto de perfil, su nombre de usuario y una
descripción. En el centro podemos visualizar los tweets del usuario, seguidores o
los que lo siguen, del lado derecho, tenemos nuevamente usuario sugeridos.

113 | Página
Fig. 23 - Página de perfil de Mini Twitter

Editar perfil de usuario

La página de perfil puede cambiar de estado, de solo lectura a editable, la cual


permite cambiar la foto de perfil, banner, nombre y la descripción:

Fig. 24 - Perfil de usuario en modo edición.

Observemos que el banner, cómo la foto, cambian, agregando un ícono de una


cámara, la cual, al poner el mouse encima, se subrayada de color naranja,
habitando cargar una foto nueva con tan solo hacer clic. También, donde aparece
el nombre de usuario y descripción, pasan a ser editables.

Página | 114
Página de seguidores

La siguiente foto corresponde a la sección de seguidores, y las personas que nos


siguen. Estas dos secciones se ven de la misma forma, con la única diferencia
que la primera muestra todas las personas que nos siguiente dentro de la red
social, mientras que la segunda, muestra solo las personas que hemos decidido
seguir.

Fig. 25 - Sección de seguidores de Mini Twitter

Detalle del Tweet

También es posible ver el detalle de cada Tweet, para ver los comentarios que
tiene y agregar nuevos.

Fig. 26 - Detalle de un Tweet en Mini Twitter

115 | Página
Inicio de sesión (Login)

Otra de las páginas que cuenta la aplicación son las clásicas pantallas de iniciar
sección (login), desde la cual es posible autenticarse ante la aplicación mediante
usuario y password:

Fig. 27 - Inicio de sesión de Mini Twitter

Registro de usuarios (Signup)

Mediante esta página, es posible crear una nueva cuenta para poder acceder a
la aplicación.

Página | 116
Fig. 28 - Página de registro de Mini Twitter

Hemos visto un recorrido rápido a lo que será la aplicación de Mini Twitter, pero
la aplicación es engañosa, porque tiene muchas más cosas de las que podemos
ver a simple vista, las cuales tenemos que analizar mucho más a detalle. Es por
ese motivo que, una vez que dimos un tour rápido de la aplicación, es hora de
verla con rayos X y ver cómo es que la aplicación se compone y todos los
componentes que vamos a requerir para crear la aplicación.

Análisis al prototipo del proyecto

En esta sección analizaremos con mucho detalle todos los componentes que
conforman la aplicación Mini Twitter. Pero antes de eso, será interesante regresar
al diagrama donde se muestra la jerarquía de los componentes que conformará
el proyecto:

117 | Página
En las siguientes secciones comenzaremos a hablar sobre estos componentes,
por lo que tener esta imagen a la mano te servirá mucho para entender cómo es
que los componentes se van a ir creado y anidando.

Componente TwitterDashboard

TwitterDashboard es la página de inicio para un usuario autenticado, desde la


cual se visualizan los últimos Tweets de los usuarios, también es posible ver un
pequeño resumen de tu perfil (lazo izquierdo) y una lista de usuario sugeridos
para seguir (lado derecho).

Fig. 29 - Topología de la página de inicio

Página | 118
En la imagen anterior, podemos ver con más detalle cómo está compuesta la
página de inicio. A simple vista, es posible ver 5 componentes, los cuales son:

• TwitterDashboard: Es un componente contenedor, pues alberga al


resto componentes que podemos ver en pantalla.
• Toolbar: Componente que muestra al usuario autenticado.
• Profile: Componente que muestra los datos del usuario autenticado,
como foto, número de Tweets, número de suscriptores y personas que lo
sigue.
• TweetsContainer: Es un componente contendedor que muestra una
serie de Tweet, los cuales veremos con detalle más adelante.
• SuggestedUser: Muestra una lista de usuario sugeridos para seguir.

Adicional a los componentes que podemos ver en pantalla, existe uno más
llamado TwitterApp, el cual envuelve toda la aplicación, e incluyendo los demás
componentes, como las páginas de login, signup, y el perfil del usuario.

Componente TweetsContainer

Hora daremos un zoom al componente TweetsContainer para ver cómo está


compuesto:

119 | Página
Fig. 30 - Topología del componente TweetsContainer

Como podemos apreciar en la imagen, el componente está conformado del


componente Reply, el cual sirve para crear un nuevo Tweet. Adicional, es posible
ver una lista de componentes Tweet, que corresponde a cada tweet de los
usuarios.

Componente UserPage

La página de perfil, permite a los usuarios ver su perfil y ver el perfil de los demás
usuarios. Este componente se muestra de dos formas posibles, ya que si estás
en tu propio los datos siempre y cuando estés en tu propio perfil. Por otra parte,
si estas en el perfil de otro usuario, te dará la opción de seguirlo.

Página | 120
Fig. 31 - Topología de la página UserPage

En esta página es posible ver varios componentes que se reúnen para formar la
página:

• UserPage: Es un componente contenedor, pues alberga al resto de


componentes, como son SuggestedUsers, Followers, Followings y
TweetsContainer.
• TweetsContainer: Este componente ya lo analizamos y aquí solo lo
reutilizamos.
• SuggestedUsers: Nuevamente, este componente ya lo analizamos y
solamente lo reutilizado.
• Followings: Este componente se muestra solo cuando presionamos el
tab “siguiendo”, el cual muestra un listado de todas las personas que
seguimos.
• Followers: Igual que el anterior, solo que esta muestra nuestros
seguidores.

Componente Signup

Este es el formulario para crear un nuevo usuario, el cual solo solicita datos
mínimos para crear un nuevo perfil.

121 | Página
Fig. 32 - Topología de la página Signup

La página de login está compuesta únicamente por el componente Login y


Toolbar.

Componente Login

La página de Login es bastante parecida a la de Signup, es solo un formulario


donde el usuario captura su usuario y password para autenticarse.

Fig. 33 - Topología de la página Login

Página | 122
Hasta este momento, hemos visto los componentes principales de la aplicación,
lo que falta son algunas componentes de popup y compontes secundarios en los
cuales no me gustaría nombrar aquí, pues no quisiera entrar en mucho detalle
para no perdernos. Por ahora, con que tengamos una idea básica de cómo está
formada la aplicación será más que suficiente y a medida que entremos en los
detalles, explicaremos los componentes restantes.

Jerarquía de los componentes del proyecto

Tal vez recuerdes que en el capítulo pasado hablamos acerca de la jerarquía de


componentes, pues en este capítulo mostraremos la jerarquía completa del
proyecto. La idea es que puedas imprimir esta imagen o guardarla en un lugar
accesible, ya que nos servirá muchísimo para entender cómo vamos a ir armando
el proyecto, así de cómo vamos a reutilizar los componentes.

Fig. 34 - Jerarquía de componentes de Mini Twitter

La imagen anterior, nos da una fotografía general de toda la aplicación Mini


Twitter, en la cual podemos ver cómo está compuesto cada componente, así
como también, podemos aprecias donde reutilizamos los componentes.

123 | Página
El enfoque Top-down & Bottom-up

Uno de los aspectos más importantes cuando vamos a desarrollar una nueva
aplicación, es determina el orden en que vamos a construir los componentes,
pues la estrategia que tomemos, repercutirá en la forma que vamos a trabajar.
Es por este motivo que vamos a presentar el en enfoque Top-down y Bottom-up
para analizar sus diferencias y las ventajas que traen cada una.

Top-down

Este enfoque consiste en empezar a construir los componentes de más arriba en


la jerarquía y continuar desarrollando los componentes hacia abajo. Este este es
el enfoque más simple y que es utilizado con más frecuencia por desarrolladores
inexpertos o proyectos donde no hubo una fase de análisis que ayudara a
identificar los componentes necesarios y su jerarquía.

Fig. 35 - El enfoque Top-down

En este enfoque se requiere poco o nula planeación, pues se empieza a construir


de lo menos especifico o lo más específico, de esta manera, vamos construyendo
los componentes a como los vamos requiriendo en el componente padre. El
inconveniente de este enfoque, es que es muy propenso a la refactorización, pues
a medida que vamos descendiendo en la jerarquía, vamos descubriendo datos
que requeríamos arrastrar desde la parte superior, también descubrimos que
algunos componentes que ya desarrollamos pudieron ser reutilizados, por lo que
se requiere refactorizar para reutilizarlos o en el peor de los casos, hacemos un
componente casi idéntico para no modificar el trabajo que ya tenemos echo. Otra
de las desventajas, es que casi cualquier dependencia a otros componentes que
requiera algún componente, no existirá y tendremos que irlos creando al vuelo.

Página | 124
Bottom-up

Este otro enfoque es todo lo contrario que Top-down, pues propone empezar con
los componentes más abajo en la jerarquía, de esta forma, iniciamos con los
componentes que no tienen dependencias y vamos subiendo en la jerarquía hasta
llegar al primer componente en la jerarquía.

Fig. 36 - El enfoque Bottom-up

El enfoque Bottom-up requiere de una planeación mucho mejor, en la cual salgan


a relucir el mayor número de componentes requeridos para el desarrollo, también
se identifica la información que requiere cada componente y se va contemplando
a medida que subimos en la jerarquía.

Este enfoque es utilizado por los desarrolladores más experimentados, que son
capases de analizar con buen detalle la aplicación a desarrollar. Un mal análisis
puede hacer que replanteemos gran parte de la estructura y con ellos, se genera
un gran impacto en el desarrollo.

Bien ejecutado, reduce drásticamente la necesidad de realizar refactor, también


ayuda a identificar todos los datos y servicios que será necesarios para la
aplicación en general.

El enfoque utilizado y porque

Aunque lo mejor sería utilizar el enfoque Botton-up, la realidad es que apenas


estamos aprendiendo a utilizar React, por lo que aventurarnos a utilizar este
enfoque puede ser un poco riesgoso y nos puede complicar más el aprendizaje.
Es por este motivo que en este libro utilizaremos el enfoque Top-down he iremos
construyendo los componentes a como sea requeridos.

125 | Página
El API REST del proyecto Mini Twitter

El proyecto Mini Twiter es una aplicación que la podemos dividir en dos partes,
la primera parte corresponde al Frontend, que es lo que hemos estado haciendo
con React, pero esta la otra parte, que corresponde al Backend, desde la cual
exponer un API REST que podrá utilizar nuestra aplicación.

Debido a que la parte del Backend la dejaremos para el final, es importante tener
un API que consumir en lo que llegamos hasta allí, por lo que de momento
consumiremos un API REST que he expuesto de forma pública en internet, el cual
es compartido entre todos los lectores y a medida que avancemos hasta el final
del libro, reemplazaremos el API de la nube por el API que nosotros mismo
construiremos.

Dicho lo anterior, comenzaremos con abrir la siguiente URL desde el navegador:

https://minitwitterapi.reactiveprogramming.io/

Al abrirla veremos una página como la siguiente:

Esto que estamos viendo ahora mismo, es la página de bienvenida del API REST
del proyecto Mini Twitter, así que tomate un minuto para leer para leer los
términos de uso antes de continuar.

Para ver los servicios que expone el API daremos click en el botón “Ver
documentación”, lo que nos llevará a la siguiente página:

Página | 126
Esta nueva página aporta muchísima información para entender lo que expone
el API, ya que nos dice el nombre y la descripción de cada servicio, así como el
método o verbo HTTP por el cual responde (verde), así como la URL en la cual
está disponible el servicio (azul). Finalmente, los servicios que están protegidos
con seguridad están marcado con una etiqueta roja que dice “secure”.

Además, de toda esta información que tenemos disponible de una forma simple
y clara, siempre podemos dar click en cualquier de los servicios para ver su
detalle.

127 | Página
En esta nueva página podemos ver toda la información anterior, más ejemplos
del formato esperado para la solicitud, un ejemplo de un request válido, una
respuesta exitosa y un ejemplo de una respuesta con error.

Con esta información es sumamente fácil consumir un servicio, pues sabemos de


antemano que enviarle y que es lo que esperamos como respuesta.

Por ahora te recomendaría que te tomaras unos minutos en navegar por el API
para que te familiarices un poco con los servicios expuestos, ya que a medida
que avancemos en el proyecto los iremos utilizando, sin embargo, cada vez que
necesitemos uno nuevo, lo mencionaré para que podamos analizarlo con más
detalle.

Finalmente, podemos probar que el API REST funcione al consultar los últimos
tweets, para eso, solo basta con abrir la siguiente URL en el navegador:

https://minitwitterapi.reactiveprogramming.io/tweets

Página | 128
Invocando el API REST desde React

Una vez que hemos analizado el API y los servicios disponibles, pasaremos a
explicar cómo podemos consumirlo desde React. Para esto, vamos a utilizar
fetch, la cual se utiliza para consumir cualquier recurso desde la WEB mediante
HTTP, por lo que es posible consumir servicios REST mediante el método POST,
GET, PUT, DELETE y PATCH, etc. Esta función recibe dos parámetros para funcionar,
el primero corresponde a la URL en la que se encuentre el servicio y el segundo
parámetro corresponde a los parámetros de invocación, como el Header y el Body
de la petición.

Veamos un ejemplo simple de cómo consumir los Tweets de un usuario. Para


esto deberemos crear una nueva clase en llamada APIInvoker.js en el directorio
/app/utils/. El archivo se ve la siguiente manera:

1. class APIInvoker {
2.
3. invoke(url, okCallback, failCallback){
4. fetch(`https://minitwitterapi.reactiveprogramming.io${url}`)
5. .then((response) => {
6. return response.json()
7. })
8. .then((responseData) => {
9. if(responseData.ok){
10. okCallback(responseData)
11. }else{
12. failCallback(responseData)
13. }
14. })
15. }
16. }
17. export default new APIInvoker();

Lo primero a tomar en cuenta es la función invoke (línea 3), que servirá para
consumir servicios del API de una forma reutilizable. Esta función recibe 4
parámetros obligatorios:

1. url: String que representa el recurso que se quiere consumir, sin contener
el host y el puerto, ejemplo “/tweets”.
2. okCallback: deberá ser una función, la cual se llamará solo en caso de que
el servicio responda correctamente. La función deberá admitir un
parámetro que representa la respuesta del servicio.
3. failCallback: funciona igual al anterior, solo que este se ejecuta cuando el
servicio responde con algún error.

De estos cuatro parámetros solo dos son requeridos, la url y params. Cuando el
servicio responde, la respuesta es procesada mediante una promesa (Promise),
es decir, una serie de then, los cuales procesan la respuesta por partes. El primer
then (línea 5) convierte la respuesta en un objeto json (línea 6) y lo retorna para
ser procesado por el siguiente then (línea 8), el cual, valida la propiedad ok de la

129 | Página
respuesta, si el valor de esta propiedad es true, indica que el servicio termino
correctamente y llamada la función okCallback, por otra parte, si el valor es
false, indica que algo salió mal y se ejecuta la función failCallback.

Finalmente exportamos una nueva instancia de la clase APIInvoker en la línea


17, con la finalidad de poder utilizar el objeto desde los componentes de React.

El siguiente paso será probar nuestra clase con un pequeño ejemplo que consulte
los Tweets de nuestro usuario de pruebas, para esto modificaremos la clase App
para dejarla de la siguiente manera:

1. import React from 'react'


2. import { render } from 'react-dom'
3. import APIInvoker from "./utils/APIInvoker"
4.
5. class App extends React.Component{
6.
7. constructor(props){
8. super(props)
9. this.state = {
10. tweets: []
11. }
12.
13. APIInvoker.invoke('/tweets, response => {
14. this.setState({
15. tweets: response.body
16. })
17. },error => {
18. console.log("Error al cargar los Tweets", error);
19. })
20. }
21.
22. render(){
23. console.log(this.state.tweets);
24. return (
25. <ul>
26. <For each="tweet" of={this.state.tweets}>
27. <li key={tweet._id}>{tweet._creator.name}: {tweet.message}</li>
28. </For>
29. </ul>
30. )
31. }
32. }
33.
34. render(<App/>, document.getElementById('root'));

No entraremos en los detalles del todo el componente, pues nos es el objetivo


de esta sección, por lo que solo nos centraremos en constructor del componente,
donde realizaremos la llamada al API REST (línea 13) para consultar los últimos
Tweets. Para eso, invocaremos el método invokeGET de la clase APIInvoker y le
enviaremos la URL del servicio para consultar los últimos Tweets (/tweets), el
segundo parámetros será la función que se ejecutará en caso de éxito, desde la
cual actualizamos el estado del componente con la respuesta del servicio, el
tercer parámetro es la función de error, que utilizaremos para imprimir en la
consola cualquier error.

Página | 130
Finalmente, los Tweets retornados son mostrados en el método render con ayuda
de un <For> para iterar los resultados.

Mejorando la clase APIInvoker

Hasta este punto ya sabemos cómo invocar el API REST desde React, sin
embargo, necesitamos mejorar aún más nuestra clase APIInvoker para
reutilizarla en todo el proyecto.

Lo primero que aremos será quitar todas las secciones Hardcode, como lo son el
host y el puerto y enviarlas a un archivo de configuración externo llamado
config.js, el cual deberemos crear justo en la raíz del proyecto, es decir a la
misma altura que los archivos package.json y webpack.config.js:

Fig. 37 - Archivo config.js

El archivo deberá quedar de la siguiente manera:

1. module.exports = {
2. debugMode: false,
3. api: {
4. host: "https://minitwitterapi.reactiveprogramming.io"
5. },
6. tweets: {
7. maxTweetSize: 140
8. }
9. }

Una vez que tenemos el archivo de configuración, deberemos de modificar el


archivo APIInvoker para que utilice estas configuraciones:

131 | Página
1. var configuration = require('../../config')
2. const debug = configuration.debugMode
3.
4. class APIInvoker {
5.
6. getAPIHeader(){
7. return {
8. 'Content-Type': 'application/json',
9. authorization: window.localStorage.getItem("token"),
10. }
11. }
12.
13. invokeGET(url, okCallback, failCallback){
14. let params = {
15. method: 'get',
16. headers: this.getAPIHeader()
17. }
18. this.invoke(url, okCallback, failCallback,params);
19. }
20.
21. invokePUT(url, body, okCallback, failCallback){
22. let params = {
23. method: 'put',
24. headers: this.getAPIHeader(),
25. body: JSON.stringify(body)
26. };
27.
28. this.invoke(url, okCallback, failCallback,params);
29. }
30.
31. invokePOST(url, body, okCallback, failCallback){
32. let params = {
33. method: 'post',
34. headers: this.getAPIHeader(),
35. body: JSON.stringify(body)
36. };
37.
38. this.invoke(url, okCallback, failCallback,params);
39. }
40.
41. invoke(url, okCallback, failCallback,params){
42. if(debug){
43. console.log("Invoke => " + params.method + ":" + url );
44. console.log(params.body);
45. }
46.
47. fetch(`${configuration.api.host}${url}`, params)
48. .then((response) => {
49. if(debug){
50. console.log("Invoke Response => " );
51. console.log(response);
52. }
53. return response.json()
54. })
55. .then((responseData) => {
56. if(responseData.ok){
57. okCallback(responseData)
58. }else{
59. failCallback(responseData)
60. }
61.
62. })
63. }
64. }
65. export default new APIInvoker();

Página | 132
Como vemos, la clase creció bastante a como la teníamos originalmente, lo que
podría resultar intimidador, pero en realidad es más simple de lo que parece.
Analicemos los cambios. Se han agregado una serie de métodos adicionales al
método invoke, pero observemos que todos se llaman invoke + un método de
HTTP, los cuales son:

• invokeGET: Permite invocar un servicio con el método GET


• InvokePUT: Permite invocar un servicio con el método PUT
• InvokePOST: Permite invocar un servicio con el método POST

Si nos vamos al detalle de cada uno de estos métodos, veremos que en realidad
contienen lo mismo, ya que lo único que hacen es crear los parámetros HTTP,
como son los headers y el body, para finalmente llamar al método invoke (sin
postfijo). La única diferencia que tienen estas funciones es que el método
invokeGET no requiere un body.

Otro punto interesante a notar es que cuando definimos los header, lo hacemos
mediante la función getAPIHeader, la cual retorna el Content-Type y una
propiedad llamada authorization. No entraremos en detalle acerca de esta
propiedad, pues lo analizaremos más adelante cuando veamos la parte de
seguridad.

Finalmente, hemos realizado algunos cambios en la función invoke, lo primero


que podemos apreciar, son dos bloques de debug (líneas 42 y 49) las cuales nos
permite imprimir en el log la petición y la respuesta de cada invocación. Lo
segundo interesante es que en la línea 47 hemos remplazado la URL del API REST
por los valores configurados en el archivo config.js.

En este punto tenemos lista la clase APIInvoker para ser utilizada a lo largo de
toda la implementación del proyecto Mini Twitter, Y ya solo nos restaría ajustar
la clase App.js para reflejar estos últimos cambios, por lo que vamos a modificar
únicamente el constructor para que se vea de la siguiente manera:

1. constructor(){
2. . . .
3. APIInvoker.invokeGET('/tweets, response => {
4. this.setState({
5. tweets: response.body
6. })
7. },error => {
8. console.log("Error al cargar los Tweets", error);
9. })
10. }

Podemos apreciar que ahora las invocaciones indican el método HTTP que
estamos utilizando en cada llamada.

133 | Página
El componente TweetsContainer

Finalmente ha llegado el momento de iniciar con la construcción del proyecto Mini


Twitter, por lo que iniciaremos con la clase TweetsContainer la cual se encarga
de cargar todos los Tweets. Veamos nuevamente la imagen de la estructura del
proyecto para entender dónde va este componente y saber qué es lo que estamos
programando.

Fig. 38 - Ubicando el componente TweetsContainer

En la siguiente imagen podemos observar una fotografía más detallada del


componente TweetsContainer, en el cual se puedan apreciar los componentes
secundarios que conforman a este.

Página | 134
Fig. 391 - Repaso a la estructura del proyecto.

Este componente tiene dos responsabilidades, la primer, es cargar los Tweets


desde el API y la segunda, es funcionar como un contenedor para ver todos los
Tweets que retorne el API, así como al componente Reply, el cual analizaremos
más adelante.

Lo primero que haremos será crear el archivo TweetsContainer.js en el path


/app, es decir, a la misma altura que el archivo App.js y lo dejaremos de la
siguiente manera:

1. import React from 'react'


2. import APIInvoker from "./utils/APIInvoker"
3. import PropTypes from 'prop-types'
4.
5. class TweetsContainer extends React.Component{
6. constructor(props){
7. super(props)
8. this.state = {
9. tweets: []
10. }
11. let username = this.props.profile.userName
12. let onlyUserTweet = this.props.onlyUserTweet
13. this.loadTweets(username, onlyUserTweet)
14. }
15.
16. loadTweets(username, onlyUserTweet){
17. let url = '/tweets' + (onlyUserTweet ? "/" + username : "")
18. APIInvoker.invokeGET(url , response => {
19. this.setState({
20. tweets: response.body
21. })
22. },error => {
23. console.log("Error al cargar los Tweets", error);
24. })
25. }
26.
27. render(){
28.
29. return (
30. <main className="twitter-panel">
31. <If condition={this.state.tweets != null}>
32. <For each="tweet" of={this.state.tweets}>
33. <p key={tweet._id}>{tweet._creator.userName}
34. : {tweet._id}-{tweet.message}</p>
35. </For>
36. </If>
37. </main>
38. )
39. }
40. }
41.
42. TweetsContainer.propTypes = {
43. onlyUserTweet: PropTypes.bool,
44. profile: PropTypes.object
45. }
46.
47. TweetsContainer.defaultProps = {
48. onlyUserTweet: false,
49. profile: {
50. userName: ""
51. }

135 | Página
52. }
53.
54. export default TweetsContainer;

Lo primero interesante a resaltar es el constructor, la cual recupera dos


propiedades, username y onlyUserTweet, necesarias para pasar a la función
loadTweets que cargará los Tweet iniciales. Cabe mencionar que este componente
puede mostrar Tweet de dos formas, es decir, puede mostrar los Tweet de todos
los usuarios de forma cronológica o solo los Tweet del usuario autenticado, y ese
es el motivo por el cual son necesarios estas dos propiedades. Si la propiedad
onlyUserTweet es true, le indicamos al componente que muestre los Tweet del
usuario autenticado, de lo contrario, mostrara los Tweet de todos los usuarios.
Más adelante veremos la importancia de hacerlo así para reutilizar el
componente.

Cuando el componente es creado por primera vez, el constructor es ejecutado,


lo que dispara la carga de Tweets mediante el método loadTweets, que a su vez,
utilizará la clase APIInvoker para cargarlos desde el API REST, pero antes, la URL
deberá ser generada (línea 17), Si requerimos los Tweets de todos los usuarios,
entonces invocamos el API con la URL “/tweets”, pero si requerimos los Tweets
de un usuario en específico, entonces invocamos la URL “/tweets/{username}”.

Prestemos atención en la parte {username}, pues esta parte de la URL es en


realidad un parámetro, y podría recibir el nombre de usuario de cualquier usuario
registrado en la aplicación.

Usuarios registrados

Al inicio solo tendremos el usuario test, por lo que de


momento únicamente podremos consultar los Tweets
de este usuario. Más adelante implementaremos el
registro de usuarios para poder hacer pruebas con más
usuarios.

Documentación del servicio de Tweets

Los servicios de consulta de Tweets se utilizan para


recuperar todos los Tweets de forma cronológica
(/tweets) y todos los de un determinado usuario
(/tweets/:username).

En las líneas 32 a 35 podemos ver cómo se Iteran todos los Tweets consultados
para mostrar el nombre de usuario del Tweet, el ID y el texto del Tweet.

Página | 136
Al final del archivo también podemos apreciar que hemos definidos los PropTypes
y los DefaultProps correspondientes a las propiedades onlyUserTweet y profile.
El primero ya lo hemos mencionado, pero el segundo corresponde al usuario
autenticado en la aplicación. De momento no nos preocupemos por esta
propiedad, más adelante regresaremos a analizarla.

Una vez terminado de editar el archivo TweetsContainer, regresaremos al


componente App.js y actualizaremos la función render para que se vea de la
siguiente manera:

1. render() {
2. return (
3. <div className="container">
4. <TweetsContainer />
5. </div>
6. )
7. }

También será necesario agregar el import del componente TweetsContainer al


inicio de la clase.

1. import TweetsContainer from './TweetsContainer'

Ya con estos últimos pasos actualizamos el navegador y veremos los tweets

Fig. 40 - Resultado del componente TweetsContainer.

El componente Tweet

Hasta este momento solo representamos algunos campos del Tweet para poder
comprobar que el componente TweetsContainer está consultando realmente los
datos desde el API REST, por lo que ahora nos concentraremos en el componente
Tweet, el cual utilizaremos pare representar los Tweets en pantalla.

137 | Página
Lo primero que haremos será crear un nuevo archivo llamado Tweet.js en el path
/app y lo dejaremos de la siguiente manera:

1. import React from 'react'


2. import PropTypes from 'prop-types'
3.
4. class Tweet extends React.Component {
5.
6. render() {
7. let tweet = this.props.tweet
8. let tweetClass = null
9. if (this.props.detail) {
10. tweetClass = 'tweet detail'
11. } else {
12. tweetClass = tweet.isNew ? 'tweet fadeIn animated' : 'tweet'
13. }
14.
15. return (
16. <article className={tweetClass} id={"tweet-" + tweet._id}>
17. <img src={tweet._creator.avatar} className="tweet-avatar" />
18. <div className="tweet-body">
19. <div className="tweet-user">
20. <a href="#">
21. <span className="tweet-name" data-ignore-onclick>
22. {tweet._creator.name}</span>
23. </a>
24. <span className="tweet-username">
25. @{tweet._creator.userName}</span>
26. </div>
27. <p className="tweet-message">{tweet.message}</p>
28. <If condition={tweet.image != null}>
29. <img className="tweet-img" src={tweet.image} />
30. </If>
31. <div className="tweet-footer">
32. <a className={tweet.liked ? 'like-icon liked' : 'like-icon'}
33. data-ignore-onclick>
34. <i className="fa fa-heart " aria-hidden="true"
35. data-ignore-onclick></i> {tweet.likeCounter}
36. </a>
37. <If condition={!this.props.detail} >
38. <a className="reply-icon" data-ignore-onclick>
39. <i className="fa fa-reply " aria-hidden="true"
40. data-ignore-onclick></i> {tweet.replys}
41. </a>
42. </If>
43. </div>
44. </div>
45. <div id={"tweet-detail-" + tweet._id} />
46. </article>
47. )
48. }
49. }
50.
51. Tweet.propTypes = {
52. tweet: PropTypes.object.isRequired,
53. detail: PropTypes.bool
54. }
55.
56. Tweet.defaultProps = {
57. detail: false
58. }
59. export default Tweet;

Página | 138
Este no es un libro HTML ni de CSS, por lo que no nos detendremos en explicar
para qué es cada clase de estilo utilizada ni la estructura del HTML generado,
salvo en ocasiones donde tiene una importancia relacionada con el tema en
cuestión.

Lo primero que debemos de resaltar es que este componente recibe dos


propiedades, la primera representa el objeto Tweet como tal y un boolean, que
indica si el Tweet debe mostrar el detalle, es decir, los comentarios relacionados
al Tweet.

Con la función render ya nos podemos dar una idea de que este es un
componente más complejo, pues retorna un JSX con varios elementos, por lo que
vamos a desmenuzarlo por partes.

En la línea 7 recuperamos el Tweet de las propiedades y la asignamos a una


variable local para accederla de una forma más simple, evitando tener que
accederla desde las propiedades y hacer el código más verboso.

Entre las líneas 8 y 13 solo determinamos las clases de estilo que vamos a ponerle
al Tweet. Dependiendo si el Tweet es nuevo ('tweet fadeIn animated'), si ya
existía con anterioridad ('tweet') o si queremos ver su detalle ('tweet detaill'),
será la clase de estilo que pondremos.

Quiero que veas que cada Tweet es englobado como un <article> para darle
más semántica al HTML generado. Cada article tendrá un ID generado a partir
del ID del Tweet (línea 16), de esta forma podremos identificar el Tweet más
adelante.

En la línea 17 definimos una imagen, la cual corresponde al Avatar del usuario


que publico el Tweet. De esta misma forma definimos el nombre (línea 22),
nombre de usuario (línea 25), el mensaje de tweet (línea 27) y la imagen
asociada al Tweet, siempre y cuando tenga imagen (línea 19).

Las líneas 31 a 43 son las que muestran los iconos de like y compartir en la parte
inferior del Tweet. De momento no tendrá funcionalidad, pero más adelante
regresaremos para implementarla.

En la línea 45 tenemos un div con solo un ID, este lo utilizaremos más adelante
para mostrar el detalle del Tweet, por lo pronto no lo prestemos atención.

En este punto el componente Tweet ya debería estar funcionando correctamente,


sin embargo, hace falta mandarlo llamar desde el componente TweetsContainer,
para esto regresaremos al archivo TweetsContainer.js y editaremos solamente
el método render para dejarlo de la siguiente manera:

1. render(){
2. return (
3. <main className="twitter-panel">
4. <If condition={this.state.tweets != null}>
5. <For each="tweet" of={this.state.tweets}>
6. <Tweet key={tweet._id} tweet={tweet}/>
7. </For>
8. </If>
9. </main>
10. )

139 | Página
11. }

Adicional tendremos que agregar el import al componente al inicio del archivo:

1. import Tweet from './Tweet'

Como último paso tendremos que agregar las clases de estilo CSS al archivo
styles.css que se encuentra en el path /public/resources/css/styles.css. Solo
agreguemos lo siguiente al final del archivo:

1. /** TWEET COMPONENT **/


2.
3. .tweet{
4. padding: 10px;
5. border-top: 1px solid #e6ecf0;
6. }
7.
8.
9. .tweet .tweet-link{
10. position: absolute;
11. display: block;
12. left: 0px;
13. right: 0px;
14. top: 0px;
15. bottom: 0px;
16. }
17.
18. .tweet:hover{
19. background-color: #F5F8FA;
20. cursor: pointer;
21. }
22.
23. .tweet.detail{
24. border-top: none;
25. }
26.
27. .tweet.detail:hover{
28. background-color: #FFF;
29. cursor: default;
30. }
31.
32.
33. .tweet .tweet-img{
34. max-width: 100%;
35. border-radius: 5px;
36. }
37.
38. .tweet .tweet-avatar{
39. border: 1px solid #333;
40. display: inline-block;
41. width: 45px;
42. height: 45px;
43. position: absolute;
44. border-radius: 5px;
45. text-align: center;
46. }
47.
48. .tweet .tweet-body{
49. margin-left: 55px;

Página | 140
50. }
51.
52. .tweet .tweet-body .tweet-name{
53. color: #333;
54. }
55.
56. .tweet .tweet-body .tweet-name{
57. font-weight: bold;
58. text-transform: capitalize;
59. margin-right: 10px;
60. z-index: 10000;
61. }
62.
63. .tweet .tweet-body .tweet-name:hover{
64. text-decoration: underline;
65. }
66.
67. .tweet .tweet-body .tweet-username{
68. text-transform: lowercase;
69. color: #999;
70. }
71.
72. .tweet.detail .tweet-body .tweet-user{
73. margin-left: 70px;
74. }
75.
76. .tweet.detail .tweet-body{
77. margin-left: 0px;
78. }
79.
80. .tweet.detail .tweet-body .tweet-name{
81. font-size: 18px;
82. }
83.
84. .tweet-detail-responses .tweet.detail .tweet-body .tweet-message{
85. font-size: 16px;
86. }
87.
88. .tweet.detail .tweet-body .tweet-username{
89. display: block;
90. font-size: 16px;
91. }
92.
93. .tweet.detail .tweet-message{
94. position: relative;
95. display: block;
96. margin-top: 25px;
97. font-size: 26px;
98. left: 0px;
99. }
100.
101. .reply-icon,
102. .like-icon{
103. color: #999;
104. transition: 0.5s;
105. padding-right: 40px;
106. font-weight: bold;
107. font-size: 18px;
108. z-index: 99999;
109. }
110.
111. .like-icon:hover{
112. color: #E2264D;
113. }
114.
115. .like-icon.liked{

141 | Página
116. color: #E2264D;
117. }
118.
119. .reply-icon:hover{
120. color: #1DA1F2;
121. }
122.
123. .like-icon i{
124. color: inherit;
125. }
126.
127. .reply-icon i{
128. color: inherit;
129. }

Guardamos todos los cambios y refrescar el navegador para apreciar cómo va


tomando forma el proyecto.

Fig. 41 - Componente Tweet

Finalmente, nuestro proyecto se debe ver de la siguiente manera:

Página | 142
Fig. 42 - Estructura actual del proyecto

Una cosa más, antes de concluir este capítulo, hasta el momento hemos
trabajado con la estructura de los Tweets retornados por el API REST, pero no
los hemos analizado, es por ello que dejo a continuación un ejemplo del JSON
que retorna el API.

1. {
2. "ok":true,
3. "body":[
4. {
5. "_id":"598f8f4cd7a3b239e4e57f3b",
6. "_creator":{
7. "_id":"598f8c4ad7a3b239e4e57f38",
8. "name":"Usuario de prueba",
9. "userName":"test",
10. "avatar":""
11. },
12. "date":"2017-08-12T23:29:16.078Z",
13. "message":"Hola mundo desde mi tercer Tweet",
14. "liked":false,
15. "likeCounter":0,
16. "replys":0,
17. "image":null
18. }
19. ]
20. }

Lo primero que vamos a observar de aquí en adelante es que todos los servicios
retornados por el API tienen la misma estructura base, es decir, regresan un
campo llamado ok y un body, el ok nos indica de forma booleana si el resultado

143 | Página
es correcto y el body encapsula todo el mensaje que nos retorna el API. Si
regresamos al componente TweetContainer en la línea 21 veremos lo siguiente:

1. this.setState({
2. tweets: response.body
3. })

Observemos que el estado lo crea a partir del body y no de todo el retorno del
API.

Ya que conocemos la estructura general del mensaje, podemos analizar los


campos del Tweet, los cuales son:

• Id: Identificador único en la base de datos.


• _creator: Objeto que representa el usuario que creo el Tweet
o _id: Identificador único del usuario en la base de datos.
o Name: Nombre del usuario
o userName: Nombre de usuario (utilizado para el login)
o avatar: Representación en Base 64 de la imagen del Avatar.
• Date: fecha de creación del Tweet.
• Message: El mensaje que escribió el usuario en el Tweet.
• Liked: Indica si el usuario autenticado le dio like al Tweet.
• likeCounter: número de likes totales recibidos en este Tweet.
• Reply: número de respuestas o comentarios recibidos en el Tweet.
• Image: Imagen asociada al Tweet (opcional)

Paginando los Tweets

Algo de lo que seguramente ya te abras dado cuenta es que cuando cargamos la


página, solo cargamos un número limitado de Tweets, sin embargo, eso no es
suficiente para una red social, pues debemos de cargar más tweets a medida que
descendemos o hacemos “scroll”.

Para lograr esto, es necesario dos cosas, la primera es que el API deberá soportar
consultar tweets en páginas, y la segunda, es modificar el componente
TweetsContainer para cargar más tweets a medida que descendemos en la
página.

Para la paginación utilizaremos el módulo ‘react-infinite-scroller’, por lo que


lo instalaremos con el siguiente comando:

npm install --save react-infinite-scroller

El siguiente paso será realizar los siguientes ajustes al componente


TweetsContainer:

Página | 144
1. import React from 'react'
2. import Tweet from './Tweet'
3. import APIInvoker from "./utils/APIInvoker"
4. import PropTypes from 'prop-types'
5. import InfiniteScroll from 'react-infinite-scroller'
6.
7. class TweetsContainer extends React.Component {
8. constructor(props) {
9. super(props)
10. this.state = {
11. hasMore: true,
12. tweets: []
13. }
14. this.loadMore = this.loadMore.bind(this)
15. }
16.
17. loadTweets(username, onlyUserTweet, page) {
18. let currentPage = page || 0
19. const url=`/tweets${onlyUserTweet ? "/" + username : ""}?page=${currentPage}`
20.
21. APIInvoker.invokeGET(url, response => {
22. this.setState({
23. tweets: this.state.tweets.concat(response.body),
24. hasMore: response.body.length >= 10
25. })
26. }, error => {
27. console.log("Error al cargar los Tweets", error);
28. })
29. }
30.
31. loadMore(page) {
32. const username = this.props.profile.userName
33. const onlyUserTweet = this.props.onlyUserTweet
34. this.loadTweets(username, onlyUserTweet, page - 1)
35. }
36.
37. render() {
38. console.log("tweets => ", this.state.tweets)
39. return (
40. <main className="twitter-panel">
41. <InfiniteScroll
42. pageStart={0}
43. loadMore={this.loadMore}
44. hasMore={this.state.hasMore}
45. loader={<div className="loader" key={0}>Loading ...</div>} >
46. <For each="tweet" of={this.state.tweets}>
47. <Tweet key={tweet._id} tweet={tweet} />
48. </For>
49. </InfiniteScroll>
50. </main>
51. )
52. }
53. }
54.
55. TweetsContainer.propTypes = {
56. onlyUserTweet: PropTypes.bool,
57. profile: PropTypes.object
58. }
59.
60. TweetsContainer.defaultProps = {
61. onlyUserTweet: false,
62. profile: {
63. userName: ""
64. }
65. }
66.

145 | Página
67. export default TweetsContainer

Comenzaremos con el constructor, desde el cual hemos agregado la propiedad


hasMore, necesaria para determina si hay más resultado que cargar, y hacemos
el binding al método loadMore (línea 14).

El método loadTweets lo hemos modificado ligeramente, agregando un tercer


parámetro que permite recibir el número de página (page) que se buscará, y
luego esa página se utiliza el generar la URL al servicio, agregando el query param
page (línea 19).

También hemos agregado el método loadMore, que será el que se ejecuta cada
vez que queramos consultar una nueva página, por lo tanto, recibe como
parámetro el número de página a consultar y luego delega la consulta al método
loadTweets.

Finalmente, hemos envuelto el renderizado de los tweets dentro del componente


InfiniteScroll, que hemos importado del paquete que instalamos hace un
momento. Este componte tiene muchas propiedades que podemos explotar, pero
por ahora solo vamos a requerir 3:

• pageStart: Indica el número de página en el que iniciará.


• loadMore: corresponde a la función que se ejecutará cada vez que una
nueva página requiere ser cargada. Esta función recibe como parámetro
el numero de la siguiente página a cargar.
• hashMore: propiedad booleana que le indica al componente si hay más
elementos que cargar, por lo que mientras que esta propiedad sea true,
el método loadMore se seguirá ejecutando.

Página | 146
Resumen

Este capítulo ha sido bastante emocionante pues hemos iniciado con el proyecto
Mini Twitter y hemos aplicados varios de los conceptos que hemos venido
aprendiendo a lo largo del libro. Si bien, solo hemos empezado, ya pudimos
apreciar un pequeño avance en el proyecto.

Por otra parte, hemos visto como instalar el API REST y hemos aprendido como
consumirlo desde React, también hemos analizado la estructura de un Tweet
retornado por el API.

147 | Página
Introducción al Shadow DOM y los
Estados
Capítulo 6

Una de las características más importantes de React, es que permite que los
componentes tengan estado, el cual es un objeto JavaScript de contiene la
información asociada al componente y que por lo general, representa la
información que ve el usuario en pantalla, pero también el estado puede
determinar la forma en que una aplicación se muestra al usuario. Podemos ver
el estado como el Modelo en la arquitectura MVC.

Introducción a los Estados

Para ilustrar mejor que es el estado, podemos imaginar un formulario de registro


o actualización de empleados. El cual estaría en modo edición cuando se trate de
un nuevo empleado o pongamos al empleado existente en modo edición, pero si
solo queremos consultar su información, entonces estaría en modo solo lectura:

Fig. 43 - Cambio de estado en un Componente.

En este ejemplo podemos apreciar la importancia del estado, pues el estado


contendrá los datos del empleado y una bandera booleana que indique si el
formulario está en modo edición. Si el componente está en modo lectura,

Página | 148
entonces solo mostrara los datos del empleado en etiquetas <label>, pero si
pasamos a edición, entonces los <label> se remplazan por etiquetas <input>, y
una vez que guardamos los datos, entonces podemos pasar el formulario
nuevamente a modo solo lectura.

Como podemos apreciar, los estados permiten representar la información en


pantalla, pero al mismo tiempo, puede repercutir en la forma en que el
componente muestra la información y se comporta. En este ejemplo, utilizamos
el estado para guardar los datos del empleado, como son nombre, edad, teléfono
y fecha de nacimiento, pero al mismo tiempo, utilizamos el estado para
determina como es que el componente de se debe de mostrar al usuario.

1. {
2. editMode: false,
3. employee: {
4. name: "Juan Pérez",
5. age: 22,
6. tel: "12334567890",
7. birthdate: "10/10/1900"
8. }
9. }

Este fragmento de código, representa el estado de la pantalla anterior, y como


podemos observar, es un objeto JavaScript común y corriente, el cual tiene la
información del empleado (líneas 3 a 8), pero por otro lado, tiene una propiedad
que sirven exclusivamente para determinar la forma de mostrar la información,
como es el caso de la propiedad editMode (línea 2).

Los estados pueden tener la estructura que sea y también pueden ser tan grandes
y complejos como nuestra interface lo requieres, de tal forma que podemos tener
muchos más datos y muchos más campos de control de interface gráfica.

Establecer el estado de Componente

Existe dos formas de establecer el Estado de un componente; definiéndolo en el


constructor del componente mediante la asignación directa a la propiedad
this.state o establecerlo dinámicamente en cualquier función o evento,
mediante la función setState().

Si lo que buscamos es inicializar el estado antes de realizar cualquier otra acción,


podemos hacerlos directamente sobre el constructor, por ejemplo:

1. constructor(props){
2. super(props)
3. this.state = {
4. tweets: []
5. }
6. }

149 | Página
En este ejemplo, definimos el estado del componente TweetsContainer mediante
una lista de tweets vacío, esto con la finalidad de que cuando el componente se
muestre por primera vez, no marce un error debido a que el estado es Null.

this.state solo funciona en el constructor

Es muy importante resaltar que establecer el Estado


directamente mediante la instrucción this.state solo
se puede hacer en el constructor, ya que de lo
contrario, provocaría que React no sea capaz de
detectar los cambios y es considerada una mala
práctica.

El segundó método, es establecer el estado mediante la función setState(), la


cual se utiliza para establecer un nuevo estado al componente. Al hacer esto,
estamos sobrescribiendo el estado anterior, lo que hará que nuestro componente
se actualice con los valores del nuevo estado.

Es normal que durante la vida de un componente establezcamos el estado varias


veces a medida que interactuamos con la interface, o simplemente cuando
cargamos datos del backend.

Si recordamos en el componente TweetsContainer, definimos la función


loadTweets, la cual cargaba los Tweets del API y luego actualizaba el Estado del
componente mediante this.setState(). Recordemos como quedo esta parte:

1. loadTweets(username, onlyUserTweet){
2. let url = '/tweets' + (onlyUserTweet ? "/" + username : "")
3. APIInvoker.invokeGET(url, response => {
4. this.setState({
5. tweets: response.body
6. })
7. },error => {
8. console.log("Error al cargar los Tweets", error);
9. })
10. }

El estado es actualizado cuando obtenemos la respuesta del API (línea 4). Cuando
actualizamos el estado mediante la función this.setState(), React
automáticamente actualiza el componente, para que de esta forma, la vista sea
actualizada para reflejando el nuevo Estado.

Página | 150
Actualizando el estado de un Componente

Una de las principales diferencias que existe entre las propiedades (Props) y el
Estado, es que el estado está diseñado para ser mutable, es decir, podemos
realizar cambios en el, de tal forma que los componentes puedan ser interactivos
y responder a las acciones del usuario.

Cuando React detecta la actualización del estado en uno de sus componentes,


este inicia una actualización en cascada de todos los componentes hijos, para
asegurarse de que todos los componentes sean actualizados con el nuevo estado.

Fig. 44 - Waterfall update

Para actualizar el Estado de un componente, se utiliza la función setState(), por


lo que en realidad no existe una diferencia entre actualizar el Estado y establecer
su valor inicial, sin embargo, la diferencia radica en la forma en la forma en que
modificamos el Objeto asociado al estado.

React está pensado para que el objeto que representa el estado sea inmutable,
por lo que cuando creamos un nuevo estado, tendremos que hacer una copia del
estado actual y sobre esa copia agregar o actualizar los nuevos valores. Esto es
así para garantiza que React pueda detectar los nuevos cambios y actualizar la
vista.

Una de las formas que tenemos para actualizar el estado, es mediante el método
Object.assign, el cual se utiliza para copiar los valores de todas las propiedades
enumerables de uno o más objetos fuente a un objeto destino. Retorna un nuevo
objeto con los cambios. Veamos un ejemplo:

1. let newState = {
2. edit: true
3. }
4. let stateUpdate = Object.assign({},this.state,newState)
5. console.log(stateUpdate)
6. this.setState(stateUpdate)

151 | Página
En este ejemplo, vamos a asumir que el estado actual del componente es un
objeto que solo tiene la propiedad edit = false y queremos cambiar el valor edit
= true, para hacer esto, nos apoyamos de Object.assign (línea 4) que en este
caso recibe 3 parámetros, el primero será el objeto al cual deberá aplicar los
cambios, por lo que el valor {} indica que es un objeto nuevo. El segundo
parámetro es el estado actual del componente (this.state). Como tercer
parámetro enviamos el objeto newState, el cual contiene los nuevos valores para
el estado. Como resultado de la asignación se realizará una “merge” (mescla)
entre el estado actual y el nuevo estado, por lo que los valores que ya están en
el estado actual solo se actualizarán y los que no estén, se agregarán.
Finalmente, actualizamos el estado mediante this.setState().

Tras ejecutar este código podremos ver en el log del navegador como quedaría
la variable stateUpdate:

Fig. 45 - Object.assign result

Actualizar el estado con Object.assign puede resultar bastante simple, y lo es,


pero tiene un problema fundamental, y es que no es capaz de realizar copias a
profundidad, esto quiere decir que, si nuestro estado tiene otros objetos dentro,
este no realizará una clonación de estos objetos, en su lugar, los pasará como
referencia.

Un ejemplo claro de este problema es cuando nuestro objeto tiene una array,
veamos otro ejemplo:

1. let newState = {
2. edit: true,
3. tweet: [
4. "tweet 1",
5. "tweet 2"
6. ]
7. }
8. let stateUpdate = Object.assign({},this.state,newState)
9. newState.tweet.push("tweet 3")
10. console.log(stateUpdate, newState);

Veamos que hemos agregar al estado una lista de tweets (línea 1 a 7), luego
aplicada la asignación (línea 8). Finalmente agregamos un nuevo Tweet al objeto
newState. (línea 9). Uno esperaría que el objeto newState tenga el tweet 3,
mientras que el stateUpdate se quedaría con los dos primero. Sin embargo, la
realidad es otra; veamos el resultado de log (línea 10) tras ejecutar este código:

Página | 152
Fig. 46 - Object.assign con objetos anidados

Si esto lo hiciéramos de esta forma, tuviéramos un problema con React, pues


podríamos modificar el Array desde el otro objeto y React no podría detectar los
cambios. Para solucionar este problema, existe una librería más sofisticada para
actualizar los estados de una forma más segura, la cual veremos a continuación.

La librería immutability-helper

La librería immutability-helper es una librería de ayuda para actualizar los


objetos de una forma segura, pues esta se encarga de realizar la clonación de
los objetos a profundidad. De esta forma, si nuestro objeto contuviera otro
objetos o arreglos anidados, este también los clonará.

Antes de empezar a trabajar con la librería, debemos instalarla con npm, para
ello, ejecutamos la siguiente instrucción:
npm install --save immutability-helper

por otra parte, será necesario realizar el import correspondiente en cada


componente donde requerimos utilizarlo.

1. import update from 'immutability-helper'

Esta librería proporciona solamente el método update, al cual solo requiere de


dos parámetros, el estado actual y los cambios que vamos a realizar sobre el
estado, la función update nos va a regresar un objeto totalmente nuevo, por lo
que deberemos tener cuidad de asignar el resultado en una nueva variable.

1. let stateUpdate = update(this.state,{


2. edit: {$set: true}
3. })
4. this.setState(stateUpdate)

Este ejemplo es parecido a los anteriores, pues modificamos el valor del atributo
edit a true, sin embargo, notemos una pequeña diferencia en la sintaxis, pues
en lugar de poner el valore del atributo directamente, tenemos que hacerlo

153 | Página
mediante un par de llaves, el cual tiene dos partes, la operación y el valor. La
operación le indica que hacer con el valor, en este caso, $set indica que se
establezca el valor true al atributo edit, en caso de que el valor exista lo
actualiza, y si no existe, lo agregará.

La librería immutability-helper ofrece varias operaciones que podremos utilizar


según el objetivo que buscamos. Las operaciones disponibles son:

Descripción
Operación
$push Agrega todos los elementos de una lista al final de la lista objetivo.
Funciona igual que la operación push() de ES
Formato: {$push: array}
Ejemplo:

1. update( [1, 2] ,{$push: [3]}) // => 1, 2, 3

$unshift Agrega todos los elementos de una lista al principio de la lista


objetivo. Funciona igual que la operación unshift de ES
Formato: {$unshift: array}
Ejemplo:

1. update( [1, 2] ,{$unshift: [3]}) // => 3, 1, 2

$splice Agrega o elimina elementos de una lista.


Formato: {$splice: array of arrays}
Por cada ítem del arrays llama la función splice() en objeto destino
Ejemplo:

1. const collection = [1, 2, {a: [12, 17, 15]}];


2. update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
3. // => [1, 2, {a: [12, 13, 14, 15]}]

$set Remplaza por completo el valor del objeto destino


Formato: {$set: any}
Ejemplo:

1. const state = {edit: false}


2. update( state, {edit: {$set: true}} // => {edit: true}

$marge Realiza una combinación de dos objetos, remplaza llaves


existentes y agrega las que no están en el objeto destino.
Formato: {$merge: object}
Ejemplo:

1. const obj = {a: 5, b: 3};


2. update(obj, {$merge: {b: 6, c: 7}}); // => {a: 5, b: 6, c: 7}

$apply Permite actualiza el valor actual por medio de una función, esta
función permite realizar cálculos más complejos.
Formato: {$apply: function}
Ejemplo:

1. const state = {a: 10};


2. update(obj, {a: {$apply: (value) => value*2 }});

Página | 154
3. // => {a: 20}

Cabe mencionar que las secciones que no son manipuladas por alguna operación,
pasan intacta al objeto resultante. Por lo que solo es necesario realizar
operaciones sobre los campos que queremos modificar.

De momento no te preocupes si no comprender del todo como se utilizan algunas


de estas operaciones, pues a lo largo del libro utilizaremos algunas y las
explicaremos con más detalle. Por otra parte, puedes revisar la documentación
oficial de la librería https://www.npmjs.com/package/immutability-helper.

El Shadow DOM de React

Dado que React se ejecuta del lado del navegador, este está obligado a
arreglárseles solo para la actualización de las vistas. De esta forma, React es el
que tiene que decirle al navegador que elementos del DOM deberán ser
actualizados para reflejar los cambios.

La forma que tiene React para identificar si un componente ha sido actualizado,


es muy compleja, aun que podemos resumirla en que React detecta los cambios
en los estados en los componentes, y de allí determina que compontes deben de
ser actualizado. Sin embargo, actualizar el DOM directamente cada vez que hay
un cambio en la vista, es un proceso pesado, por lo que React utiliza algo llamado
Shadow DOM, el cual es una representación del DOM real, pero administrado
por React.

Fig. 47 - DOM y Shadow DOM

El Shadow DOM es utilizado por React pare evaluar las actualizaciones que deberá
aplicar al DOM real, y una vez evaluados los cambios, se ejecuta un proceso
llamado reconciliación, encargado de sincronizar los cambios del Shadow DOM
hacia el DOM real. Este proceso hace que React mejore su rendimiento, al no

155 | Página
tener que actualizar la vista ante cada cambio, en su lugar, calcula todos los
cambios y los aplica en Batch al DOM real.

Nuevo concepto: Reconciliación (Reconciliación)

Reconciliación es el proceso que ejecuta React para


sincronizar los cambios que existen en el Shadow DOM
contra el DOM real del navegador.

Atributos de los elementos:

Como ya mencionamos en la sección de JSX, React requiere que todos los


atributos de los elementos estén en formato Camel Case, por lo que para React
no será lo mismo decir maxlength que maxLength. En caso de no poner los
atributos en Camel Case, es posible que React no reconozca el atributo y/o que
nos lance un error en la consola.

React tiene una lista de todos los atributos que soporte, la cual nos puede servir
de guía:
accept, acceptCharset, accessKey, action, allowFullScreen, allowTransparency, alt,
async, autoComplete, autofocus, autoPlay, capture, cellPadding, cellSpacing, challenge,
charSet, checked classID, className, colSpan, cols, content, contentEditable,
contextMenu, controls, coords, crossOrigin, data, dateTime, default, defer, dir,
disabled, download, draggable, encType, form, formAction, formEncType, formMethod,
formNoValidate, formTarget, frameBorder, headers, height, hidden, high, href, hrefLang,
htmlFor, httpEquiv, icon, id, inputMode, integrity, is, keyParams, keyType, kind, label,
lang, list, loop, low, manifest, marginHeight, marginWidth, max, maxLength, media,
mediaGroup, method, min, minLength, multiple, muted, name, noValidate, nonce, open,
optimum, pattern, placeholder, poster, preload, radioGroup, readOnly, rel, required,
reversed, role, rowSpan, rows, sandbox, scope, scoped, scrolling, seamless, selected,
shape, size, sizes, span, spellCheck, src, srcDoc, srcLang, srcSet, start, step, style,
summary, tabIndex, target, title, type, useMap, value, width, wmode, wrap.

Más detalles en la documentación oficial:


https://www.reactenlightenment.com/react-jsx/5.7.html

Muchos de los atributos que ves en esta lista se ven exactamente igual al HTML
tradicional, y esto se debe a que son atributos formados por una palabra, por lo
que no hay necesidad de utilizar Camel Case. Ahora bien, existe ciertos atributos
con los que debemos de tener cuidado, pues son difieren del nombre original de
HTML, como es el caso de className para class, defaultChecked para checked,
htmlFor para for, etc.

Puedes ver más información al respecto en la documentación oficial:


https://facebook.github.io/react/docs/dom-elements.html

Página | 156
Eventos

Los eventos en React tiene el mismo tratamiento que los atributos, pues deben
de definirse en Camel Case, de lo contrario no serán tomados en cuenta por
React, los principales eventos son:

Keyboard Events
onKeyDown onKeyPress onKeyUp

Focus Events
DOMEventTarget relatedTarget

Mouse Events
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave
onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave onMouseMove
onMouseOut onMouseOver onMouseUp

Selection Events
onSelect

Puedes ver la lista completa de eventos en la documentación oficial de React:


https://facebook.github.io/react/docs/events.html

157 | Página
Resumen

A lo largo de este capítulo hemos analizado los Estados, y como es que estos
afectan la forma que se ven y actualizan los componentes. Por otra parte, hemos
analizado las dos formas de establecer el estado, ya sea en el constructor o
mediante la función setState(). También analizamos la librería immutability-
helper para actualizar de forma correcta el estado.

Hemos analizado la forma en que React actualiza los componentes mediante el


Shadow DOM y como es que este se utiliza para optimizar la actualización del
DOM del navegador.

Finalmente hemos visto la forma correcta de declarar los atributos y eventos


mediante Camel Case.

Página | 158
Trabajando con Formularios
Capítulo 7

Los formularios son una parte fundamental de cualquier aplicación, pues son la
puerta de entrada para que los usuarios puedan interactuar con la aplicación, y
en React no es la excepción. Sin embargo, existe una diferencia sustancia al
trabajar con formularios en React y en una tecnología web convencional, ya que
React almacena la información en sus Estados, y no en una sesión del lado del
servidor, como sería el caso de una aplicación web tradicional.

En React existe dos formas de administrar los controles de un formulario,


controlados y no controlados, el primero permite ligar el valor de un campo al
estado, mientras que el segundo permite tener un campo de libre captura, el cual
no está ligado al estado o una propiedad.

Nuevo concepto: Controles

El termino Controles se utiliza para llamar de forma


genérica a los elementos que conforman un formulario
y que el usuario puede interactuar con ellos, por
ejemplo: input, radio button, checkbox, select,
textarea, etc.

Controlled Components

Los componentes controlados son todos aquellos que su valor está ligado
directamente al estado del componente o una propiedad (prop), lo que
significa que el control siempre mostrará el valor del objeto asociado.

Para que un control sea considerado controlador (Controlled), es necesario


vincular su valor con el estado o una propiedad mediante el atributo value, como
podemos ver en el siguiente ejemplo.

1. <input type="text" value={this.state.field} />

159 | Página
Debido a que el valor mostrado es una representación del estado o propiedad, el
usuario no podrá editar el valor del control directamente. Veamos un ejemplo
para comprender esto. Crearemos un nuevo archivo llamado FormTest.js en el
path /app y lo dejaremos de la siguiente forma:

1. import React from 'react'


2.
3. class FormTest extends React.Component{
4.
5. constructor(props){
6. super(props)
7. this.state = {
8. field: "Init values"
9. }
10. }
11.
12. render(){
13.
14. return (
15. <input type="text" value={this.state.field} />
16. )
17. }
18. }
19. export default FormTest

A continuación, modificaremos el archivo App.js para mostrar el componente


FormTest en el método render. No olvidemos importar el componente:

1. render(){
2. return (
3. <FormTest/>
4. )
5. }

Finalmente, guardamos los cambios y actualizamos el navegador para reflejar los


cambios:

Fig. 48 - Controlled Componente

Página | 160
Una vez actualizado el navegador, podremos ver el campo de texto con el valor
que pusimos en el estado del componente, y si intentamos actualizar el valor,
veremos que este simplemente no cambiará. Esto es debido a que React muestra
el valor como inmutable.

Observemos en la imagen que React nos ha arrojado un warning, esta


advertencia se debe a que un campo controlado, deberá definir el atributo
onChange para controlar los cambios en el control, de lo contrario, este campo
será de solo lectura.

Para solucionar este problema, deberemos de crear una función que tome los
cambios en el control y posteriormente actualice el estado con el nuevo valor.
Cuando React detecte el cambio en el estado, iniciará la actualización de la vista
y los cambios será reflejados. Veamos cómo implementar esta función:

1. import React from 'react'


2. import update from 'immutability-helper'
3.
4. class FormTest extends React.Component{
5.
6. constructor(props){
7. super(props)
8. this.state = {
9. field: "Init values"
10. }
11. }
12.
13. updateField(e){
14. this.setState(update(this.state, {
15. field: {$set: e.target.value}
16. }))
17. }
18.
19. render(){
20. return (
21. <input type="text" value={this.state.field}
22. onChange={this.updateField.bind(this)}/>
23. )
24. }
25. }
26. export default FormTest

Lo primero será definir el atributo onChange y asignarle la función updateField.


Con esto, cuando se presione una tecla sobre el control, este lanzará un evento
que será tratado por esta función. Cuando la función reciba el evento de cambio,
actualizará el estado (línea 14) y React automáticamente actualizará la vista,
cuando esto pase, el campo tomará el nuevo valor del estado.

Todos los controles funcionan exactamente igual que en HTML tradicional, sin
embargo, existe dos controles que se utilizan de forma diferente, los cuales son
los TextArea y Select. Analicémoslo por separado.

TextArea

161 | Página
La forma tradicional de utilizar un TextArea es poner el valor entre la etiqueta de
apertura y cierre, como se ve en el siguiente ejemplo:

1. <textarea>{this.state.field}</textarea>

En React, para definir el valor de un TextArea se deberá utilizar el atributo value,


como podemos ver en el siguiente ejemplo:

1. <textarea value={this.state.field}/>

Select

El control Select cambia solo la forma en que seleccionamos el valor por default,
pues en HTML tradicional solo deberemos utilizar el atributo selected sobre el
valor por default, veamos un ejemplo:

1. <select>
2. <option value="volvo">Volvo</option>
3. <option value="saab">Saab</option>
4. <option value="vw">VW</option>
5. <option value="audi" selected>Audi</option>
6. </select>

En React, cambiamos el atributo selected sobre la etiqueta option, por el atributo


value sobre la etiqueta select, el cual deberá coincidir con el value del option,
veamos un ejemplo:

1. <select value="audi">
2. <option value="volvo">Volvo</option>
3. <option value="saab">Saab</option>
4. <option value="vw">VW</option>
5. <option value="audi">Audi</option>
6. </select>

Uncontrolled Components

Los componentes no controlados son aquellos que no están ligados al estado


o propiedades. Por lo general deberemos evitar utilizar controles no
controlados, pues se sale del principio de React, aunque esto no significa que no
debamos utilizarlos nunca, pues existen escenarios concretos en donde pueden
resultar útiles, como lo son formularios grandes, donde no requerimos realizar
acción alguna hasta que se manda el formulario.

Para crear un componente no controlado, están simple como definir el control sin
el atributo value. Al no definir este atributo, React sabrá que el valor de este

Página | 162
control es libre y permitirá su edición sin necesidad de crear una función que
controle los cambios en el control. Para analizar esto, modifiquemos el método
render del componente FormTest para que quede de la siguiente manera.

1. render(){
2. return (
3. <div>
4. <input type="text" value={this.state.field}
5. onChange={this.updateField.bind(this)}/>
6. <br/>
7. <input type="text" name="field2" defaultValue="Init Value 2" />
8. </div>
9. )
10. }

Otro de las peculiaridades de React es que ofrece el atributo defaultValue para


establecer un valor por default, y así poder inicializar el control con un texto
desde el comienzo. También será necesario utilizar el atributo name, pues será la
forma en que podremos recuperar el valor del campo al momento de procesar el
envío.

Una vez aplicados los cambios, actualizamos el valor del control y presionamos
el botón submit para que nos arroje en pantalla el valor capturado

Fig. 49 - Probando los controles no controlados.

Enviar el formulario

Ahora bien, si lo que queremos hacer es recuperar los valores del control al
momento de mandar el formulario, deberemos encapsular los controles dentro
de un form. Regresemos al archivo FormTest y actualicemos la función render
para que sea de la siguiente manera:

163 | Página
1. render(){
2. return (
3. <div>
4. <input type="text" value={this.state.field}
5. onChange={this.updateField.bind(this)}/>
6. <br/>
7. <form onSubmit={this.submitForm.bind(this)} >
8. <input type="text" name="field2" />
9. <br/>
10. <button type="submit">Submit</button>
11. </form>
12. </div>
13. )
14. }

Adicional, agregaremos la función submitForm:

1. submitForm(e){
2. alert(this.state.field)
3. alert(e.target.field2.value)
4. e.preventDefault();
5. }

Existen dos variantes para recuperar los valores de un control. Si está controlado,
solo tendremos que hacer referencia a la propiedad del estado al que está ligado
el campo (línea 2), por otra parte, si el campo no es controlado, entonces
deberemos recuperar el valor del campo mediante su tributo name, como lo vemos
en la línea 3.

preventDefault function

La función e.preventDefault previene que el


navegador mande el formulario al servidor, lo que
provocaría que el navegador se actualice y nos haga
un reset del estado del componente.

Debido a que React trabajo por lo general con AJAX, es importante impedir que
el navegador actualice la página, pues esto provocaría que los compontes sean
cargados de nuevo y nos borre los estados. Es por eso la importancia de utilizar
la función preventDefault.

Página | 164
Mini Twitter (Continuación 1)

Una vez que hemos explicado la forma de trabajar con los controles,
continuaremos desarrollando nuestro proyecto de Mini Twitter, pero en este
capítulo nos centraremos en los componentes que utilizan formularios.

El componente Signup

El primer componente que trabajaremos será Signup, el cual es un formulario de


registro, en donde los nuevos usuarios se registran para crear una cuenta en la
aplicación y poder autenticarse. La siguiente imagen muestra como se ve el
componente terminado.

Fig. 50 - Signup Component

Antes de irnos al código, entendamos como funciona. Para dar de alta a un nuevo
usuario, este deberá de capturar un nombre de usuario, su nombre y una
contraseña. El usuario deberá ser único, por lo que, si selecciona una ya
existente, la aplicación le mostrará una leyenda advirtiendo el error y no lo
deberá dejar continuar. El usuario también deberá confirmar que acepta los
términos de licencia, de lo contrario, tampoco podrá continuar.

Para confirmar el envío del formulario crearemos un botón, el cual tome los datos
capturados y cree el nuevo usuario mediante el API REST. Si todo sale bien, el

165 | Página
usuario será redirigido al componente de login, el cual crearemos en la siguiente
sección.

Finalmente, al final de la vista crearemos un link que lleve al usuario a la pantalla


de iniciar sesión. Esto con la intención de que los usuarios que ya tiene una
cuenta puedan autenticarse.

En esta pantalla utilizaremos dos servicios del API REST, el primero nos ayudará
a validar que el nombre de usuario no esté repetido, y este se ejecutará cuando
el campo del nombre de usuario pierda el foco. El segundo servicio es el de
creación del usuario, el cual se ejecutará cuando el usuario presione el botón de
registro.

Nuevo concepto: Foco

Se dice que un elemento tiene el foco cuando este está


recibiendo los eventos de entrada como el teclado. Por
lo que cuando seleccionamos un campo de texto para
escribir sobre él, se dice que tiene el foco. Solo puede
existir un elemento con el foco en un tiempo
determinado.

Documentación: “Alta de usuarios” (Signup)

El servicio de alta de usuarios es el utilizado para crear


una nueva cuenta de usuario (/signup).

Documentación: “Validación de nombre de


usuario”

Este servicio es el encargado de validar que un nombre


de usuario no esté siendo utilizado ya por otro usuario
durante la creación de la cuenta
(/usernamevalidate/:username).

Iniciemos creando el formulario sin ninguna acción, y posteriormente iremos


agregando la integración con los servicios del API REST. Para esto, deberemos
crear un nuevo archivo en llamado Signup.js en el path /app y lo dejaremos
como se ve a continuación:

1. import React from 'react'


2. import update from 'immutability-helper'
3. import APIInvoker from './utils/APIInvoker'
4.
5. class Signup extends React.Component{
6.
7. constructor(){

Página | 166
8. super(...arguments)
9. this.state = {
10. username: "",
11. name:"",
12. password: "",
13. userOk: false,
14. license: false
15. }
16. }
17.
18. handleInput(e){
19. let field = e.target.name
20. let value = e.target.value
21. let type = e.target.type
22.
23. if(field === 'username'){
24. value = value.replace(' ','').replace('@','').substring(0, 15)
25. this.setState(update(this.state,{
26. [field] : {$set: value}
27. }))
28. }else if(type === 'checkbox'){
29. this.setState(update(this.state,{
30. [field] : {$set: e.target.checked}
31. }))
32.
33. }else{
34. this.setState(update(this.state,{
35. [field] : {$set: value}
36. }))
37. }
38. }
39.
40. render(){
41.
42. return (
43. <div id="signup">
44. <div className="container" >
45. <div className="row">
46. <div className="col-xs-12">
47.
48. </div>
49. </div>
50. </div>
51. <div className="signup-form">
52. <form>
53. <h1>Únite hoy a Twitter</h1>
54. <input type="text" value={this.state.username}
55. placeholder="@usuario" name="username" id="username"
56. onChange={this.handleInput.bind(this)}/>
57. <label id="usernameLabel"
58. ref={self => this.usernameLabel = self}
59. htmlFor="username"></label>
60.
61. <input type="text" value={this.state.name} placeholder="Nombre"
62. name="name" id="name" onChange={this.handleInput.bind(this)}/>
63. <label id="nameLabel" htmlFor="name"
64. ref={self => this.nameLabel = self}></label>
65.
66. <input type="password" id="passwordLabel"
67. value={this.state.password} placeholder="Contraseña"
68. name="password" onChange={this.handleInput.bind(this)}/>
69. <label ref={self => this.passwordLabel = self}
70. htmlFor="passwordLabel"></label>
71.
72. <input id="license" type="checkbox"
73. ref={self => this.license = self }

167 | Página
74. value={this.state.license} name="license"
75. onChange={this.handleInput.bind(this)} />
76. <label htmlFor="license" > Acepto los terminos de licencia</label>
77.
78. <button className="btn btn-primary btn-lg " id="submitBtn"
79. >Regístrate</button>
80. <label id="submitBtnLabel" htmlFor="submitBtn"
81. ref={self => this.submitBtnLabel = self}
82. className="shake animated hidden "></label>
83. <p className="bg-danger user-test">
84. Crea un usuario o usa el usuario
85. <strong>test/test</strong></p>
86. <p>¿Ya tienes cuenta? Iniciar sesión</p>
87. </form>
88. </div>
89. </div>
90. )
91. }
92. }
93. export default Signup;

Para comprender mejor este componente dividiremos la explicación en 3 partes,


el constructor, la función handleInput y render

En el constructor no hay mucho que ver, salvo que iniciamos el estado (línea 9)
con los valores en blanco para username, name, password, los cuales son
precisamente los mismos campos que hay en la pantalla. Adicional, definimos
dos variables booleanas de control, userOk y license, la primera nos indica si el
nombre de usuario es válido (validado desde el API REST) y license corresponde
al checkbox para aceptar los términos de licencia. Ambos deberán ser true para
permitir la creación del usuario.

Como ya sabemos, la función render es la encargada de generar la vista, por lo


que no entraremos mucho en los detalles, pero si hablaremos de los controles
utilizados.

Podemos observar que creamos un input de texto para nombre de usuario (línea
54) y para el nombre (línea 61), los cuales están ligados a su campo
correspondiente del estado this.state.username y this.state.name
respectivamente, para el password (línea 66) creamos también un input de tipo
password ligado a this.state.password, finalmente, creamos otro input de tipo
checkbox para los términos de licencia (línea 72) ligado a this.state.license.
Cabe mencionar que todos estos controles tienen definido el atributo onChange
ligado a la función handleInput, el cual analizamos a continuación.

La función handleInput tiene como responsabilidad tomar los cambios realizados


a los controles y actualizar el estado, de tal forma que la vista pueda reflejar los
cambios, recordemos que ya habíamos hablado de esto. Solo cabe resaltar que
limpiamos los espacios en blanco y la @ del nombre de usuario, también le
hacemos un substring a 15 caracteres (línea 24). Con esto impedimos que el
usuario utiliza una @ y espacios en blanco en su nombre de usuario, del mismo
modo, restringimos los nombres de usuario a 15 caracteres.

Página | 168
El siguiente paso es agregar los estilos (CSS) correspondientes, por lo que
regresamos al archivo estyles.css y agregamos las siguientes líneas al final del
archivo:

1. /** SIGNUP COMPONENT **/


2. #signup{
3. position: relative;
4. padding-top: 150px;
5. height: auto;
6. }
7.
8. #signup h1{
9. font-size: 27px;
10. margin-bottom: 30px;
11. }
12.
13. #signup .signup-form{
14. position: relative;
15. display: block;
16. width: 400px;
17. margin: 0 auto;
18. }
19.
20. #signup input{
21. margin-bottom: 30px;
22. }
23.
24. #signup .signup-form button{
25. background-color: #1DA1F2;
26. padding: 10px;
27. outline: none;
28. border: none;
29. }
30.
31. #signup input[type="text"],
32. #signup input[type="password"]{
33. outline: none;
34. border: 1px solid #999;
35. font-size: 18px;
36. width: 100%;
37. padding: 8px;
38.
39. border-radius: 5px;
40. }
41.
42. #signup button{
43. display: block;
44. padding: 5px;
45. width: 100%;
46.
47. }
48.
49. #signup .user-test{
50. margin-top: 30px;
51. padding: 10px;
52. border-radius: 5px;
53. }
54.
55.
56. #signup #usernameLabel{
57. display: inline-block;
58. position: absolute;
59. width: 100%;
60. padding: 10px;
61. }

169 | Página
62.
63. #signup #usernameLabel.ok{
64. color: #1DA1F2;
65. }
66.
67. #signup #usernameLabel.fail{
68. color: tomato;
69. }
70.
71. #signup #submitBtnLabel{
72. display: block;
73. text-align: center;
74. color: red;
75. margin-top: 20px;
76. }

Ya con los estilos agregados, deberemos actualizar el archivo App.js para mostrar
el componente Signup al iniciar la aplicación:

1. import React from 'react'


2. import { render } from 'react-dom'
3. import TweetsContainer from './TweetsContainer'
4. import FormTest from './FormTest'
5. import Signup from './Signup'
6. import Login from './Login'
7.
8. class App extends React.Component{
9.
10. constructor(props){
11. super(props)
12. }
13.
14. render(){
15. return (
16. <Signup/>
17. )
18. }
19. }
20.
21. render(<App/>, document.getElementById('root'));

Finalmente actualizamos el navegador para comprobar que el formulario de


registro ya se ve correctamente:

Página | 170
Fig. 51 - Vista previa del Componente Signup.

En este punto, el formulario puede capturar los datos del usuario y actualizando
el estado al mismo tiempo que esto pasa.

Validando usuario único

Para asegurarnos de que el nombre de usuario capturado sea único antes de


enviar el formulario, deberemos ejecutar un servicio que nos proporciona el API
REST cada vez que el campo del usuario pierda el foco. Para esto, vamos a
regresar al componente Signup y vamos a crear la función validateUser.

1. validateUser(e){
2. let username = e.target.value
3. APIInvoker.invokeGET('/usernameValidate/' + username, response => {
4. this.setState(update(this.state, {
5. userOk: {$set: true}
6. }))
7. this.usernameLabel.innerHTML = response.message
8. this.usernameLabel.className = 'fadeIn animated ok'
9. },error => {
10. console.log("Error al cargar los Tweets");
11. this.setState(update(this.state,{
12. userOk: {$set: false}
13. }))
14. this.usernameLabel.innerHTML = error.message
15. this.usernameLabel.className = 'fadeIn animated fail'
16. })
17. }

171 | Página
Y agregaremos el evento onBlur al campo username:

1. <input type="text" value={this.state.username}


2. placeholder="@usuario" name="username" id="username"
3. onBlur={this.validateUser.bind(this)}
4. onChange={this.handleInput.bind(this)}/>

Cuando el campo username pierda el foco, llamará la función validateUser, la cual


funciona de la siguiente manera: en la línea 2 de la función recuperamos el
username capturado por el usuario, luego consumimos el servicio
/usernameValidate/{username} (línea 3), donde {username} corresponde al
usuario capturado. Si el servicio responde correctamente, entonces actualizamos
el estado con el campo userOk = true (línea 5), de lo contrario, establecemos el
campo userOk = false y mandamos una leyenda a al usuario (línea 14 y 15).

Fig. 52 - Validación del nombre de usuario único.

Para probar la validación, solo es necesario introducir como nombre de usuario


test y presionar tabulador. Como el usuario test ya existe, React nos arrojará el
error.

Crear el usuario

Para finalizar el componente, solo nos queda habilitar la función para que se cree
el usuario, para lo cual deberemos hacer 3 cambios a nuestro componente. El
primer cambio será agregar el evento onClick al botón para que llame la función
signup justo después de presionar el botón.

1. <button className="btn btn-primary btn-lg " id="submitBtn"


2. onClick={this.signup.bind(this)}>Regístrate</button>

El segundo cambio es agregar el evento onSubmit a la etiqueta form para llamar


la función signup.

1. <form onSubmit={this.signup.bind(this)}>

Notemos que estamos mandando llamar la función signup desde el botón y desde
la etiqueta form, lo que puede resultar redundante o confuso, pero existe una
razón por la cual hacerlo así. Cuando el usuario presione el botón directamente,
entonces se mandará llamar la función por medio del evento onClick, sin

Página | 172
embargo, si el usuario decide presionar enter sobre algún campo en lugar del
botón, entonces el evento onSubmit procesará la solicitud.

Finalmente, solo nos falta agregar el método singup al componte:

1. signup(e){
2. e.preventDefault()
3.
4. if(!this.state.license){
5. this.submitBtnLabel.innerHTML =
6. 'Acepte los términos de licencia'
7. this.submitBtnLabel.className = 'shake animated'
8. return
9. }else if(!this.state.userOk){
10. this.submitBtnLabel.innerHTML =
11. 'Favor de revisar su nombre de usuario'
12. this.submitBtnLabel.className = ''
13. return
14. }
15.
16. this.submitBtnLabel.innerHTML = ''
17. this.submitBtnLabel.className = ''
18.
19. let request = {
20. "name": this.state.name,
21. "username": this.state.username,
22. "password": this.state.password
23. }
24.
25. APIInvoker.invokePOST('/signup',request, response => {
26. alert('Usuario registrado correctamente');
27. },error => {
28. console.log("Error al cargar los Tweets");
29. this.submitBtnLabel.innerHTML = error.error
30. this.submitBtnLabel.className = 'shake animated'
31. })
32. }

Recordemos que debemos mandar llamar la función preventDefault (línea 2)


para evitar que el navegador manda una solicitud al servidor y actualice la
pantalla.

Veamos que en las líneas 4 y 9 validamos que los campos license y userOk del
estado sean true, de lo contrario, mandamos el error correspondiente al usuario.
Si las validaciones son correctas, entonces limpiamos cualquier error sobre la
vista (líneas 16 y 17).

En la línea 19 creamos el request para crear el usuario por medio del API REST,
el cual contiene el nombre (name), el nombre de usuario (username) y el password.

Finalmente, en la línea 25 se ejecuta la operación signup del API REST utilizando


el método POST. Si el servicio responde correctamente, deberíamos redireccionar
al usuario a la vista de login, sin embargo, como todavía no desarrollamos este
componente, hemos puesto un alert para darnos cuenta de que todo funcionó
bien (más adelante regresaremos a este punto para terminarlo). Por otro lado,

173 | Página
si el servicio retorna con algún error (línea 27) entonces actualizamos la vista
con dicho error.

Librería de animación

En la línea 7 y 30 estamos haciendo uso de la clase de


estilo animated, la cual es proporcionada por librería
de animación Animate, que sirve para dar efectos de
entrada y salida a los elementos, puede revisar su
documentación en
https://daneden.github.io/animate.css/

Esta librería la importamos en el archivo index.html


(línea 5)

Una vez terminados estos cambios, actualizamos el navegador e intentamos


crear un nuevo usuario, si todo funciona bien, la pantalla nos arrojara un mensaje
de éxito.

Fig. 53 - Registro existo se un nuevo usuario.

El componente login

Página | 174
En esta sección desarrollaremos el componente Login, el cual es un formulario
de autenticación, en donde el usuario deberá capturar su nombre de usuario y
contraseña para tener acceso.

Este componente es muy parecido al anterior, pero mucho más simples, pues
este solo tiene dos campos de texto, tiene el botón para iniciar sesión y un link
que lleva al usuario a la pantalla de registro en caso de que no tenga una cuenta
registrada.

Fig. 54 - Vista previa del componente Login.

Iniciaremos creando un archivo llamado Login.js en el path /app con el siguiente


contenido:

1. import React from 'react'


2. import update from 'immutability-helper'
3. import APIInvoker from './utils/APIInvoker'
4.
5. class Login extends React.Component{
6.
7. constructor(){
8. super(...arguments)
9. this.state = {
10. username: "",
11. password: ""
12. }
13. }
14.
15. handleInput(e){
16. let field = e.target.name
17. let value = e.target.value
18.
19. if(field === 'username'){
20. value = value.replace(' ','').replace('@','').substring(0, 15)
21. this.setState(update(this.state,{
22. [field] : {$set: value}
23. }))
24. }
25.

175 | Página
26. this.setState(update(this.state,{
27. [field] : {$set: value}
28. }))
29. }
30.
31. login(e){
32. e.preventDefault()
33.
34. let request = {
35. "username": this.state.username,
36. "password": this.state.password
37. }
38.
39. APIInvoker.invokePOST('/login',request, response => {
40. window.localStorage.setItem("token", response.token)
41. window.localStorage.setItem("username", response.profile.userName)
42. window.location = '/'
43. },error => {
44. this.submitBtnLabel.innerHTML = error.message
45. this.submitBtnLabel.className = 'shake animated'
46. console.log("Error en la autenticación")
47. })
48. }
49.
50. render(){
51.
52. return(
53. <div id="signup">
54. <div className="container" >
55. <div className="row">
56. <div className="col-xs-12">
57. </div>
58. </div>
59. </div>
60. <div className="signup-form">
61. <form onSubmit={this.login.bind(this)}>
62. <h1>Iniciar sesión en Twitter</h1>
63.
64. <input type="text" value={this.state.username}
65. placeholder="usuario" name="username" id="username"
66. onChange={this.handleInput.bind(this)}/>
67. <label ref={self => this.usernameLabel = self} id="usernameLabel"
68. htmlFor="username"></label>
69.
70. <input type="password" id="passwordLabel"
71. value={this.state.password} placeholder="Contraseña"
72. name="password" onChange={this.handleInput.bind(this)}/>
73. <label ref={self => this.passwordLabel = self}
74. htmlFor="passwordLabel"></label>
75.
76. <button className="btn btn-primary btn-lg " id="submitBtn"
77. onClick={this.login.bind(this)}>Regístrate</button>
78. <label ref={self => this.submitBtnLabel = self}
79. id="submitBtnLabel" htmlFor="submitBtn"
80. className="shake animated hidden "></label>
81. <p className="bg-danger user-est">Crea un usuario o usa el usuario
82. <strong>test/test</strong></p>
83. <p>¿No tienes una cuenta? Registrate</p>
84. </form>
85. </div>
86. </div>
87. )
88. }
89. }
90. export default Login

Página | 176
Nuevamente vamos a dividir la explicación en 3 secciones, el constructor, la
función render y login.

En el constructor simplemente iniciamos el estado (línea 9) con el nombre de


usuario y password con un valor en blanco.

En la función render creamos el campo username (línea 64) de tipo text el cual
está ligado al estado mediante this.state.username, creamos otro campo de
texto de tipo password también ligado al estado, mediante this.state.password.
Creamos el botón (línea 76) que procesará la autenticación mediante la función
login. Como podemos ver, estamos haciendo exactamente lo mismo que en el
componente Signup.

Cuando el usuario presione el botón para autenticarse, este llamará la función


login, la cual recupera los datos del formulario para crear el request (línea 39)
para el API REST. Seguido se hace la llamada al servicio del API /login mediante
el método POST. Si el login se llega a cabo correctamente, entonces guardamos
en el LocalStorage un token (línea 40) que genera el API y el nombre del usuario
registrado (línea 41). Adicional, la aplicación redirección al usuario al usuario a
la raíz de la aplicación (línea 43). De momento la raíz es la misma página de
login, pero más adelante cambiaremos esto para que la raíz sea donde se
muestran los Tweets.

Nuevo concepto: Token

Un token es una cadena alfanumérica encriptada la


cual sirve para que el usuario pueda autenticarse en el
API. El token no tiene un significado claro para el
usuario, pero para el API si tiene un significado.

Nuevo concepto: LocalStorage

LocalStorage es una de las nuevas características de


HTML5, la cual permite almacenar información
persistente del lado del navegador, de tal forma que
esta permanece aun cuando cerramos el navegador.

Por el momento solo guardaremos el token, pero más adelante lo retomaremos


para utilizarlo al consumir servicios de API REST. En el capítulo 15 nos
introduciremos de lleno al desarrollo del API REST y hablaremos de cómo es que
el token se genera y como lo utilizamos para autenticar al usuario, pero por
ahora, solo es importante saber que el token lo genera el API y nosotros lo
guardamos en el LocalStorage.

177 | Página
Por último, regresaremos al archivo App.js para retornar el componente Login,
para esto, agregamos el import al componente Login y modificaremos la función
render para que se vea de la siguiente manera:

1. render(){
2. return (
3. <Login/>
4. )
5. }

Podrás observar que si cambiamos la línea 3 para agregar el componente Login


ya no veremos el Signup, esto se debe a que todavía no hemos hablado de las
rutas, una librería que nos permite definir las páginas de navegación de nuestra
aplicación, por lo que por ahora, reemplazaremos un componente por otro para
poder visualizarlo.

Con este cambio ya deberíamos de poder ver la página de login al actualizar el


navegador, y cuando esto pase, podremos intentar autenticarnos con el usuario
que hemos creado en el paso anterior. Ahora bien, veremos que si ponemos las
credenciales correctas, la aplicación aparentemente no hará nada, sin embargo,
el LocalStorage debería haberse afectado. Para validar esto, tendremos que
entrar al inspector de Chrome y dirigirnos a la pestaña Application, luego del
lado izquierdo, buscamos la sección que dice Local Storage y seleccionamos la
página localhost, en el centro de la pantalla nos arrojará el token y el username
registrado, tal como se ve en la siguiente imagen.

Fig. 55 - Analizando el LocalStorae.

Página | 178
El token tiene una vigencia de 24 hrs, por lo que, al pasar este tiempo, el token
ya no servirá y será necesario autenticarnos de nuevo para renovarlo.

En las siguientes secciones analizaremos las rutas con React Router, con la
intención de poder ver un componente diferente para cada URL, por ejemplo
/login, /signup.

179 | Página
Resumen

En esta unidad hemos analizado la forma de trabajar con Formularios, y como es


que React administra los Controles. También hemos analizado la diferencia que
existe entre controles controlados y no controlados.

Hemos hablado de que los controles no controlados son por lo general una mala
práctica, pues difieren de la propuesta de trabajo de React, aunque también
hemos dicho que existe situaciones en las que podría ser una buena idea, lo
importante es no abusar del uso de controles no controlados.

También hemos creado el componente de registro y de autenticación,


apoyándonos del API REST para recuperar el token que más adelante
utilizaremos para consumir servicios restringidos del API.

Página | 180
Ciclo de vida de los componentes
Capítulo 8

El ciclo de vida (life cycle) de un componente, representa las etapas por las que
un componente pasa durante toda su vida, desde la creación hasta que es
destruido. Conocer el ciclo de vida de un componente es muy importante debido
a que nos permite saber cómo es que un componente se comporta durante todo
su tiempo de vida y nos permite prevenir la gran mayoría de los errores que se
provocan en tiempo de ejecución.

Hasta el momento hemos hablado de muchas características de React, como lo


son las props, los estados (state), el shadow dom y controles, pero poco hemos
hablado acerca de cómo React administra los componentes. Por lo que en esta
unidad nos centraremos exclusivamente en esto.

Para comprender el ciclo de vida de los componentes es importante hablar un


poco de la historia de React y como este ha venido evolucionando y madurando
su ciclo de vida, en este contexto, es importante mencionar que desde su
nacimiento, React ha cambiado ligeramente el clico de vida de los componentes,
desaconsejando y agregando nuevos métodos, es por ello que me gustaría
mostrar primero que nada el ciclo de vida de React hasta la versión 16.2:

Fig. 56 - Ciclo de vida de los componentes en React.

181 | Página
En la imagen anterior puedes ver todos los métodos del ciclo de vida de los
componentes que funcionaban hasta la versión 16.2, donde podemos apreciar de
color verde los métodos que siguen vigentes hasta el día de hoy y en rojo los
métodos que se han desaconsejado su uso (deprecados). Si bien los métodos
deprecados siguen funcionando en todas las versiones de React 16.x, están
programados para ser removidos en la versión 17, por lo que hay que tener
cuidado de utilizarlos.

Hoy en día los métodos deprecados pueden seguir siendo utilizados con su
nombre original, sin embargo, siempre nos arrojará warnings en la consola, a
menos que renombremos los métodos con el prefijo UNSAFE_, por ejemplo
UNSAFE_componentWillMount, UNSAFE_componentWillReceiveProps y
UNSAFE_componentWillUpdate. Finalmente, se espera que para la versión 17 de
React, solo estén disponibles estos métodos con el prefijo UNSAFE_, por lo que
mi consejo es, no utilizar nunca más estos métodos en desarrollos nuevos, pues
están programados para ser removidos por completo en versiones posteriores.

Vamos a tratar de mencionar para que sirve todos estos métodos, incluso los
desaconsejados, con la intención de que si los ves en algún proyecto antiguo
sepas que hacen, pero no nos centraremos de lleno en entender como funcionaba
todo este ciclo de vida, pues consideramos que es algo que ya va de salida y no
vale la pena invertir mucho tiempo en ello.

Una vez que ya hablamos del ciclo de vida antiguo de React, pasaremos a la
nueva configuración del ciclo de vida que está disponible a partir de la versión
16.3:

En la imagen anterior podemos ver la nueva configuración del ciclo de vida,


marcando en amarillo los nuevos métodos que son agregados a partir de la
versión 16.3.

Página | 182
Hemos tratado de ordenar los métodos del ciclo de vida según el orden en el que
se ejecutan, sin embargo, no siempre se ejecutarán todos los métodos o incluso,
habrá otros que se ejecuten más de una vez, por lo que la imagen anterior
representa el flujo de un componente que se monta, actualiza (una sola vez) y
finalmente de desmonta.

En las siguientes secciones explicaremos con detalle para que sirve cada método
de forma individual y seguido explicaremos como interviene cada método en los
3 escenarios posibles por los que puedes pasar un componente, es decir,
Montaje, Actualización y Desmontaje.

Para comprender el ciclo de vida de los componentes es super importante


entender 3 conceptos fundamentes:

Nuevo concepto: Montaje

En React, el montaje es el proceso por medio del cual


el componente es construido y renderizado en pantalla
por primera vez, por lo tanto, se considera montado
solo cuando el componente ya es visible en pantalla y
ya es parte del Document Object Model (DOM).

Nuevo concepto: Actualización

En React, la actualización es el proceso por medio del


cual un componte ya montado es actualizado, ya sea
por cambiar el state o las props.

Nuevo concepto: Desmontaje

En React, el desmontaje es el proceso por medio del


cual un componte es destruido y finalmente removido
del Document Object Model (DOM), lo que implica que
no sea visible en pantalla.

Dicho lo anterior, podemos ver que métodos del ciclo de vida se ejecutan en cada
etapa:

183 | Página
El Constructor

Antes que nada, me gustaría aclarar que el constructor no es como tal un método
del ciclo de vida, sin embargo, lo quise agregar debido a que a partir de la versión
16.3 toma un mayor protagonismo al ser deprecado el método
componentWillMount.

Como vamos a ver en el siguiente punto, el método componentWillMount se


ejecutaba justo antes de montar el componente, por lo que se utiliza para
inicializar el componente, como llamar algún servicio o inicializar el status, sin
embargo, a partir de la versión 16.3 de React el método componentWillMount ha
sido deprecado, por lo que se aconseja utilizar el constructor para inicializar el
componente.

Si bien no es necesario que todos los componentes definan el constructor, es


importante hacer algunas aclaraciones para evitar posibles errores en tiempo de
ejecución, así que lo analizaremos con un ejemplo:

1. import React from 'react'


2. import { render } from 'react-dom'
3. import "core-js/stable";
4. import "regenerator-runtime/runtime";
5.
6. class App extends React.Component {
7. constructor(args){
8. super(args)
9. APIInvoke.invokeGET("/tweets", response => {
10. console.log("api response =>")
11. this.setState(response.body)
12. }, error => {
13.

Página | 184
14. })
15. }
16.
17.
18. render() {
19. console.log("render =>")
20. return (
21. <h1>Hello World</h1>
22. )
23. }
24. }
25. render(<App />, document.getElementById('root'));

Lo primero que tenemos que tomar en cuenta es que el constructor recibe como
parámetro las propiedades, por lo que es indispensable agregar dicho parámetro.
Por otra parte, es super importante que la primera línea del cuerpo del
constructor mande llamar al constructor de la super clase mediante super(args),
de lo contrario, el componente no será inicializado correctamente y las props no
estarán disponibles en tiempo de ejecución.

Error común

No inicializar el constructor de la super clase con


super(args) puede provocar que las props no estén
disponibles en tiempo de ejecución.

Los siguiente a tomar en cuenta es que cualquier llamada a un servicio externo


o cualquier operación que sea asíncrona, dará como resultado que el componente
sea renderizado antes de obtener los resultados de esa llamada, por ejemplo, en
la clase anterior, hacemos una llamada al API REST mediante la clase APIInvoke,
y luego en el método render imprimimos el mensaje “render” en pantalla.

Un programador inexperto esperaría que el método render sea llamado justo


después de que el API response, sin embargo, al ser una llamada asíncrona, el
constructor termina y una vez terminado, se ejecuta la llamada al API, lo que da
como resultado que veamos en el log del navegador la siguiente salida:

1. render =>
2. API response =>

Esto es importante porque hay que ser precavidos en el método render y no


esperar que los valores del estado estén inicializados previo al primer renderizado
del componente.

185 | Página
Importante

El constructor solo se ejecuta una sola vez durante


todo el ciclo de vida de un componente.

Function getDerivedStateFromProps

La función getDerivedStateFromProps se utiliza en casos muy raros donde el


estado del componte depende de las props, de tal forma que este método permite
actualizar el estado en función de las propiedades y retornar un nuevo estado.

Este método recibe como parámetro el estado y las propiedades, y deberá


retornar un objeto, el cual será establecido como el nuevo estado del
componente, o retornar null para indicar que se conserva el estado actual sin
aplicarle ningún cambio.

1. static getDerivedStateFromProps(props, state){


2. if (props.client.id != state.cliente.id) {
3. return {
4. ...state,
5. client: props.client
6. }
7. }
8. return null
9. }

Observa que este es el único método del ciclo de vida que es estático, lo que
quiere decir que no tiene acceso a la instancia del componente, por lo tanto, todo
lo relacionado al DOM no estará disponible.

Importante

La función getDerivedStateFromProps se ejecuta cada


vez que las propiedades del componente cambien y se
ejecuta justo después del constructor y justo antes del
render, tanto en el montaje como las actualizaciones

Página | 186
Function componentWillMount
(deprecated)

La función componentWillMount() se ejecuta antes de que el componente sea


montado y antes de la función render(). Se utiliza por lo general para realizar la
carga de datos desde el servidor o para realizar una inicialización síncrona del
estado.

Esta función no recibe ningún parámetro y se ve de la siguiente manera:

1. UNSAFE_componentWillMount(){
2. //Any action
3. }

En este punto, los elementos del componente no podrán ser accedidos por medio
del DOM, pues aún no han sido creados, esto quiere decir que si buscamos un
elemento en el DOM mediante document.getElementById(“id”), este no existirá
porque en este punto no ha sido enviado al DOM.

Importante

Recordemos que este método fue deprecado a partir de


React 16.3, por lo que no deberíamos utilizarlo más,
sin embargo, si se llegara a utilizar, deberá de llevar el
prefijo UNSAFE_.

Importante

La función componentWillMount solo se ejecuta una sola


vez durante todo el ciclo de vida de un componente.

Function render

El método render() es el único de todos los métodos del ciclo de vida que es
obligatorio, pues es el que se encarga de definir como el componente deberá ser
renderizado en pantalla, por lo que como resultado deberá retornar cualquiera
de los siguientes valores:

187 | Página
• Elementos de React: Son elementos creados normalmente con ayuda
de JSX, como por ejemplo un <div/> o un componente <MyComponent/>.
• Arrays y fragmentos: Los fragmentos (<></>) ya los discutimos con
anterioridad, los cuales nos permiten retornar múltiples elementos si la
necesidad de retornar un elemento root concreto, por otro lado, es
posible retornar un array con múltiples elementos.
• Portales. Los portales proporcionan una opción de primera clase para
renderizar hijos en un nodo DOM que existe por fuera de la jerarquía del
DOM del componente padre. Puedes ver más de los portales en la
documentación oficial.
• String y números. Estos son renderizados como nodos de texto en el
DOM.
• Booleanos o nulos. No renderizan nada, pero es utilizado como
estrategia para impedir que el componente se muestre si alguna
condición no se cumple.

La función tiene el siguiente formato:

1. render(){
2. // Vars and logic sección
3. return (
4. //JSX section
5. )
6. }

Podemos dividir el método en dos partes, el cuerpo del método y el retorno, la


primera parte (línea 2), nos permite declarar variables, hacer validaciones,
llamadas a métodos, o cualquier instrucción JavaScript válida, en segundo lugar
tenemos el return (línea 4), donde podemos retornar cualquier de los posibles
valores que ya explicamos.

La función render debe ser pura, lo que significa que no deberá modifica el estado
del componente, devuelve el mismo resultado cada vez que se invoca con los
mismos parámetros y no interactúa directamente con el navegador.

Error común

Actualizar el estado desde la función render puede


provocar que el componente se cicle, en un
renderizado infinito, ya que cada actualización del
estado dispara nuevamente el método render.

Error común

Se debo de evitarse intentar interactuar con el DOM


desde el método render, ya que en este punto los

Página | 188
elementos no han sido renderizados en pantalla, por
lo tanto, no existen en el navegador.

Finalmente, la función render no será invocado si shouldComponentUpdate


devuelve falso (hablaremos de esta función más adelante).

Importante

La función render se ejecutará cada vez que el state o


las props del componente cambien, lo que significa que
se puede ejecutar muchas veces durante todo su ciclo
de vida.

Function getSnapshotBeforeUpdate

Según la documentación oficial de React, la función getSnapshotBeforeUpdate se


invoca justo antes de que la salida renderizada más reciente se entregue. Permite
al componente capturar cierta información del DOM (por ejemplo, la posición del
scroll) antes de que se cambie potencialmente. Cualquier valor que se devuelva
en este ciclo de vida se pasará como parámetro al método componentDidUpdate.

Dicho de otra forma, este método nos permite recuperar cierta información del
componente justo antes de que se actualice en pantalla, de esta forma, podemos
mandar lo que encontramos como parámetro al método componentDidUpdate para
hacer alguna acción.

1. getSnapshotBeforeUpdate(prevProps, prevState) {
2. if (prevProps.list.length < this.props.list.length) {
3. const list = this.listRef.current;
4. return list.scrollHeight - list.scrollTop;
5. }
6. return null;
7. }
8.
9. componentDidUpdate(prevProps, prevState, snapshot) {
10. if (snapshot !== null) {
11. const list = this.listRef.current;
12. list.scrollTop = list.scrollHeight - snapshot;
13. }
14. }

Observa que la función getSnapshotBeforeUpdate retorna un valor, el cual es


pasado como parámetro a componentDidUpdate (línea 9).

189 | Página
Este es quizás el método más complicado de entender y quizás el menos utilizado
de todos, por lo que es probable que no lo utilices en un largo tiempo, pero vale
la pena que al menos comprendas el concepto.

Importante

La función getSnapshotBeforeUpdate se ejecutará justo


después del método render pero antes de que los
cambios sean reflejados en el navegador.

Function componentDidMount

La función componentDidMount() se ejecuta justo después del método render(),


cuando el componente ya ha sido montado y todos los elementos del DOM ya
están disponibles, y es utilizado para cargar datos del servidor o para realizar
operaciones que requieren elementos del DOM. En este punto todos los
elementos ya existen en el DOM y pueden ser accedidos.

1. componentDidMount(){
2. //Any action
3. }

En esta función es seguro modificar el estado o cargar datos del servidor.

Importante

La función componentDidMount solo se ejecuta una sola


vez durante todo el ciclo de vida de un componente.

Function componentWillReceiveProps
(deprecated)

La función componentWillReceiveProps(nextProps) se invoca cada vez que un


componente ya montado, recibe nuevas propiedades. Este método no se ejecuta
durante el montaje inicial.

Recibe la variable nextProps que contiene los valores de las nuevas propiedades,
por lo que es posible comparar los nuevos valores (nextProps) contra los props

Página | 190
actuales (this.props) para determinar si tenemos que realizar alguna acción para
actualizar el componente.

1. UNSAFE_componentWillReceiveProps(nextProps){
2. // Any action
3. }

Hay que tener en cuenta que esta función se puede llamar incluso si no ha habido
ningún cambio en las props, por ejemplo, cuando el componente padre es
actualizado.

Este método es utilizado con frecuencia cuando el estado del componente


depende de las propiedades, lo que nos permite actualizar el estado cuando las
propiedades del componente cambien.

Importante

Recordemos que este método fue deprecado a partir


de React 16.3, por lo que no deberíamos utilizarlo más,
sin embargo, si se llegara a utilizar, deberá de llevar el
prefijo UNSAFE_.

Importante

La función componentWillReceiveProps se ejecuta cada


vez que las propiedades de un componente ya
montado cambien, lo que significa que se puede
ejecutar varias veces durante todo su ciclo de vida.

Function shouldComponentUpdate

La función shouldComponentUpdate(nextProps, nextState) se ejecuta justo antes


de la función render() y se utiliza para determinar si un componente requiere ser
actualizado. Por default, este método siempre retorna true, lo que significa que
el método render se ejecutará siempre que el state o las props cambien.

Esta función existe únicamente para mejorar el performance de un componente,


permitiéndole determinar si el método render debe de ser ejecutado o no, incluso
si el state o las props cambien.

Esta función recibe las nuevas propiedades (nextProps) y el nuevo estado


(nextState) y deberá de retornar forzosamente un valor booleano, donde un true

191 | Página
le indica a React que el método render deberá ser ejecutado, en otro caso, el
método render será omitido y el componente no se actualizará.

1. shouldComponentUpdate(nextProps, nextState) {
2. // Any action
3. return boolean
4. }

Error común

Cabe mencionar que según la misma documentación


oficial de React, este método es solo una
recomendación, por lo que devolver false, puede
incluso dar como resultado una nueva renderización
del componente.

Importante

La función shouldComponentUpdate se ejecutará cada


vez que el state o las props del componente cambien,
por lo que es posible que se ejecute varias veces
durante todo el ciclo de vida del componente.

Function forceUpdate

La función forceUpdate no en realidad un método del ciclo de vida de un


componente, más bien es un método que tiene todos los componentes para
forzar la actualización del componente, brincándose el método
shouldComponentUpdate y pasando directo al método render.

1. someMethod() {
2. // Forzar la actualización
3. this.forceUpdate();
4. }

Este método puede ser llamado desde cualquier parte del componente,
provocando que el método render se ejecuta, incluso si las propiedades y el
estado no cambiaron.

Este es un método de emergencia, cuando por alguna razón no logramos que el


componte se actualice cuando queremos, por lo que lo debemos de utilizar con
cuidado, y en su lugar debemos de procurar ajustarnos al ciclo de vida normal
de React.

Página | 192
Importante

La función forceUpdate jamás se ejecuta como parte


del ciclo de vida de un componente en React, sino más
bien, es llamado bajo demanda por el mismo
programador en caso extremos.

Function componentWillUpdate
(deprecated)

La función componentWillUpdate(nextProps, nextState) se ejecuta antes de la


función render() cuando se reciben nuevas propiedades o estado. Se utiliza para
preparar el componente antes de la actualización. Este método no se llama
durante el montado inicial del componente.

Tenga en cuenta que no se puede llamar a this.setState() desde aquí. En caso


de requerir actualizar el estado en respuesta a un cambio en las propiedades,
entonces deberá utilizar el método componentWillReceiveProps().

1. componentWillUpdate(nextProps, nextState) {
2. // Any action
3. }

La función componentWillUpdate() no será invocado si la función


shouldComponentUpdate() retorna false.

Importante

Recordemos que este método fue deprecado a partir


de React 16.3, por lo que no deberíamos utilizarlo más,
sin embargo, si se llegara a utilizar, deberá de llevar el
prefijo UNSAFE_.

Importante

La función componentWillUpdate se ejecuta previo


siempre que el state o las props del componte
cambien, siempre y cuando el método
shouldComponentUpdate retorne true, por lo que se
puede ejecutar varias veces durante todo el ciclo de
vida del componente.

193 | Página
Function componentDidUpdate

La función componentDidUpdate(prevProps, prevState) se ejecuta justo después


del método render(), pero solo cuando es actualizado, lo que implica que no será
invocado durante la fase de montaje.

Esta función recibe como parámetro el estado anterior, las propiedades


anteriores y el snapshot generado por la función getSnapshotBeforeUpdate, y se
utiliza para realizar operaciones sobre los elementos del DOM o para consumir
recursos de red, imágenes o servicios del API, sin embargo, es necesario validar
el nuevo estado y props contra los anteriores, para determinar si es necesario
realizar alguna acción.

1. componentDidUpdate(prevProps, prevState, snapshot ){


2. // Any action
3. if (this.props.userID !== prevProps.userID) {
4. APIInvoke.invokeGET()
5. }
6. }

Este servicio se utiliza a menudo para hacer referencia a elementos del DOM una
vez que ya están disponibles, por otra parte, también es utilizado para consultar
recursos en la red, como es el caso del API, sin embargo, es importante siempre
validar el state y las props para asegurarse de que realmente algo cambio para
justificar la búsqueda en la red.

Importante

La función componentDidUpdate se ejecuta siempre que


el estado o las propiedades sean actualizadas, con la
condición de que el método shouldComponenteUpdate
retorne true. Esto quiere decir que este método se
puede ejecutar varias veces durante todo el ciclo de
vida de los componentes.

Function componentWillUnmount

La función componentWillUnmount() se invoca inmediatamente antes de que un


componente sea desmontado y destruido. Se utiliza para realizar tareas de
limpieza, como invalidar temporizadores, cancelar solicitudes de red o limpiar
cualquier elemento del DOM. Esta función no recibe ningún parámetro.

1. componentWillUnmount() {
2. // Any action
3. }

Página | 194
Actualizar el estado en este método será en vano, pues el componente nunca
más será renderizado.

Importante

La función componentWillUnmount solo se ejecuta una


sola vez durante todo el ciclo de vida de un
componente.

Flujos de montado de un componente

En esta sección se describe el ciclo de vida por el cual pasa un componente


cuando es montado por primera vez. Se dice que un componente es montado
cuando es creado inicialmente y mostrado por primera vez en pantalla.

Fig. 57 - Ciclo de vida del montaje de un componente.

Cuando un componente es montado inicialmente, siempre se ejecutará el


constructor de la clase, pues el componente no existe en ese momento. El
constructor lo podemos utilizar para inicializar el estado y realizar operaciones
de carga de servicios. Cabe mencionar que el constructor no es precisamente
parte del ciclo de vida del componente, sino más bien es una característica de
los lenguajes orientados a objetos, como es el caso de JavaScript.

Justo antes de que el componente sea renderizado, el método


getDerivedStateFromProps es ejecutado, con la intención de actualizar el estado

195 | Página
basado en las propiedades. Recordemos que este método regresa el nuevo
estado del componte o null para dejar el estado actual.

El siguiente paso es la renderización del componente mediante el método


render(). En este momento son creados los elementos en el DOM y el
componente es mostrado en pantalla. En este punto no debemos actualizar el
estado mediante setState(), pues podemos ciclar en una actualización sin fin.

Una vez que React ha terminado de renderizar el componente mediante render(),


ejecuta la función componentDidMount para darle la oportunidad a la aplicación de
realizar operaciones sobre los elementos del DOM. En este punto es
recomendable realizar carga de datos o simplemente actualizar elementos
directamente sobre el DOM.

Flujos de actualización

El flujo de actualización de un componente se dispara cuando se actualiza el


estado mediante this.setState(), pero también las propiedades entrantes
pueden detonar en la actualización, por lo que en ocasiones la actualización del
componente padre, puede detonar en la actualización del estado en los elementos
hijos.

Fig. 58 – Ciclo de vida de la actualización del estado.

Justo antes de que el componente sea actualizado, el método


getDerivedStateFromProps es ejecutado, con la intención de actualizar el estado
basado en las propiedades. Recordemos que este método regresa el nuevo
estado del componte o null para dejar el estado actual.

Página | 196
Cuando React detecta cambios en el estado o las propiedades, lo primero que
hará React será validar si el componente debe ser o no actualizado, para esto,
existe la función shouldComponentUpdate. Esta función deberá retornar true en
caso de que el componente requiera una actualización y false en caso contrario.
Si esta función retorna false, el ciclo de vida se detiene y no se ejecuta el resto
de los métodos.

El siguiente paso es la actualización del componente en pantalla, mediante la


ejecución de la función render(), en este punto los elementos son creados en el
DOM.

Entre que el método render termina y los cambios son reflejados en el navegador,
el método getSnapshotBeforeUpdate es ejecutado, con la intención de obtener
datos relevantes del DOM actual vs el nuevo DOM para pasar estos datos
relevantes al método componentDidUpdate como parámetros.

Una vez que el componente es actualizado y los elementos ya son visibles en el


navegador, se ejecuta la función componentDidUpdate, el cual permite realizar
carga de datos o realizar operaciones que requiera de la existencia de los
elementos en el DOM. Recordemos que este método recibe el snapshot de
getSnapshotBeforeUpdate.

Flujos de desmontaje de un componente

El ciclo de vida de desmontaje se activa cuando un componente ya no es


requerido más y requiere ser eliminado. El desmontaje se pueda dar por ejemplo
cuando un componente es eliminado de una lista o un componente es remplazado
por otro.

Fig. 59 - Ciclo de vida del desmontaje de un componente.

El ciclo de vida de desmontaje es el más simple, pues solo se ejecuta la función


componenteWillUnmount, que es utilizada para realizar alguna limpieza o eliminar
algún elemento del DOM antes de que el componente sea destruido.

197 | Página
Mini Twitter (Continuación 2)

En esta sección continuaremos desarrollando nuestro proyecto Mini Twitter. Con


los nuevos conocimientos podremos continuar desarrollando componentes más
complejos y al terminar esta sección podremos ir viendo como el proyecto va
tomando forma.

Configuración inicial

Antes de comenzar a construir componentes y empezar a mostrar algo en


pantalla, es necesario hacer algunas configuraciones e instalar algunas
dependencias para dejar preparado el proyecto para lo que viene, así que
comenzaremos instalando algunas dependencias.

• npm install --save history@4.10.1

• npm install --save react-router@5.1.2

Lo siguiente será crear el archivo History.js en el path /app con el siguiente


contenido:

1. import { createBrowserHistory } from 'history';


2. export default createBrowserHistory();

Este archivo nos permitirá controlar el historial del navegador para poder
redireccionar al usuario a diferentes páginas a medida que ciertos eventos
ocurran en la aplicación.

Ahora regresaremos al archivo App.js y lo ajustaremos para que quede de la


siguiente manera:

1. import React from 'react'


2. import { render } from 'react-dom'
3. import TwitterApp from './TwitterApp'
4. import { Router } from "react-router-dom";
5. import history from './History'
6.
7. render((
8. <Router history={history}>
9. <TwitterApp />
10. </Router>
11. ), document.getElementById('root'));

Página | 198
No te preocupes si no comprendes nada esta este momento, en la siguiente
sección regresaremos para explicar que está pasando aquí. Finalmente, podrás
ver que en este punto la aplicación aun no funciona, pues todavía faltan
componentes por desarrollar, los cuales veremos a continuación.

El componente TwitterApp

El componte TwitterApp podría ser considerado el componente raíz de la


aplicación, ya que servirá como contendor para todo el resto de componentes
que iremos creado de ahora en adelante, por lo que su principal rol será el de
determinar que página deberá ver el usuario a partir de la URL y si está
autenticad o no.

Para que este componente pueda determinar que página mostrar, es necesario
identificar si el usuario ya está autenticado, y una vez determinado eso, podrá
decidir si permite el acceso a la URL solicitad.

Dicho lo anterior, procederemos a crear el archivo TwitterApp.js en el path /app,


el cual deberá tener el siguiente contenido:

1. import React from 'react'


2. import APIInvoker from "./utils/APIInvoker"
3. import browserHistory from './History'
4. import { Route, Switch } from "react-router-dom";
5. import Signup from './Signup'
6. import Login from './Login'
7. import TweetsContainer from './TweetsContainer'
8.
9. class TwitterApp extends React.Component {
10.
11. constructor(props) {
12. super(props)
13. this.state = {
14. load: false,
15. profile: null
16. }
17. }
18.
19. componentDidMount() {
20. let token = window.localStorage.getItem("token")
21. if (token == null) {
22. this.setState({
23. load: true,
24. profile: null
25. })
26. browserHistory.push('/login')
27. } else {
28. APIInvoker.invokeGET('/secure/relogin', response => {
29. this.setState({
30. load: true,
31. profile: response.profile
32. });
33. window.localStorage.setItem("token", response.token)
34. window.localStorage.setItem("username", response.profile.userName)
35. browserHistory.push('/')
36. }, error => {

199 | Página
37. console.log("Error al autenticar al autenticar al usuario ");
38. window.localStorage.removeItem("token")
39. window.localStorage.removeItem("username")
40. browserHistory.push('/login');
41. })
42. }
43. }
44.
45. render() {
46. if(!this.state.load){
47. return null
48. }
49.
50. return (
51. <>
52. <div id="mainApp" className="aminate fadeIn">
53. <Switch>
54. <Route exact path="/" component={ () =>
55. <TweetsContainer profile={this.state.profile} />} />
56. <Route exact path="/signup" component={Signup} />
57. <Route exact path="/login" component={Login} />
58. </Switch>
59. <div id="dialog" />
60. </div>
61. </>
62. )
63. }
64. }
65. export default TwitterApp;

Lo primero que tenemos que observar es el estado inicial del componente que
definimos en el constructor, donde definimos la propiedad load y profile. La
propiedad load es de tipo booleano, lo que indica si ya se llevó a cabo la
comprobación de si el usuario está autenticado, y la propiedad profile guardar
el perfil del usuario solo cuando este ya está autenticado. Pongamos mucha
atención en estas dos propiedades, porque son clave para entender lo que viene
a continuación.

Una vez que el constructor termina, el método render es invocado, lo que implica
que React intentará renderiza el componente, sin embargo, podemos observar
en la línea 46 que tenemos una validación para comprobar la propiedad load, lo
que implica que retornaremos null si la propiedad load es false, es decir, que
no ha concluido la validación del usuario.

En esta misma unidad hablamos del método render y los posibles valores que
puede devolver, y mencionamos que entre unos de los valores válidos es null,
el cual utilizamos saltarnos el renderizado del componente, de esta forma
evitamos que React construya la vista.

Una vez que el método render finaliza, React ejecutará el siguiente método del
ciclo de vida definido, es decir componentDidMount. En este método intentaremos
comprobar si el usuario está autenticado, para esto, será necesario comprobar si
el usuario cuenta con un Token (hablaremos de los tokens más adelante), el cual

Página | 200
es una cadena alfanumérica que expide el API REST para comprobar que el
usuario está autenticado.

Documentación: actualización de credenciales


(relogin)

Este servicio tiene como finalidad validar si un token


es vigente y darnos un nuevo token con una vigencia
actualizada (/secure/relogin)

En este punto pueden suceder dos cosas: la primera es que el usuario no cuente
con un token (línea 21) y sea redireccionado a la página de login (página 26), la
segunda es que el usuario si cuenta con un token, por lo que tendremos que
mandar el token al API REST (línea 28) para validar su autenticidad. Existen dos
posibles respuestas del API, la primera, es que el token se validó, lo que
implicaría que el API nos regresaría el perfil del usuario y lo utilizaríamos para
actualizar el state (línea 29), actualizando la propiedad load a true y profile
con el perfil retornado por el API y luego guardaremos el token retornado en el
local storage (línea 33), por otra parte, si el token es invalido, borraremos el
token del local storage (línea 38) y redireccionaremos al usuario a la página de
login (línea 40).

Justo en el momento en el que el token es validado, el estado es actualizado, lo


que implica que el método render será nuevamente ejecutado, con la diferencia
de que ahora la propiedad load tendrá el valor true, lo que implica que el
componente ahora si será renderizado.

Finalmente, podrás observar tres componentes Router, los cuales se activan solo
cuando la URL actual del navegador corresponde con la propiedad path,
renderizando el componente definido en la propiedad component.

Nuevamente no entraremos en muchos detalles, pues todo lo que vemos aquí es


nuevo y lo abordaremos en la siguiente sección de este libro, cuando abordemos
de lleno el tema de React Router. Por ahora solo limitémonos a dejar el archivo
tal cual se muestra.

En este punto, nuestra aplicación debería funcionar perfectamente, de tal forma


que, si no estamos autenticados, nos deberá mandar a la pantalla de Login, y en
caso de estar autenticado nos deberá mostrar los Tweets.

201 | Página
Fig. 60 - Usuario no autenticado.

Fig. 61 - Usuario autenticado.

NOTA: Debido a que todavía no tenemos una forma de cerrar la sesión, solo
tenemos una forma de cerrar la sesión, que es ir al local storage del navegador
y borrar el token manualmente:

Página | 202
Para ir al local storage hay que abrir el inspector de elementos del navegador,
ubicar la pestaña Application, seleccionar el local storage, seleccionar el token
y presionar suprimir.

El componente TwitterDashboard

El siguiente paso consistirá mejorar la página de inicia, para no solo mostrar los
Tweets, si nos que nos muestre la barra de navegación en la parte superior, el
perfil del usuario autenticado, los tweets en el centro, y del lado derecho, una
lista de usuarios sugeridos para seguir, la idea es hacer que la página de inicio
se vea como la siguiente imagen:

El resultado anterior no se logra con un solo componente, sino más bien, es el


resultado de la composición de varios componentes anidados que dan como
resultado la página anterior.

203 | Página
Puede que esto te resulte confuso en este momento, pero veamos la siguiente
imagen para darnos una idea de los componentes que será necesario para lograr
el resultado esperado:

Fig. 62 – Componente TwitterDashboard.

En esta sección nos centraremos en el componente TwitterDashboard, que como


puedes ver en la imagen anterior, es solo el contenedor de los componentes
Profile, TweetsContainer, SuggestedUser y Toolbar, los cuales son componentes
que veremos en las siguientes secciones.

Dicho lo anterior, procederemos con la creación del componente


TwitterDashboard.js en el path /app, el cual se verá de la siguiente manera:

1. import React from 'react'


2. import Profile from './Profile'
3. import TweetsContainer from './TweetsContainer'
4. import SuggestedUser from './SuggestedUser'
5. import PropTypes from 'prop-types'
6.
7. const TwitterDashboard = (props) => {
8.
9. return (
10. <div id="dashboard" className="animated fadeIn">
11. <div className="container-fluid">
12. <div className="row">
13. <div className="hidden-xs col-sm-4 col-md-push-1
14. col-md-3 col-lg-push-1 col-lg-3" >
15. <Profile profile={props.profile} />
16. </div>
17. <div className="col-xs-12 col-sm-8 col-md-push-1
18. col-md-7 col-lg-push-1 col-lg-4">
19. <TweetsContainer profile={props.profile} />
20. </div>
21. <div className="hidden-xs hidden-sm hidden-md
22. col-lg-push-1 col-lg-3">
23. <SuggestedUser />
24. </div>

Página | 204
25. </div>
26. </div>
27. </div>
28. )
29. }
30.
31. TwitterDashboard.propTypes = {
32. profile: PropTypes.object.isRequired
33. }
34.
35. export default TwitterDashboard;

Lo primero que podemos observar del componente TwitterDashboard es un


componente de función o componente sin estado (Stateless component), ya que
simplemente es utilizado para organizar como se verán visualmente los
componentes Profile (línea 15), TweetsContainer (línea 19) y SuggestedUser
(línea 23) en un formato de 3 columnas.

Otro de los puntos importantes es que este componente requiere como


parámetro el perfil del usuario autenticado, ya que será necesario para replicarlos
a los componentes Profile y TweetsContainer, por este motivo, definimos que la
propiedad profile sea obligatoria (línea 32).

Tip

Para lograr el sistema de columnas estamos utilizando


Boostrap, el cual nos permite dividir la página en hasta
12 columnas, así como determinar cuántas columnas
tomará cada componte según la resolución de la
pantalla.

En este punto la aplicación todavía no podrá funcionar, pues todavía faltan


componentes algunos componentes por definir, así que se paciente.

El componente Profile

El componente Profile es un Widget muy simple, que muestra los datos de


nuestro perfil, como es la foto, nombre de usuario, número de seguidores,
número de personas a las que seguimos y un contador de Tweets. La siguiente
imagen muestra el resultado que buscamos conseguir:

205 | Página
Algo a tomar en cuenta es que, el perfil lo obtenemos desde que la aplicación
valida nuestro token, por lo que el perfil será enviado al componente Profile
como una prop. El mapeo de los campos se puede ver a continuación:

Fig. 63 - Campos del componente Profile.

Iniciaremos creado un archivo llamado Profile.js en el path /app, el cual deberá


tener la siguiente estructura:

1. import React from 'react'


2. import { Link } from 'react-router-dom'
3. import PropTypes from 'prop-types'
4.
5. const Profile = (props) => {
6.
7. return (
8. <aside id="profile" className="twitter-panel">
9. <div className="profile-banner">
10. <Link to={"/" + props.profile.userName}
11. className="profile-name" style={{
12. backgroundImage: (props.profile.banner!=null
13. ? `url('${props.profile.banner}')`
14. : 'none')}}
15. />
16. </div>
17. <div className="profile-body">
18. <img className="profile-avatar" src={props.profile.avatar} />
19. <Link to={"/" + props.profile.userName}
20. className="profile-name">

Página | 206
21. {props.profile.name}
22. </Link>
23.
24. <Link to={"/" + props.profile.userName}
25. className="profile-username">
26. @{props.profile.userName}
27. </Link>
28. </div>
29. <div className="profile-resumen">
30. <div className="container-fluid">
31. <div className="row">
32. <div className="col-xs-3">
33. <Link to={`/${props.profile.userName}`}>
34. <p className="profile-resumen-title">TWEETS</p>
35. <p className="profile-resumen-value">
36. {props.profile.tweetCount}</p>
37. </Link>
38. </div>
39. <div className="col-xs-4">
40. <Link to={`/${props.profile.userName}/following`}>
41. <p className="profile-resumen-title">SIGUIENDO</p>
42. <p className="profile-resumen-value">
43. {props.profile.following}</p>
44. </Link>
45. </div>
46. <div className="col-xs-5">
47. <Link to={`/${props.profile.userName}/followers`}>
48. <p className="profile-resumen-title">SEGUIDORES</p>
49. <p className="profile-resumen-value">
50. {props.profile.followers}</p>
51. </Link>
52. </div>
53. </div>
54. </div>
55. </div>
56. </aside>
57. )
58. }
59.
60. Profile.propTypes = {
61. profile: PropTypes.object.isRequired
62. }
63.
64. export default Profile;

Lo primero que podemos observar se trata de un componente de función o


Componente sin estado (Stateless Component), ya al recibir toda la información
que requiere desde las propiedades, no requiere de un estado.

Lo segundo interesante es el método render, que si bien puede parecer


complicado, no es más que un componente muy simple que lo único que hace es
representar los datos del perfil del usuario, como lo es el banner (línea 10-15),
nombre (línea 19-22), nombre de usuario (línea 24-27), contador de Tweets
(línea 33-37), personas seguidas (línea 40-44) y número de seguidores (línea
47-51).

Finalmente, podemos observar que hemos marcado la propiedad profile como


obligatoria (línea 61).

207 | Página
NOTA: Podremos observar repetidas veces el componente <Link>, el cual
analizaremos en la siguiente sección, por lo pronto te adelanto que este
componente se convierte en una etiqueta <a> y el atributo to equivaldría al
atributo href, lo que da como resultado un simple link.

Finalmente, tendremos que agregar las clases de estilo para que el componente
se vea correctamente, para esto, regresaremos al archivo styles.css y
agregaremos las siguientes clases de estilo al final del archivo.

1. /** PROFILE COMPONENT **/


2.
3. .twitter-panel{
4. background-color: #fff;
5. background-clip: inherit;
6. border: 1px solid #e6ecf0;
7. border-radius: 5px;
8. line-height: inherit;
9. }
10.
11. #suggestedUsers,
12. #profile{
13. max-width: 350px;
14. }
15.
16. #profile{
17. float: right;
18. width: 100%;
19. max-width: 350px;
20. }
21.
22. #profile{
23. overflow: hidden;
24. }
25.
26. #profile .profile-banner{
27. }
28.
29. #profile .profile-banner a{
30. min-height: 100px;
31. background-size: cover;
32. display: block;
33.
34. }
35.
36. #profile .profile-body{
37. position: relative;
38. padding-top: 5px;
39. padding-bottom: 10px;
40. }
41.
42. #profile .profile-body img{
43. position: absolute;
44. display: inline-block;
45. border: 3px solid #fafafa;
46. border-radius: 8px;
47. height: 75px;
48. width: 75px;
49. left: 10px;
50. top: -30px;
51. }
52.

Página | 208
53. #profile .profile-body > a{
54. margin-left: 90px;
55. color: inherit;
56. }
57.
58. .profile-body > a:hover{
59. text-decoration: underline;
60. }
61.
62. #profile .profile-resumen a {
63. color:#657786;
64. }
65.
66. #profile .profile-resumen a:hover{
67. color: #1B95E0;
68. }
69.
70. #profile .profile-resumen a .profile-resumen-title{
71. font-size: 10px;
72. margin: 0px;
73. color:inherit;
74. transition: 0.5s;
75. }
76.
77. #profile .profile-resumen a .profile-resumen-value{
78. color: #1B95E0;
79. font-size: 18px;
80. }
81.
82. #profile .profile-name,
83. #profile .profile-username{
84. display: block;
85. margin: 0px;
86. }
87.
88. #profile .profile-username{
89. color: #66757f;
90. }
91.
92. #profile .profile-name{
93. font-weight: bold;
94. font-size: 18px;
95. }

En este punto no es posible probar los cambios, sino hasta terminar el siguiente
componente.

El componente SuggestedUsers

El componente SuggestedUsers es un Widget que se muestra del lado derecho del


componente TwitterDashboard, el cual muestra usuarios recomendados para
seguir. Los usuarios a mostrar son retornados por el API, por lo que el
componente solo se limita a mostrarlos.

209 | Página
Comenzaremos con crear el archivo llamado SuggestedUsers.js en el path /app,
el cual se deberá ver de la siguiente manera:

1. import React from 'react'


2. import APIInvoker from './utils/APIInvoker'
3. import {Link} from 'react-router-dom'
4.
5. class SuggestedUser extends React.Component {
6.
7. constructor(args) {
8. super(args)
9. this.state = {
10. load: false
11. }
12. }
13.
14. componentDidMount() {
15. APIInvoker.invokeGET('/secure/suggestedUsers', response => {
16. this.setState({
17. load: true,
18. users: response.body
19. })
20. }, error => {
21. console.log("Error al actualizar el perfil", error);
22. })
23. }
24.
25. render() {
26. return (
27. <aside id="suggestedUsers" className="twitter-panel">
28. <span className="su-title">A quién seguir</span>
29. <If condition={this.state.load} >
30. <For each="user" of={this.state.users}>
31. <div className="sg-item" key={user._id}>
32. <div className="su-avatar">
33. <img src={user.avatar} alt={user.name} />
34. </div>
35. <div className="sg-body">
36. <div>
37. <Link to={"/" + user.userName}>
38. <span className="sg-name">{user.name}</span>
39. <span className="sg-username">@{user.userName}</span>
40. </Link>
41. </div>
42. <Link to={"/" + user.userName}
43. className="btn btn-primary btn-sm">
44. <i className="fa fa-user-plus" aria-hidden="true"></i>
45. Ver perfil</Link>
46. </div>
47. </div>

Página | 210
48. </For>
49. </If>
50. </aside>
51. )
52. }
53. }
54. export default SuggestedUser;

Lo primero que podemos observar es que SuggestedUser es un componente con


estado (Statefull Componente), el cual utilizará la propiedad load que ya
habíamos utilizado antes para evitar que el componente se muestra hasta no
haber terminado de cargar los usuarios sugeridos.

Justo después de que el componente es montado, el método componentDidMount


será ejecutado, disparando la llamada al API (línea 15) para consultar los
usuarios sugeridos.

Cuando los usuarios sugeridos son cargados, el state es actualizado, lo que


dispara una nueva llamada al método render, y la lista de los perfiles consultados
son iterados (línea 30) para ser mostrado en pantalla. Por cada perfil, será
mostrado su foto (línea 33), su nombre y nombre de usuario (línea 37-40) y un
botón para ir al perfil (línea 42-45).

Para finalizar el componente, tendremos que agregar las clases de estilo.


Regresaremos al archivo styles.css y agregaremos las siguientes clases de estilo
al final del archivo:

1. /** SuggestedUsers Component **/


2. #suggestedUsers{
3. padding: 10px;
4. }
5.
6.
7. #suggestedUsers .su-title{
8. font-size: 18px;
9. color: #66757f;
10. margin: 0px;
11. }
12.
13. #suggestedUsers .sg-item{
14. padding: 5px 0px;
15. }
16.
17. #suggestedUsers .sg-item .su-avatar{
18. position: relative;
19. }
20.
21. #suggestedUsers .sg-item .su-avatar img{
22. display: inline-block;
23. position: absolute;
24. width: 48px;
25. height: 48px;
26. border-radius: 10px;
27. top: 3px;
28. }
29.
30. #suggestedUsers .sg-item .sg-body{

211 | Página
31. position: relative;
32. display: block;
33. margin-left: 55px;
34.
35. }
36.
37. #suggestedUsers .sg-item .sg-body .sg-name{
38. color: #333;
39. font-weight: bold;
40. padding-right: 5px;
41. }
42.
43. #suggestedUsers .sg-item .sg-body .sg-username{
44.
45. }
46.
47. #suggestedUsers .sg-item .sg-body i{
48. color: #fafafa;
49. }

En este punto, ya hemos terminado de desarrollar el componente


TwitterDashboard con todas sus dependencias, por lo que ya solo nos falta
agregarlo al componente TwitterApp para que lo muestre. Para esto,
cambiaremos el componente TweetsContainer por TwitterDashboard (no
olvidemos importarlo):

1. render() {
2. if(!this.state.load){
3. return null
4. }
5.
6. return (
7. <>
8. <div id="mainApp" className="aminate fadeIn">
9. <Switch>
10. <Route exact path="/" component={ () =>
11. <TwitterDashboard profile={this.state.profile} />} />
12. <Route exact path="/signup" component={Signup} />
13. <Route exact path="/login" component={Login} />
14. </Switch>
15. <div id="dialog" />
16. </div>
17. </>
18. )
19. }

Observa que propagamos el Perfil del usuario al componente TwitterDashboard


mediante el atributo profile (línea 11).

Finalmente guardamos los cambios, y ya deberíamos de ver la aplicación de la


siguiente forma:

Página | 212
Fig. 64 - Componente TwitterDashboard terminado.

El componente Reply

Reply es el componente que dejamos pendiente de la sección de formularios,


pues requería tener la aplicación un poco más desarrollada para que tuviera
sentido. Este componente nos permitirá crear nuevos Tweets, los cuales podrán
tener texto y una imagen:

Fig. 65 - Estado inicial del componente Reply.

Una de las características de este componente, es que tiene dos estados posibles,
el primero es el inicial, o cuando no tiene el foco. Cuando el componente no tiene
el foco, se ve de una forma compacta, en la cual solamente muestra una leyenda
invitando al usuario a que escriba lo que está pensando.

Cuando el usuario selecciona el área de texto, este pasa a su segundo estado,


desplegando el botón para cargar una foto y habilita el botón “Twittear” para
enviar el mensaje.

Fig. 66 – Estado con el foco del componente Reply.

213 | Página
Debido a que este componente tiene cierto nivel de complejidad, lo iremos
desarrollando paso a paso, con la finalidad de no poner todo el código de una y
no comprender lo que está pasando.

Iniciaremos creando el archivo Reply.js en el path /app, el cual tendrá en un


inicio el siguiente contenido:

1. import React from 'react'


2. import update from 'immutability-helper'
3. import config from '../config.js'
4. import PropTypes from 'prop-types'
5.
6. const uuidV4 = require('uuid/v4');
7.
8. class Reply extends React.Component{
9.
10. constructor(props){
11. super(props)
12. this.state={
13. focus: false,
14. message: '',
15. image: null
16. }
17. }
18.
19. render(){
20. let randomID = uuidV4();
21.
22. return (
23. <section className="reply">
24. <If condition={this.props.profile!=null} >
25. <img src={this.props.profile.avatar} className="reply-avatar" />
26. </If>
27. <div className="reply-body">
28. <textarea
29. ref={self => this.reply = self}
30. name="message"
31. type="text"
32. maxLength = {config.tweets.maxTweetSize}
33. placeholder="¿Qué está pensando?"
34. className={this.state.focus ? 'reply-selected' : ''}
35. value={this.state.message}
36. />
37. <If condition={this.state.image != null} >
38. <div className="image-box"><img src={this.state.image}/></div>
39. </If>
40.
41. </div>
42. <div className={this.state.focus ? 'reply-controls' : 'hidden'}>
43. <label htmlFor={"reply-camara-" + randomID}
44. className={this.state.message.length===0 ?
45. 'btn pull-left disabled' : 'btn pull-left'}>
46. <i className="fa fa-camera fa-2x" aria-hidden="true"></i>
47. </label>
48.
49. <input href="#" className={this.state.message.length===0 ?
50. 'btn pull-left disabled' : 'btn pull-left'}
51. accept=".gif,.jpg,.jpeg,.png"
52. type="file"
53. id={"reply-camara-" + randomID}>
54. </input>
55.

Página | 214
56. <span ref="charCounter" className="char-counter">
57. {config.tweets.maxTweetSize - this.state.message.length }</span>
58.
59. <button className={this.state.message.length===0 ?
60. 'btn btn-primary disabled' : 'btn btn-primary '}
61. >
62. <i className="fa fa-twitch" aria-hidden="true"></i> Twittear
63. </button>
64. </div>
65. </section>
66. )
67. }
68. }
69.
70. Reply.propTypes = {
71. profile: PropTypes.object,
72. operations: PropTypes.object.isRequired
73. }
74.
75. export default Reply;

Antes de continuar, será necesario instalar el módulo UUID que nos ayudará para
la generación de ID dinámicos:

• npm install --save uuid

Primero que nada, nos centraremos en el constructor del componente, pues en


él se establece el estado inicial. El estado está compuesto por tres propiedades:

• focus: Indica si el área de texto tiene el foco, con la finalidad de mostrar


el botón para la carga de la imagen y el botón para enviar el Tweet.
• message: esta propiedad está ligada al área de texto, por lo que todo lo
que escribimos en él, actualiza esta propiedad.
• Image: En esta propiedad se guarda la imagen cargada.

Fig. 67 - Campos del componente Reply.

Dentro del método render, podríamos dividir la vista en dos partes, lo que se ve
cuándo el componente no está activo (focus=false) y cuando está activo. Cuando
no está activo y solo se requiere mostrar un área de texto, solo se verá lo que

215 | Página
está entre las líneas 24 a 41 y lo que está entre las líneas 42 a 64 solo se mostrará
cuando el área de texto obtenga el foco.

Empecemos analizando la primera sección (no activo), específicamente el


<textarea> de la línea 28. Debido a que todo el componente gira alrededor de
este control, le asignaremos una referencia (ref)(línea 29) con el cual podremos
referenciarlo de una forma más rápida.

También podemos ver que el control esta mapeado contra la propiedad message
del estado (línea 35), por lo que todos lo que escribamos actualizará esta
propiedad. También tenemos un placeholder para mostrar un texto por default
(línea 32).

El atributo maxLength nos ayuda a controlar el número máximo de caracteres


permitidos, el valor lo obtenemos del archivo config.js que más adelante
analizaremos.

Finalmente, asignamos una clase de estilo dependiendo si el control tiene o no el


foco (línea 34). Solo agregamos el estilo reply-selected cuando el control tiene
el foco, con la finalidad de aumentar su tamaño.

En segundo lugar, tenemos parte del componente que se activa cuando el


textarea tiene el foco, esta sección corresponde a la línea 42 en adelante, como
podrá ver en esta misma línea, ocultamos o mostramos toda la sección mediante
una clase de estilo condicional (className={this.state.focus ? 'reply-
controls' : 'hidden'}).

Una vez esta sección es mostrada, tenemos 3 componentes que van a mostrarse,
el botón para cargar la foto, el contador de caracteres y el botón de Twittear.
Veamos la siguiente imagen:

Fig. 68 - Controles que se activan con el foco.

El contador de caracteres es lo más simple, pues solo es un span (línea 56) el


cual realiza el cálculo entre los máximos caracteres permitidos y el número de
caracteres capturados.

Por otra parte, el botón es muy simple, pues solo mandará llamar una función en
el evento onClick. En este momento no hemos implementado la función, por lo
que más adelante lo retomaremos.

Finalmente, el botón de la foto es el componente más complejo, pues para darle


una apariencia más estética, hemos tenido que jugar con dos controles, un

Página | 216
<input> y un <label> (líneas 43 a 54), esto debido a que los inputs de tipo file
se ven diferente en cada navegador:

Fig. 69 - Input file en diferentes navegadores.

Para asegurarnos de que el botón siempre se va bien en todos los navegadores,


hemos decido ocular el input mediante CSS y el label funcionará como botón,
para esto, hemos ligado el label y el input mediante el atributo id y htmlFor
respectivamente. Con ayuda del atributo htmlForm estamos asegurando que
cuando el usuario presione el label, este realizara una acción contra el input.

Algo que puede llamar mucho la atención es el ID puesto al input ("reply-camara-


" + randomID). Primero que nada, la variable randomID la creamos con ayuda del
módulo UUID, en segundo lugar y debido a que Reply es un componente pensado
para ser reutilizado, es posible que exista más de una instancia del componente
en una misma vista, es por ello, que requerimos de un identificador que nos
ayude a distinguir a cada uno, y es allí donde entra el módulo UUID, pues nos
permite crear un ID que no se repita distinguir entre cada una de las instancias
de este componente.

Configuración

Como pudimos percatarnos, el número de caracteres permitidos en el tweet y el


contador de caracteres están ligados a la propiedad config.tweets.maxTweetSize,
el cual es un valor configurable que hemos definido en el archivo config.js que
se encuentra en la raíz del proyecto, este archivo ya lo habíamos creado en el
pasado, por lo que solo lo agregaremos la sección de tweets:

1. module.exports = {
2. debugMode: true,
3. server: { ... },
4. tweets: {
5. maxTweetSize: 140
6. }
7. }

217 | Página
Esta configuración bien se puede guardada en la base de datos y proporcionarla
por API para hacerla más configurable, pero sería aumentar aún más la
complejidad de este componente. Así que, si te sientes confiado, puedes mejorar
esta característica más adelante.

Registrando los eventos

En este punto, nuestro componente se podrá ver correctamente en pantalla,


aunque todavía no podremos hacer nada con él, ya que no hemos registrado
ninguno delo eventos, por lo que en esta sección nos centraremos en agregar la
funcionalidad.

Página | 218
Los 3 componentes que tendrá eventos son:

• Textarea:
o onFocus: Cuando el control gane el foco, deberá actualizar la
propiedad focus del estado, disparando la actualización de todo el
componente para mostrar el resto de controles.
o onKeyDown: Cuando el usuario presione la tecla escape, el
componente se deberá limpiar y pasar a su estado inicial.
o onBlur: Cuando el control pierda el foco deberá limpiar el componente
dejándolo en su estado inicial, siempre y cuando no allá texto en el
textarea.
o onChange: actualizará la propiedad message del estado, sincronizado el
textarea con el estado.
• Input file:
o onChange: cuando el usuario seleccione una foto el evento onChange
se disparará para cargar la foto y ponerla en la propiedad image del
estado.
• Botón Twittear:
o onClick: cuando el usuario presione el botón twittear, se invocará una
funcione para guardar el Tweet y regresar el componente en su estado
inicial.

Control TextArea

Una vez mencionados los eventos esperados, iniciaremos con los eventos del
textarea, para lo deberemos actualizar para agregar los siguientes eventos:

1. <textarea
2. ref={self => this.reply = self}
3. name="message"
4. type="text"
5. maxLength = {config.tweets.maxTweetSize}
6. placeholder="¿Qué está pensando?"
7. className={this.state.focus ? 'reply-selected' : ''}
8. value={this.state.message}
9. onKeyDown={this.handleKeyDown.bind(this)}
10. onBlur={this.handleMessageFocusLost.bind(this)}
11. onFocus={this.handleMessageFocus.bind(this)}
12. onChange={this.handleChangeMessage.bind(this)}
13. />

El primer evento a analizar será cuando toma el foco, pues es lo que sucede
primero, para ello agregaremos la siguiente función a nuestro componente:

1. handleMessageFocus(e){
2. let newState = update(this.state,{
3. focus: {$set: true}
4. })
5. this.setState(newState)

219 | Página
6. }

Como podemos ver, esta función únicamente cambia la propiedad focus del
estado a true. Este pequeño cambio hace que el componente se actualice y
muestre el botón para cargar una imagen, el contador de caracteres y el botón
para envía el Tweet.

El siguiente evento que se produce es onChange cuando el usuario empieza a


capturar información:

1. handleChangeMessage(e){
2. this.setState(update(this.state,{
3. message: {$set: e.target.value}
4. }))
5. }

Esta función es tan simple como actualizar la propiedad message del estado a
medida que el usuario capturar el mensaje del Tweet.

Lo siguiente que puede pasar es que el usuario decida siempre no mandar el


tweet, por lo que puede cancelar lo capturado y reiniciar el componte a su estado
inicial, para ello utilizamos el evento onKeyDown:

1. handleKeyDown(e){
2. //Scape key
3. if(e.keyCode === 27){
4. this.reset();
5. }
6. }
7.
8. reset(){
9. let newState = update(this.state,{
10. focus: {$set: false},
11. message: {$set: ''},
12. image: {$set:null}
13. })
14. this.setState(newState)
15.
16. this.reply.blur();
17. }

Cuando una tecla es presionada, el evento onKeyDown es disparado, con ello,


validamos que la tecla presionada sea “escape”, es decir el KeyCode 27 (línea 3).
Si la tecla es escape, entonces se manda llamar la función reset, la cual se
encargará de limpiar el estado y volver a la normalidad el componente.

Por otra parte, la función reset además de limpiar el estado, invoca la función
blur del textarea, el cual se accede por medio de la referencia (refs), línea 16.
Con esto, el componente pierde el foco.

Página | 220
Finalmente, el usuario puede optar por seleccionar otra cosa en la pantalla y el
componente perderá el foco, es en ese momento cuando deberá pasar a su
estado inicial (siempre y cuando no tenga texto capturado). El evento onBlur es
ejecutado en este caso:

1. handleMessageFocusLost(e){
2. if(this.state.message.length=== 0){
3. this.reset();
4. }
5. }

Nuevamente, la ecuación se repite, la función reset es invocada cuando se pierde


el foco si el número de caracteres es 0.

Control Input file (Carga de imagen)

Recordemos que el botón realmente es el label asociado al input, sin embargo,


el que realiza la acción es el input. Por lo tanto, deberemos agregar el evento
onChange al input para que se vea de la siguiente manera:

1. <input href="#" className={this.state.message.length===0 ?


2. 'btn pull-left disabled' : 'btn pull-left'}
3. accept=".gif,.jpg,.jpeg,.png"
4. type="file"
5. onChange={this.imageSelect.bind(this)}
6. id={"reply-camara-" + randomID}>
7. </input>

La función imageSelect es la encargada de procesar la carga de la imagen, para


esto, se obtiene la imagen seleccionada (línea 4), luego se valida que la imagen
no sea superior a un Megabyte (línea 5). Para cargar la imagen se implementa
la función onloadend (línea 10) que actualizará el estado una vez se termine la
carga de la imagen, la imagen se guarda en la propiedad image.

1. imageSelect(e){
2. e.preventDefault();
3. let reader = new FileReader();
4. let file = e.target.files[0];
5. if(file.size > 1240000){
6. alert('La imagen supera el máximo de 1MB')
7. return
8. }
9.
10. reader.onloadend = () => {
11. let newState = update(this.state,{
12. image: {$set: reader.result}
13. })
14. this.setState(newState)
15. }
16. reader.readAsDataURL(file)

221 | Página
17. }

Con ayuda de la función readAsDataURL (línea 16) del objeto FileReader iniciamos
la carga del archivo.

Control Button

Finalmente tenemos el botón para enviar el Tweet, para lo cual,


implementaremos el evento onClick:

1. <button className={this.state.message.length===0 ?
2. 'btn btn-primary disabled' : 'btn btn-primary '}
3. onClick={this.newTweet.bind(this)}>
4. <i className="fa fa-twitch" aria-hidden="true"></i> Twittear
5. </button>

Cuando el usuario presione el botón se ejecutará la función newTweet la cual


podemos ver a continuación:

1. newTweet(e){
2. e.preventDefault();
3.
4. let tweet = {
5. _id: uuidV4(),
6. _creator: {
7. _id: this.props.profile._id,
8. name: this.props.profile.name,
9. userName: this.props.profile.userName,
10. avatar: this.props.profile.avatar
11. },
12. date: Date.now,
13. message: this.state.message,
14. image: this.state.image,
15. liked: false,
16. likeCounter: 0
17. }
18.
19. this.props.operations.addNewTweet(tweet)
20. this.reset();
21. }

En esta función realizamos tres acciones, la primera es crear el request para el


servicio de creación del Tweet, para lo cual le mandamos los datos:

Página | 222
• _id: asignamos un nuevo ID con ayuda del módulo UUID.
• _creator: esta sección no es necesario para crear el Tweet en el servicio
REST, si no para que se vea correctamente en pantalla.
• Date: Corresponde a la fecha de creación, es decir, en ese mismo
momento.
• message: Mensaje del Tweet, es decir, lo que el usuario escribió.
• Image: Imagen asociada a Tweet (opcional)
• like: En la creación siempre es false, pues indica si le hemos dado like al
Tweet.
• likeCounter: contador de likes que tiene el tweet, en la creación
siempre es 0.

Una vez que hemos creado el request mandamos llamar a la función addNewTweet
la cual es recibida como un prop dentro del objeto this.props.operations. Esta
función será que se encargue realmente de crear el Tweet, por lo este
componente no se deberá preocupar más por la creación. Vamos a analizar la
función addNewTweet más adelante.

Finalmente, se manda llamar la función reset, la cual limpiará el componente y


lo dejará en su estado inicial.

Funciones como props

Recuerda que mediante las props es posible enviar


cualquier tipo de objetos, incluyendo funciones que
son heredadas de los padres, como es el caso de la
función addNewTweet.

En este punto deberíamos estar muy felices, pues hemos terminado de


implementar el componente Reply, sin embargo, todavía nos falta un paso más,
y es utilizar el componente Reply dentro de la aplicación. Para esto, regresaremos
al componente TweetsContainer y modificaremos la función render para dejarla
como se ve a continuación:

1. render(){
2.
3. let operations = {
4. addNewTweet: this.addNewTweet.bind(this)
5. }
6.
7. return (
8. <main className="twitter-panel">
9. <Choose>
10. <When condition={this.props.onlyUserTweet} >
11. <div className="tweet-container-header">
12. TweetsDD
13. </div>
14. </When>
15. <Otherwise>

223 | Página
16. <Reply profile={this.props.profile} operations={operations}/>
17. </Otherwise>
18. </Choose>
19. <If condition={this.state.tweets != null}>
20. <For each="tweet" of={this.state.tweets}>
21. <Tweet key={tweet._id} tweet={tweet}/>
22. </For>
23. </If>
24. </main>
25. )
26. }

Podemos apreciar dos cambios, por una parte, hemos creado una variable
llamada operations (línea 3) que contiene la referencia a la función addNewTweet,
observemos que hemos referenciado la función con bind(this), ya que, de lo
contrario, no funcionará.

El segundo cambio es la sección del <Choose> el cual analizaremos más adelante,


pero lo que nos incumbe ahora es que en el caso del <Otherwise> se creará un
componente Reply (línea 16). El componente recibe dos props, el perfil del
usuario autenticado y la variable operations, el cual tiene la función addNewTweet.

Finalmente deberemos agregar la famosa función addNewTweet, la cual realiza la


creación del Tweet en el API REST.

1. addNewTweet(newTweet){
2. let oldState = this.state;
3. let newState = update(this.state, {
4. tweets: {$splice: [[0, 0, newTweet]]}
5. })
6.
7. this.setState(newState)
8.
9. //Optimistic Update
10. APIInvoker.invokePOST('/secure/tweet',newTweet, response => {
11. this.setState(update(this.state,{
12. tweets:{
13. 0 : {
14. _id: {$set: response.tweet._id}
15. }
16. }
17. }))
18. },error => {
19. console.log("Error al cargar los Tweets");
20. this.setState(oldState)
21. })
22. }

Analicemos que hace la función; primero que nada, recibe como parámetro el
Tweet a crear en la variable newTweet, lo segundo en hacer es respaldar el estado
actual en la variable oldState (línea 2), luego agregamos el nuevo Tweet a un
nuevo estado que hemos llamado newState, seguido actualizamos el estado del
componente con la variable newState, es decir con el nuevo Tweet que vamos a

Página | 224
crear. Para terminar, llamamos al servicio /secure/tweet del API REST para crear
el Tweet.

Documentación: Creación de un nuevo Tweet

Este servicio nos permite crear un nuevo Tweet


asociado al usuario autenticado (/secure/tweets).

Si el servicio logra guardar correctamente el Tweet, entonces solo actualiza el ID


con el ID real que nos asignó el API REST (línea 14) y no el UUID que habíamos
generado. En caso de error, actualizo el estado del componte con el oldState
(estado viejo).

Te voy a pedir que te tomes un tiempo y veas detenidamente esta función y


pienses si no vez algo extraño. Analiza el orden de los pasos y pregúntate si algo
que no encaje. Si te diste cuenta, felicidades, pero si no, no te preocupes aquí lo
explicamos.

Seguramente te estarás preguntando, porque actualizo el estado con el nuevo


Tweet (línea 7) antes de haberlo creado con el API (línea 10). Y Luego tengo que
regresar el estado del componente al anterior en caso de falla. Entonces, ¿no
sería más simple, crear el Tweet y luego actualizar el estado? De esta forma de
ahorro tener que respaldar el estado actual y luego restaurarlo en caso de error.

La verdad es que se podría haber hecho así, sin embargo, esta era una excelente
oportunidad para explicar una de las características más potentes React, que es
el Optimistic update.

Nuevo concepto: Optimistic update

Es una técnica para actualizar la vista antes del


BackEnd, dando al usuario una experiencia de usuario
superior, pues permite darle una respuesta inmediata.

Como acabamos de ver, el Optimistic Update nos permite actualizar la vista con
el nuevo Tweet sin tener la confirmación del servidor, lo que dará al usuario una
sensación de velocidad extraordinaria. Aunque, por desgracia, sabemos que
cualquier cosa puede fallar en cualquier momento, por lo que puede pasar que el
API nos regrese error o sea inaccesible en ese momento, es entonces cuando es
necesario realizar un Rollback.

En nuestro caso, solo hemos revertido el estado del componente, pero en la


práctica lo mejor sería que adicional al rollback del componente, lancemos alguna
notificación visual al usuario para que esté al tanto de la situación. Esto te lo
puedes llevar de tarea, para seguir mejorando tus habilidades en React.

225 | Página
Para concluir, no olvidemos realizar el import de la función update:

1. import update from 'immutability-helper'

En este punto ya está todo funcionando, pero falta agregar los estilos para que
todo se vea estéticamente bien, por lo que agregamos las siguientes clases de
estilo en el archivo styles.css:

1. /** Reply component **/


2.
3. .reply{
4. padding: 10px;
5. border-top: 1px solid #e6ecf0;
6. background-color: #E8F4FB;
7. }
8.
9. .reply .reply-avatar{
10. display: inline-block;
11. position: absolute;
12. border: 1px solid #333;
13. border-radius: 5px;
14. height: 35px;
15. width: 35px;
16. left: 40px;
17. text-align: center;
18. }
19.
20. .reply .reply-body{
21. margin-left: 80px;
22.
23. }
24.
25. .reply .reply-body textarea{
26. height: 35px;
27. display: block;
28. width: 100%;
29. border: 1px solid #DDE2E6;
30. outline: none;
31. padding-left: 10px;
32. padding-right: 10px;
33. resize: none;
34. border-radius: 5px;
35.
36. }
37.
38. .reply .reply-body .reply-selected{
39. height: 70px;
40. }
41.
42. .reply .reply-body .image-box{
43. display: block;
44. position: relative;
45. color: #F1F1F1;
46. padding: 10px;
47. border-left: 1px solid #DDE2E6;
48. border-right: 1px solid #DDE2E6;
49. border-bottom: 1px solid #DDE2E6;
50. border-radius: 0 0 5px 5px;
51. max-height: 250px;
52. width: 100%;
53. }
54.

Página | 226
55. .tweet-event{
56. position: absolute;
57. display: block;
58. left: 0;
59. right: 0;
60. top: 0;
61. bottom: 0;
62. z-index: 0;
63. }
64.
65. .reply .reply-body .image-box img{
66. display: inline-block;
67. position: relative;
68. max-height: 230px;
69. border-radius: 5px;
70. max-width: 100%;
71. }
72.
73.
74. .reply .reply-controls{
75. padding: 10px 0px 0px 0px;
76. text-align: right;
77. margin-left: 55px;
78. }
79.
80. .reply .reply-controls button{
81. font-size: 18px;
82. font-weight: bold;
83. }
84.
85. .reply .reply-controls button i{
86. color: inherit;
87. font-size: inherit;
88. }
89.
90. .reply .reply-controls .char-counter{
91. margin-right: 10px;
92. color: #333;
93. }
94.
95. input[type="file"]{
96. display: none;
97. }

No suelo detenerme en explicar las clases de estilo, pues no es el foco de este


libro, pero si quisiera que vieras la línea 95 a 97, en donde hacemos que el input
de tipo file no se vea nunca. Es por eso que solo alcanzamos a ver el label
asociado al input.

Si hemos seguido todos los pasos hasta ahora, podrás guardar los cambios y
actualizar el navegador para ver como se ve nuestra aplicación hasta el
momento:

227 | Página
Fig. 70 - Reply componente terminado.

En la imagen ya podemos apreciar cómo hemos podidos capturar un nuevo Tweet


con todo y su imagen, y luego de presionar el botón “Tweet” podemos ver como
este ya se ve en nuestro feed y el componte Reply ha regresado a su estado
inicial.

Fig. 71 - Nuevo Tweet creado.

Este componente ha sido por mucho el más complejo y largo de explicar, pues
tiene varios comportamientos que teníamos que explicar, de lo contrario, podrían
quedar dudas acerca de su funcionamiento.

Solo para recapitular lo que llevamos hasta el momento, te dejo esta imagen,
donde se ve la estructura actual de nuestro proyecto, para que la compares y
veas si todo está bien.

Página | 228
Fig. 72 - Estructura actual del proyecto.

Recuerda que, si has tenido algún problema en hacer funcionar algún


componente hasta este momento, puedes descargar el código fuente del
repositorio y colocarte en el branch de este capítulo, de esta forma tendrá el
código hasta este momento totalmente funcional.

229 | Página
Resumen

Sin duda alguna, este ha sido unos de los capítulos más interesantes hasta el
momento, pues hemos aprendido el ciclo de vida de los componentes, lo cual es
clave para poder desarrollar aplicaciones correctamente.

Por otra parte, hemos hablado del concepto de Optimistic Update, una de las
ventajas que ofrece React para crear aplicaciones con una experiencia de
usuarios sin precedentes, pues crea una sensación de respuesta inmediata por
parte del servidor, incluso si este no responde a la misma velocidad.

También hemos visto con el Componente Reply, que las propiedades (props)
también pueden contener funciones, las cuales son transmitidas por los padres
hacia los hijos, con la intención de delegar la responsabilidad a otro componte.
Sin olvidar que hemos reforzado nuestros conocimientos acerca de la forma de
utilizar los eventos, como lo son el onClick, onBlur, onKeyDown, onChange, onFocus,
etc.

Página | 230
React Routing
Capítulo 9

Hasta este momento hemos aprendido a desarrollar componentes y como estos


se comportan a medida que interactuamos con ellos, pero poco o nada hemos
hablado acerca de cómo un usuario navega por la aplicación, y como es que a
medida que navegamos son otros los componentes que se muestran en pantalla.

Cuando la WEB inicio, las URL no representaban nada más que una simple
dirección a un documento HTML, por lo que la estructura de la misma era casi
irrelevante. Sin embargo, con todas las mejoras que ha tenido la WEB, las URL
ha evolucionado a tal punto que hoy en día, son capases de darnos mucha
información acerca de donde estamos parados dentro de la aplicación. Veamos
el siguiente ejemplo:

http://twitter.com/oscarjblancarte/followers

Solo con ver esta URL puedo determinar que estoy en los seguidores (followers)
del usuario oscarjblancarte.

Podemos ver la ventaja evidente de crear URL amigables, no solo por estética y
que el usuario puede recordar mejor la URL, sino que también los buscadores
como Google toman en cuenta la URL para posicionar mejor nuestras páginas.

Veamos otro ejemplo rápido, imagina que tengo una página que vende cursos
online, por lo que cada curso debe de tener su URL, yo podría tener las siguientes
dos URL:

• http://mysite.com/react
• http://mysite.com/cursos/react

Cuál de las dos siguientes URL crees que es más descriptiva para vender un
curso, la primera URL me deja claro que se trata de algo de React, pero no sé si
sea un artículo, un video, o cualquier otro caso, en cambio, la segunda URL me
deja muy en claro que se trata del curso de React.

231 | Página
Pues bien, para no entrar mucho en detalles, React Router es el módulo de React
que nos ayuda a diseñar la navegación del usuario mediante las URL, utilizando
un concepto llamado Single Page Application, el cual analizaremos a
continuación.

Single page Application

El termino de Single Page Application o SPA de ahora en adelante, hace referencia


a aplicaciones que se ejecutan en una sola página, y no requieren ir al servidor
para consultar las siguientes páginas, en su lugar, las páginas son creadas al
vuelo mediante JavaScript desde el navegador.

Para comprender que es una SPA es importante analizar como las tecnologías
tradicionales muestran las páginas, en donde el navegador requiere hacer una
petición GET al servidor para recuperar la siguiente página y mostrarla. Durante
este proceso, el servidor determina cual es la página que debe de mostrar al
usuario, consulta la base de datos y luego crea la vista, como resultado, el
servidor responde con la página web (HTML) y los datos incrustados, de tal forma
que el navegador solo se limita a mostrar la página web retornada por el servidor.
Este proceso de consultar la página al servidor se conoce como Server Side
Render (SSE) o renderizado del lado del servidor.

Por otra parte, React crea páginas SPA, es decir, que cuando el usuario accede a
la aplicación, el servidor siempre regresará la misma página para todas las URL,
el cual consiste en un HTML con la referencia al archivo bundle.js. Cuando el
archivo bundle.js se carga, se ejecuta y crea la vista dinámicamente desde el
navegador, y cuando el usuario navega a otra página, este ya no va al servidor,
si no que el archivo bundle.js tiene la lógica para generar la siguiente página.

Debido a lo anterior, es necesario utilizar librerías como React Router para


actualizar la página según la URL en que estemos, pero con la diferencia de que
esto se hace directamente desde el navegador.

Ya habíamos hablado de este concepto en la sección Server Side Apps vs Single


Page Apps. Por si gustas regresar a leerlo, ya que este concepto es fundamental
para entender lo vamos a explicar.

Página | 232
Router & Route

Debido a que React no requiere ir al servidor para obtener una página según la
URL ejecutada, debemos definir las reglas de navegación que tendrá nuestra
página.

Las reglas de navegación se definen básicamente con los componentes Router y


Route, donde Router es utilizado únicamente para iniciar las reglas y definir el
tipo de historial que utilizaremos para navegar (lo analizaremos más adelante) y
Route nos sirve para definir qué componente se tendrá que ver en cada path.
Veamos un ejemplo:

1. import { Router, Route, Switch } from "react-router-dom"


2. import history from './History'
3.
4. render((
5. <Router history={history}>
6. <TwitterApp>
7. <Switch>
8. <Route exact path="/" component={Home} />
9. <Route exact path="/signup" component={Signup} />
10. <Route exact path="/login" component={Login} />
11. <Route exact path="/:user" component={UserPage} />
12. <Route exact path="/:user/:tab" component={UserPage} />
13. </Switch>
14. </TwitterApp>
15. </Router>
16. ), document.getElementById('root'));

Lo primero que debemos de hacer para implementar React Router es englobar


toda nuestra aplicación dentro del componente Router el cual podemos ver en la
línea 5 del ejemplo anterior. Este componente es importante porque controla el
historial de navegación, pero es un API de muy alto nivel como para preocuparnos
por él. Para que este componte funcione, hay que pasarle una instancia del
historial mediante la propiedad history.

Debido a que vamos a necesitar la referencia al historial más adelante, hemos


decidido crearlo por separado en el archivo History.js que creamos en la unidad
pasada, así que solo lo importamos y luego se lo pasamos al componente Router
por medio de la propiedad history.

El siguiente paso para definir las rutas, y aquí es donde nos podemos dar cuenta
de que vamos a necesitar una serie de instancias del componente Route, los
cuales están anidados de forma jerárquica, es decir, todos dentro de Router.
Cada Route tiene un atributo path, el cual indica en que URL se deberá mostrar
el componente definido en la propiedad componente. De esta forma, React Router
solo mostrará los componentes que hagan match el path actual del navegador, y
desmontará todos aquellos que no.

Si bien las rutas pueden ser estáticas (una URL fija que no cambia), pueden
existir situaciones donde necesitamos una URL que soporte una serie de valores.
Para comprender mejor esto, analicemos la URL /login (línea 9) y /signup (línea
10), está dos URL se pueden considerar estáticas, pues nunca cambiaran y ante

233 | Página
esa misma URL dará como resultado la misma página, sin embargo, existen
ocasiones donde necesitamos paths que soporten varios valores, como es el caso
de la página del perfil de los usuarios, en este sentido, podríamos tener URL como
las siguientes: /oscar, /juan o /rene, todas estas URL son diferentes, pero
deberían de mostrar la misma página aunque con datos diferentes y cómo
podemos tener cientos, miles o millones de usuarios, no sería viable crear una
ruta por cada usuario, por lo que en su lugar, se crear path con variables.

Los paths con variables permite indicarle a React Router que ciertas partes de la
URL en realidad son parámetros que deberían de ser propagados al componente
en cuestión, de tal forma que tenemos paths como /:user. Presta atención en
los dos puntos, pues con esto definimos que puede llegar un slash (/) seguido de
cualquier número de caracteres, y todos estos serán propagados al componente
como una propiedad con el mismo nombre.

Los path pueden ser expresiones muy complejas que no vamos a analizar en este
libro, ya que explicarlas podría hacer que nos salgamos demasiado del tema,
además, con lo que vamos a explicar en este libro será suficiente para más del
90% de los casos que se te podrían presentar, sin embargo, si quieres
profundizar en el tema, puedes ir a la documentación de la Path-ToRegExp, la
cual es el motor utilizado internamente por React Router para evaluar las rutas.

El otro atributo importante del componente Route es exact, ya que este permite
indicar que el path es exacto, lo que quiere decir que para que el path se active,
la URL deberá ser exacta y no bastará con que comiencen con el path, veamos
la siguiente tabla para comprender mejor:

path location.pathname exact matches?


/one /one/two true No
/one /one/two false Si

En la tabla anterior puedes observar que un path se puede activas cuando la URL
del navegador (location.pathname) comienza con el path del Route (path). Es por
ello que marcamos las rutas como exactas, así solo se activarán solo cual la
coincidencia sea exacta. En pocas palabras, si no agregamos exact, le dice a
React Router que la ruta se activará si la URL comienza con el valor del path, por
otro lado, con exact, le decimos que el componente se activará solo cuando la
coincidencia entre el path y la URL sean exactas.

Switch

Un problema que se presenta con frecuencia es que dos o más rutas pueden
hacer match ante la misma URL, lo cual es válido, incluso, nos puede funcionar
si queremos que dos compontes diferentes se muestran ante la misma URL, sin
embargo, existen ocasiones donde esto puede ser un problema, ya que solo
necesitamos que se muestra la primera coincidencia y descargar el resto.

Página | 234
Un caso práctico de esto que te estoy hablando es el path /login y /signup contra
el path /:user, si recordamos, :user es una variable que puede tener cualquier
número de caracteres, por lo tanto, el path /login y /signup también activarían
este path, lo que diera como resultado que el componente del perfil del usuario
y el login se activaran al mismo tiempo, mostrando el formulario de login y justo
por debajo, el perfil del usuario.

Para evitar este comportamiento, React Router proporciona el componte Switch,


que permite mostrar solo el primer componente que hace match con la URL,
evitando que el resto de componentes se muestren.

1. render((
2. <Provider store={store}>
3. <Router history={history}>
4. <TwitterApp>
5. <Switch>
6. <Route exact path="/" component={Home} />
7. <Route exact path="/signup" component={Signup} />
8. <Route exact path="/login" component={Login} />
9. <Route exact path="/:user" component={UserPage} />
10. <Route exact path="/:user/:tab" component={UserPage} />
11. </Switch>
12. </TwitterApp>
13. </Router>
14. </Provider>
15. ), document.getElementById('root'));

Error común

Cuando utilizamos Switch es muy importante el orden


en el que ponemos los Route, pues recordemos que
solo se mostrará el primero que haga match.

Link

Cuando trabajamos con React Router, hay que tener especial cuidado en la forma
en que creamos los enlaces (<a>), pues esta puede tener un resultado adverso
al esperado. Las etiquetas <a> fuerzan al navegador a realizar una consulta al
servidor y actualizar toda la página, provocando que todos nuestros componentes
se creen de nuevo, pasando por el ciclo de vida de creación, como lo es el
constructor, render, componentWillMount, etc. y puede provocar un degrado en
el performance y la experiencia de uso del usuario.

Para evitar esto, React Router ofrece un componente llamado <Link>, el cual
actúa exactamente como la etiqueta <a>, pero esta tiene la ventaja de que no
lanza un request al servidor, si no que solo actualiza la URL del navegador pero
sin hacer una llamada al servidor, lo que provoca que las nuevas rutas sean
activadas.

235 | Página
Link se renderiza como una etiqueta <a> al memento de mostrarse en el
navegador, sin embargo remplaza su funcionamiento estándar por el mencionado
anteriormente.

Veamos un ejemplo de cómo se utiliza este componente:

1. <Link to={"/login"}>
2. //Any element
3. </Link>

El único atributo obligatorio de Link es to, la cual hace referencia a la URL a la


que debemos de llevar al usuario. To es equivalente a la propiedad href de la
etiqueta <a>.

NOTA: Debemos siempre procurar utilizar Link para la navegación del usuario,
sin embargo, este solo sirve para URL relativas a nuestro dominio, por lo que si
queremos llevar al usuario fuera de nuestra página, entonces podemos utilizar la
clásica etiqueta <a>.

NavLink

Este componente es exactamente igual al anterior, con la única diferencia de que


nos permite aplicar estilos diferentes a los links que hacen match con la URL
actual, de esta forma podemos resaltar o personalizar el link que hace referencia
a la página en la cual estamos actualmente.

En la siguiente imagen podemos apreciar como utilizamos este componente para


indicar si estamos viendo los Tweets de un usuario, los seguidores o las personas
que nos siguen.

Página | 236
Redirect

El componente Redirect es tan simple como bueno, pues nos permite


redireccionar a un usuario desde una página a otra sin refrescar la página, muy
parecido a como si diéramos click a un Link, la única diferencia es que Redirect
no necesita que lo presionen el usuario para redireccionarlo, si no que con el
simple hecho de que el componente sea renderizado se realizará el
redireccionamiento. Veamos un ejemplo:

1. import React from 'react'


2.
3. class Component extends React.Component {
4.
5. render(){
6. if(this.props.user == null){
7. return <Redirect to="/login" />
8. }
9.
10. render(
11. <h1>hola mundo</h1>
12. )
13. }
14. }
15. export default Component

Observa cómo hemos utilizado la propiedad user para determinar si debemos


mostrarle la página al usuario o redireccionarlo a la página de login. El
componente Redirect se activa solo con el hecho de ser renderizado, por eso,
cuando hacemos el return, le estamos diciendo al componente que renderiza el
componente Redirect, haciendo que el usuario sea redireccionado
automáticamente.

Redirect solo acepta la propiedad to, la cual deberá indicar la URL a la cual
queremos redireccionar el usuario, pero tomemos en cuenta que la URL que
pongamos deberá ser dentro de nuestro mismo sitio web.

URL params

Debido a que las aplicaciones cada vez requieren de la generación de URL más
amigables, hemos llegado al punto en que las URL pueden representar
parámetros para las aplicaciones, y de esta forma, saber qué información debe
de mostrar. Por ejemplo, la siguiente URL: http://localhost:8080/oscar o
http://localhost:8080/maria, estas dos URL deberían de llevarnos al perfil de
oscar y maría, y la página debería ser la misma, con la diferencia de la
información que muestra. Esto se hace debido a que oscar y maria, son
parámetros que React Router puede identificar y pasar como prop al componte.

237 | Página
1. <Route path=":user" component={UserPage} >

Dónde (:user) es el parámetro y React es capaz de identificarlo, también


podemos tener Route como los siguientes:

1. <Route path=":user/tweet/:id" component={UserPage} >

Esta URL la podríamos usar para ver un Tweet especifico de un usuario por medio
del ID del Tweet, por ejemplo, http://localhost:8080/oscar/tweet/110, donde
oscar es el parámetro (:user) y 110 es el parámetro (:id). Los parámetros
pueden ser recuperados mediante this.props.route.{prop-name}.

Hasta este punto hemos analizado lo más importante de React Router, pero sin
duda hay más cosas por explorar, por lo que, si quieres profundizar más en el
tema, te deje la documentación oficial para que lo análisis con más calma.

Página | 238
Mini Twitter (Continuación 3)

Después de una larga teoría acerca del funcionamiento de React Router, ha


llegado el momento de implementarlo en nuestro proyecto. Pero antes de
continuar, te recuerdo que es necesario instalar el módulo react-router-dom e
history mediante npm.

• npm install --save history@4.10.1


• npm install --save react-router-dom@5.1.2

En el capítulo pasado ya habíamos instalado estas dependencias, así que si ya


las tienes en el archivo package.json, ya no es necesario volverlas a instalar.

Error común

React Router anteriormente se llamaba react-router,


pero a partir de la versión 3 cambia de nombre a
react-router-dom, por lo que no hay que confundirlos.

Implementando Routing en nuestro proyecto

Recordarás que en el capítulo anterior modificamos el archivo TwitterApp.js para


agregar react-router-dom, pero no entramos en detalles, ya que no era el
momento de abordar el tema, pero ahora que ya hemos explicado cómo funciona,
no debería de ser un problema entenderlo. Ahora agregaremos algunos cambios:

1. import React from 'react'


2. import APIInvoker from "./utils/APIInvoker"
3. import browserHistory from './History'
4. import { Route, Switch } from "react-router-dom";
5. import Signup from './Signup'
6. import Login from './Login'
7. import TwitterDashboard from './TwitterDashboard'
8. import AuthRouter from './AuthRouter'
9.
10. class TwitterApp extends React.Component {
11.
12. …
13.
14. render() {
15. if (!this.state.load) {
16. return null
17. }
18.
19. return (
20. <div id="mainApp" className="aminate fadeIn">
21. <Switch>

239 | Página
22. <AuthRouter isLoged={this.state.profile != null} exact path="/"
23. component={() =>
24. <TwitterDashboard profile={this.state.profile} />} />
25. <Route exact path="/signup" component={Signup} />
26. <AuthRouter isLoged={this.state.profile != null}
27. exact path="/login" component={Login} />
28. </Switch>
29. <div id="dialog" />
30. </div>
31. )
32. }
33. }
34. export default TwitterApp;

Podemos apreciar claramente que, hasta el momento, tenemos tres reglas de


ruteo, la primera, es para el root (/) de la aplicación, que está ligado al
componente TwitterDashboard, por otro lado, tenemos los paths /signup y /login
ligados a los componentes Signup y Login respectivamente. Es posible comprobar
que estas reglas de ruteo funcionan entrando a http://localhost:8080/login y
http://localhost:8080/signup.

Creo que con la explicación anterior, nos debería de quedar más claro lo que está
pasando entre las líneas 21 y 28, con la única duda quizás de lo que está pasando
en la línea 22, por lo que vamos explicar esto un poco mejor. El atributo
component de Route acepta un componente o una función que retorne un
componente, en este sentido, definimos una arrow function que retorna el
componente TwitterDashboard, esto lo hacemos así porque necesitamos pasarle
el profile como prop.

Veamos que hemos agregado un nuevo componente llamado AuthRouter no es


parte del módulo react-router-dom, sin embargo eso lo analizaremos por
separado.

El componente AuthRouter

Debido a que habrá algunas rutas que solo se podrá tener acceso cuando estemos
autenticados, será necesario crear un componente que nos ayude a redireccionar
a la página de login a todos los usuarios que intenten entra a estas páginas y
que no estén autenticados, es por ello que crearemos el componente
AuthRouter.js en el path /app.

1. import React from 'react'


2. import { Route, Redirect } from 'react-router-dom'
3.
4. class AuthRoute extends React.Component {
5.
6. render() {
7. const { component: Component, isLoged, ...rest } = this.props
8. return (<Route {...rest} render={(props) => (
9. isLoged
10. ? <Component {...props} />

Página | 240
11. : <Redirect to='/login' />
12. )} />)
13. }
14. }
15.
16. export default AuthRoute

Este componente puede llegar a resultar algo confuso, sobre todo por que
utilizamos algunas cosas que no vemos normalmente, pero quiero hacerte un
resumen antes de entrar a los detalles.

La clave de este componente es la propiedad isLoged (línea 9) que debe de


recibir, la cual le indica si el usuario está o no autenticado, basado en esta
propiedad determina que componente va a renderizar, por ejemplo, si isLoged
es true, se renderizará el componente que pasemos como prop en el atributo
component, y si es false, se renderizará el componente Redirect de React Router,
de esta forma, el usuario será redireccionado al login si no está autenticado.

En otras palabras, este componente extiende la funcionalidad de React Router


para crear una Route que restringe el acceso a usuarios no autenticados,
redireccionándolos a login.

El componente Toolbar

El componente Toolbar es la barra que podemos apreciar en la parte superior de


la aplicación, desde ella podemos ver una foto de nuestro perfil, ir a nuestro perfil
o cerrar sesión.

Iniciaremos con la creación del archivo Toolbar.js en el path /app, el cual deberá
tener la siguiente estructura:

1. import React from 'react'


2. import { Link } from 'react-router-dom'
3. import PropTypes from 'prop-types'
4.
5. class Toolbar extends React.Component {
6. constructor(props) {
7. super(props)
8. this.state = {}
9. }
10.
11. logout(e) {
12. e.preventDefault()
13. window.localStorage.removeItem("token")
14. window.localStorage.removeItem("username")
15. window.location = '/login';
16. }
17.
18. render() {
19.
20. return (
21. <nav className="navbar navbar-default navbar-fixed-top">
22. <span className="visible-xs bs-test">XS</span>

241 | Página
23. <span className="visible-sm bs-test">SM</span>
24. <span className="visible-md bs-test">MD</span>
25. <span className="visible-lg bs-test">LG</span>
26.
27. <div className="container-fluid">
28. <div className="container-fluid">
29. <div className="navbar-header">
30. <Link className="navbar-brand" to="/">
31. <i className="fa fa-twitter" aria-hidden="true"/>
32. </Link>
33. <ul id="menu">
34. <li id="tbHome" className="selected">
35. <Link to="/">
36. <p className="menu-item">
37. <i className="fa fa-home menu-item-icon"
38. aria-hidden="true" />
39. <span className="hidden-xs hidden-sm">
40. Inicio</span>
41. </p>
42. </Link>
43. </li>
44. </ul>
45. </div>
46. <If condition={this.props.profile != null} >
47. <ul className="nav navbar-nav navbar-right">
48. <li className="dropdown">
49. <a href="#" className="dropdown-toggle"
50. data-toggle="dropdown" role="button"
51. aria-haspopup="true"
52. aria-expanded="false">
53. <img className="navbar-avatar"
54. src={this.props.profile.avatar}
55. alt={this.props.profile.userName}/>
56. </a>
57. <ul className="dropdown-menu">
58. <li>
59. <Link to={`/${this.props.profile.userName}`}>
60. Ver perfil</Link>
61. </li>
62. <li role="separator" className="divider"></li>
63. <li>
64. <Link to="#" onClick={this.logout.bind(this)}>
65. Cerrar sesión</Link>
66. </li>
67. </ul>
68. </li>
69. </ul>
70. </If>
71. </div>
72. </div>
73. </nav>
74. )
75. }
76. }
77.
78. Toolbar.propTypes = {
79. profile: PropTypes.object
80. }
81.
82. export default Toolbar

Página | 242
Este componte es sin duda uno de los más simples, pues en realidad solo muestra
un botón de inicio (línea 35) la cual regresará al usuario al inicio de la aplicación
( / ).

Por otra parte, mostramos una foto del avatar del usuario (línea 50), que al
presionarse, arrojará un menú con dos opciones, la primera nos lleva al nuestro
perfil de usuario (/:user) (línea 59), y la segunda opción es cerrar la sesión (línea
64). La opción de cerrar sesión detonará en la ejecución de la función logout.

La función logout remueve del local storage el token y el username (líneas 13 y


14) y redirecciona al usuario a la pantalla de login (línea 15).

Para concluir, será necesario regresar al archivo TwitterApp,js y modificar la


función render para agregar el componente Toolbar. Veamos cómo quedaría:

1. return (
2. <>
3. <Toolbar profile={this.state.profile} />
4. <div id="mainApp" className="aminate fadeIn">
5. <Switch>
6. <Route exact path="/" component={() =>
7. <TwitterDashboard profile={this.state.profile} />} />
8. <Route exact path="/signup" component={Signup} />
9. <Route exact path="/login" component={Login} />
10. </Switch>
11. <div id="dialog" />
12. </div>
13. </>
14. )

No olvidemos agregar el importo correspondiente al archivo TwitterApp.js:

1. import Toolbar from './Toolbar'

Finalmente, quedaría solo agregar las clases de estilo correspondientes a este


componente, para ello agregaremos las siguientes clases de estilo al final del
archivo styles.css:

1. /** Toolbar componente **/


2.
3. .bs-test{
4. display: inline-block;
5. position: absolute;
6. }
7.
8. #dashboard{
9. margin-top: 60px;
10. }
11.
12. .navbar-brand{
13. font-size: 24px;
14. position: absolute;

243 | Página
15. left: 50%;
16. transform: translateX(50%);
17. }
18.
19. .navbar-brand i{
20. color: #1DA1F2;
21. }
22.
23.
24. .navbar{
25. background-color: #FFFFFF;
26. height: 48px;
27. }
28.
29. .navbar-nav>li>a{
30. padding: 0px;
31. }
32.
33. .navbar-avatar{
34. height: 32px;
35. width: 32px;
36. border-radius: 50%;
37. margin: 9px 10px;
38. }
39.
40. #menu{
41. padding: 0px;
42. margin: 0px;
43. position: relative;
44. }
45.
46. #menu li{
47. display: inline-block;
48. color: #666;
49. position: relative;
50. }
51.
52. #menu li::before{
53. content: "";
54. position: absolute;
55. display: block;
56. left: 0px;
57. right: 0px;
58. height: 0px;
59. background-color: #1B95E0;
60. bottom: -2px;
61. z-index: 1000;
62. transition: 0.5s;
63. }
64.
65.
66.
67. #menu li:hover::before{
68. height: 5px;
69. }
70.
71. #menu li:hover{
72. color: #1DA1F2;
73. }
74.
75.
76. #menu li.selected::before{
77. height: 5px!important;
78. }
79.
80. #menu li.selected{

Página | 244
81. color: #1DA1F2;
82. }
83.
84.
85. #menu li a{
86. display: inline-block;
87. padding: 13px 18px 0px 0px;
88. font-size: 12px;
89. font-weight: bold;
90. color: inherit;
91. }
92.
93. #menu li a:focus{
94. text-decoration: none;
95. }
96.
97.
98. #menu li a span{
99. color: inherit;
100. }
101.
102.
103. #menu li .menu-item{
104. color: inherit;
105. padding: 0px 10px;
106. }
107.
108. #menu li .menu-item-icon{
109. font-size: 18px;
110. font-size: 24px;
111. vertical-align: sub;
112. padding-right: 5px;
113. color: inherit;
114. }
115.
116. @media (max-width: 576px) {
117. #menu li .menu-item{
118. padding: 0px 5px;
119. }
120.
121. #menu li a{
122. padding: 13px 0px 0px 0px;
123. margin-right: 5px;
124. }
125. }

Guardamos todos los cambios y actualizamos el navegador para observar los


cambios:

245 | Página
Fig. 73 - Componente Toolbar terminado.

Toques finales al componente Login

Ahora que ya hemos aprendido a utilizar React Router, podemos agregar los
toques finales, con los cuales podremos redirigir al usuario a la pantalla de Signup
para crear una cuenta en caso de que no tenga. Para ello abriremos el archivo
Login.js y modificaremos la función render para dejarla de la siguiente manera:

1. render(){
2. return(
3. <div id="signup">
4. <div className="container" >
5. <div className="row">
6. <div className="col-xs-12">
7. </div>
8. </div>
9. </div>
10. <div className="signup-form">
11. <form onSubmit={this.login.bind(this)}>
12. <h1>Iniciar sesión en Twitter</h1>
13.
14. <input type="text" value={this.state.username}
15. placeholder="usuario" name="username" id="username"
16. onChange={this.handleInput.bind(this)}/>
17. <label ref="usernameLabel" id="usernameLabel"
18. htmlFor="username"></label>
19.
20. <input type="password" id="passwordLabel"
21. value={this.state.password} placeholder="Contraseña"
22. name="password" onChange={this.handleInput.bind(this)}/>
23. <label ref="passwordLabel" htmlFor="passwordLabel"></label>
24.
25. <button className="btn btn-primary btn-lg " id="submitBtn"
26. onClick={this.login.bind(this)}>Regístrate</button>
27. <label ref="submitBtnLabel" id="submitBtnLabel" htmlFor="submitBtn"
28. className="shake animated hidden "></label>
29. <p className="bg-danger user-test">Crea un usuario o usa el usuario
30. <strong>test/test</strong></p>
31. <p>¿No tienes una cuenta? <Link to="/signup">Registrate</Link> </p>
32. </form>
33. </div>
34. </div>

Página | 246
35. )
36. }

Agregamos solo la línea 31 al archivo que ya tenemos, con esto, habilitaremos


un link para llevar al usuario a la página de registro.

Fig. 74 - Nuevo link para crear una cuenta.

No olvidemos importar al componente Link:

1. import { Link } from 'react-router'

Toques finales al componente Signup

En el caso del componente Signup pasa algo similar al componente Login, pues
en este tendremos que agregar un Link para redireccionar al usuario a la pantalla
de login en caso de que ya tenga una cuenta, para esto, abriremos el archivo
Signup.js y modificaremos la función render para dejarla de la siguiente manera:

1. render(){
2. return (
3. <div id="signup">
4. <div className="container" >
5. <div className="row">
6. <div className="col-xs-12">
7. </div>
8. </div>
9. </div>
10. <div className="signup-form">

247 | Página
11. <form onSubmit={this.signup.bind(this)}>
12. <h1>Únete hoy a Twitter</h1>
13. <input type="text" value={this.state.username}
14. placeholder="@usuario" name="username" id="username"
15. onBlur={this.validateUser.bind(this)}
16. onChange={this.handleInput.bind(this)}/>
17. <label ref="usernameLabel" id="usernameLabel"
18. htmlFor="username"></label>
19.
20. <input type="text" value={this.state.name} placeholder="Nombre"
21. name="name" id="name" onChange={this.handleInput.bind(this)}/>
22. <label ref="nameLabel" id="nameLabel" htmlFor="name"></label>
23.
24. <input type="password" id="passwordLabel"
25. value={this.state.password} placeholder="Contraseña"
26. name="password" onChange={this.handleInput.bind(this)}/>
27. <label ref="passwordLabel" htmlFor="passwordLabel"></label>
28.
29. <input id="license" type="checkbox" ref="license"
30. value={this.state.license} name="license"
31. onChange={this.handleInput.bind(this)} />
32. <label htmlFor="license" >Acepto los terminos de licencia</label>
33.
34. <button className="btn btn-primary btn-lg " id="submitBtn"
35. onClick={this.signup.bind(this)}>Regístrate</button>
36. <label ref="submitBtnLabel" id="submitBtnLabel" htmlFor="submitBtn"
37. className="shake animated hidden "></label>
38. <p className="bg-danger user-test">Crea un usuario o usa el usuario
39. <strong>test/test</strong></p>
40. <p>¿Ya tienes cuenta? <Link to="/login">Iniciar sesión</Link> </p>
41. </form>
42. </div>
43. </div>
44. )
45. }

Podemos ver los resultados en la siguiente imagen:

Página | 248
Fig. 75 - Nuevo link para iniciar sesión.

Otros de los cambios está en la función Signup, encargada de crear la cuenta. El


cambio consiste en utilizar el módulo History para redireccionar al usuario de
forma automática a la pantalla de login, solo cuando el registro del usuario se
realizó exitosamente.

1. signup(e){
2. e.preventDefault()
3.
4. if(!this.state.license){
5. this.refs.submitBtnLabel.innerHTML =
6. 'Acepte los términos de licencia'
7. this.refs.submitBtnLabel.className = 'shake animated'
8. return
9. }else if(!this.state.userOk){
10. this.refs.submitBtnLabel.innerHTML =
11. 'Favor de revisar su nombre de usuario'
12. this.refs.submitBtnLabel.className = ''
13. return
14. }
15.
16. this.refs.submitBtnLabel.innerHTML = ''
17. this.refs.submitBtnLabel.className = ''
18.
19. let request = {
20. "name": this.state.name,
21. "username": this.state.username,
22. "password": this.state.password
23. }
24.
25. APIInvoker.invokePOST('/signup',request, response => {
26. browserHistory.push('/login');
27. },error => {
28. console.log("Error al cargar los Tweets");
29. this.refs.submitBtnLabel.innerHTML = response.error
30. this.refs.submitBtnLabel.className = 'shake animated'

249 | Página
31. })
32. }

Una vez que la creación de la cuenta es exitosa, hacemos un redireccionamiento


del usuario a la página de login para que inicie sesión con su nueva cuenta.

No olvidemos importar el componente Link y browserHistory:

1. import { Link } from 'react-router'


2. import { browserHistory } from ./History'

Podemos realizar una prueba ahora mismo, para esto, creemos un nuevo usuario
desde la pantalla de Signup y si el registro sale bien, te debería de llevar
automáticamente a la pantalla de login.

El componente UserPage

El componente UserPage es el que utilizaremos para mostrar el perfil del usuario,


es decir, cuando entremos al path /:user. Este componente es sin duda unos de
los más complejos, pues está compuesto de otros componentes que juntos,
hacen que esta página toma forma. Además de esto, el componente tiene dos
estados, uno es de solo lectura y el otro es de edición.

El modo de solo lectura permitirá a los usuarios ver su perfil o el de otros


usuarios, que está compuesto de: sus tweets, las personas que sigue y que lo
siguen, además de poder visualizar sus datos básicos, como lo son el nombre,
nombre de usuario, avatar, banner y la descripción.

Por otro lado, el modo de edición, solo se podrá habilitar cuando el usuario está
viendo su propio perfil, de esta forma, podrá modificar sus datos básicos, que
son: banner, avatar, nombre y la descripción.

View mode

El modo de solo lectura es el más simple, ya que solo presentamos la información


del usuario y no hay ningún tipo de interacción, más que el botón de Seguir.
veamos la siguiente imagen:

Página | 250
Fig. 76 - Apariencia final del componente UserPage terminado.

La estructura de componentes se puede apreciar mejor en el siguiente diagrama:

Fig. 77 - Estructura de componentes de UserPage.

Los componentes TweetsContainer y SuggestedUser ya los conocemos, pues ya


los hemos utilizado en el pasado, pero el componente MyTweets es un nuevo
componente que más adelante analizaremos.

Ya con esta breve introducción, podemos comenzar a desarrollarlo, crearemos el


archivo UserPage.js en el path /app, el cual deberá tener el siguiente contenido:

1. import React from 'react'


2. import update from 'immutability-helper'
3. import APIInvoker from './utils/APIInvoker'
4. import { NavLink } from 'react-router-dom'
5.

251 | Página
6. class UserPage extends React.Component {
7.
8. constructor(props) {
9. super(props)
10. this.state = {
11. edit: false,
12. profile: {
13. name: "",
14. description: "",
15. avatar: null,
16. banner: null,
17. userName: ""
18. }
19. }
20. }
21.
22. componentDidMount() {
23. let user = this.props.match.params.user
24.
25. APIInvoker.invokeGET('/profile/' + user, response => {
26. this.setState({
27. edit: false,
28. profile: response.body
29. });
30. }, error => {
31. console.log("Error al cargar los Tweets");
32. window.location = '/'
33. })
34. }
35.
36. follow(e) {
37. let request = {
38. followingUser: this.props.match.params.user
39. }
40. APIInvoker.invokePOST('/secure/follow', request, response => {
41. if (response.ok) {
42. this.setState(update(this.state, {
43. profile: {
44. follow: { $set: !response.unfollow }
45. }
46. }))
47. }
48. }, error => {
49. console.log("Error al actualizar el perfil");
50. })
51. }
52.
53. render() {
54. let profile = this.state.profile
55. let storageUserName = window.localStorage.getItem("username")
56.
57. let bannerStyle = {
58. backgroundImage: 'url(' + (profile.banner) + ')'
59. }
60.
61. return (
62. <div id="user-page" className="app-container">
63. <header className="user-header">
64. <div className="user-banner" style={bannerStyle}>
65. </div>
66. <div className="user-summary">
67. <div className="container-fluid">
68. <div className="row">
69. <div className="hidden-xs col-sm-4 col-md-push-1
70. col-md-3 col-lg-push-1 col-lg-3" >
71. </div>

Página | 252
72. <div className="col-xs-12 col-sm-8 col-md-push-1
73. col-md-7 col-lg-push-1 col-lg-7">
74. <ul className="user-summary-menu">
75. <li>
76. <NavLink to={`/${profile.userName}`}
77. activeClassName="selected">
78. <p className="summary-label">TWEETS</p>
79. <p className="summary-value">{profile.tweetCount}</p>
80. </NavLink>
81. </li>
82. <li>
83. <NavLink to={`/${profile.userName}/following`}
84. activeClassName="selected">
85. <p className="summary-label">SIGUIENDO</p>
86. <p className="summary-value">{profile.following}</p>
87. </NavLink>
88. </li>
89. <li>
90. <NavLink to={`/${profile.userName}/followers`}
91. activeClassName="selected">
92. <p className="summary-label">SEGUIDORES</p>
93. <p className="summary-value">{profile.followers}</p>
94. </NavLink>
95. </li>
96. </ul>
97.
98. <If condition={profile.follow != null &&
99. profile.userName !== storageUserName} >
100. <button className="btn edit-button"
101. onClick={this.follow.bind(this)} >
102. {profile.follow
103. ? (<span><i className="fa fa-user-times"
104. aria-hidden="true"></i> Siguiendo</span>)
105. : (<span><i className="fa fa-user-plus"
106. aria-hidden="true"></i> Seguir</span>)
107. }
108. </button>
109. </If>
110. </div>
111. </div>
112. </div>
113. </div>
114. </header>
115. <div className="container-fluid">
116. <div className="row">
117. <div className="hidden-xs col-sm-4 col-md-push-1 col-md-3
118. col-lg-push-1 col-lg-3" >
119. <aside id="user-info">
120. <div className="user-avatar">
121. <div className="avatar-box">
122. <img src={profile.avatar} />
123. </div>
124. </div>
125. <div>
126. <p className="user-info-name">{profile.name}</p>
127. <p className="user-info-username">@{profile.userName}</p>
128. <p className="user-info-description">
129. {profile.description}</p>
130. </div>
131. </aside>
132. </div>
133. <div className="col-xs-12 col-sm-8 col-md-7
134. col-md-push-1 col-lg-7">
135. </div>
136. </div>
137. </div>

253 | Página
138. </div>
139. )
140. }
141. }
142. export default UserPage

Empezaremos con el constructor, pues será lo primero que se ejecutará. En el


podemos observar únicamente la creación del estado inicial, el cual solo tiene
dos partes, la propiedad profile que contiene los datos del usuario a mostrar en
pantalla y la propiedad edit, la cual usaremos para saber si el componente
UserPage está en modo edición o solo lectura. Al inicio, la propiedad edit es false,
indicando que iniciará en modo solo lectura. Por otra parte, los datos del perfil
inician en blanco y será actualizados más adelante.

Los siguiente en ejecutarse es la función componentDidMount, en la cual vamos a


cargar los datos del perfil por medio del API. El servicio utilizado para cargar los
datos del perfil es /profile/{user}, donde {user} es el nombre de usuario del
perfil a buscar.

Este servicio tiene doble propósito, pues si lo consumimos por medio del método
GET nos arrojara los datos del perfil, pero si lo ejecutamos por el método PUT,
estaremos haciendo una update. Por ahora estaremos utilizando el método GET,
pues solo requerimos consultar los datos del usuario (línea 25) para mostrarlos
en pantalla. Si la consulta termina correctamente, actualizamos el estado con el
nuevo perfil (línea 26); mientras que, si el perfil no se encuentra, regresamos al
usuario a la pantalla de inicio (/) (línea 32).

Documentación: Consulta del perfil de usuario

Mediante este servicio es posible recuperar el perfil de


un usuario determinado (/profile/:username)

Los siguiente que analizaremos es la función render. En ella tenemos las


variables profile y storageUserName las cuales vamos a estar utilizando de aquí
en adelante. La variable profile (línea 54) no es más que un atajo a
this.state.profile, con la intención de no hacer el código más verboso de lo
que ya es. La variable storageUserName (línea 55) contiene el nombre de usuario
autenticado en la aplicación, que obtenemos del LocalStorage.

El resto de la pantalla lo vamos a dividir en 3 secciones para explicarlo mejor, el


banner que es solo la imagen superior, los datos del usuario, que contempla:
avatar, nombre de usuario, nombre y descripción. Y por último, la barra de
navegación que se encuentra debajo del banner.

Iniciaremos con el banner, pues es lo primero que aparece al cargar la página.


El banner no es más que un div (línea 64) con la imagen del banner como

Página | 254
background, el fondo es puesto por medio de estilos, que definimos previamente
en variable bannerStyle (línea 57).

Lo segundo por implementar sería la barra de navegación que está justo por
debajo del banner, esta barra es implementada mediante una lista <ul>, donde
cada ítem será una opción. Las opciones disponibles son:

• Tweet: Lleva al usuario a la URL /{user}, es decir, al perfil del usuario.


Por default, en esta URL debemos ver solo los Tweets del usuario,
podemos ver como esta implementado en las líneas (76 a 80).
• Siguiendo: Lleva al usuario a la URL /{user}/following, es decir, nos
lleva al perfil del usuario, pero nos muestra a los usuarios que sigue.
Podemos ver como quedo implementado en las líneas (83 a 87).
• Seguidores: lleva al usuario a la URL /{user}/followers, es decir, nos
lleva al perfil del usuario, pero nos muestra las personas que lo siguen.
Podemos ver como quedo implementado en las líneas (90 a 94).

Fig. 78 - Barra de navegación del Perfil.

Fuera de la lista, pero siendo parte de la barra de navegación, tenemos un botón


que nos permite seguir a un usuario. Para que este botón se muestre, el perfil
mostrado debe de ser diferente al usuario que se encuentra autenticado (línea
98). Si el botón se muestra, puede variar su comportamiento, pues si es un
usuario que no seguimos, se mostrará a leyenda “Seguir”, pero si ya lo seguimos,
dirá “Siguiendo”. La propiedad profile.follow, nos indicará de forma booleana
si seguimos al usuario. Este dato es retornado por el API.

Este botón mandará llamar la función follow (línea 36), la cual es la encargada
de comunicarse con el API para seguir o dejar de seguir a un usuario. El servicio
utilizado para seguir o dejar de seguir a un usuario es /secure/follow, la cual
únicamente necesita que le enviemos el usuario al que queremos seguir, el API
determinará si ya seguimos al usuario o no y aplicar la operación
correspondiente.

255 | Página
Documentación: Seguir o dejar de seguir a un
usuario

Mediante este servicio es posible seguir o dejar de


seguir a un usuario (/secure/follow)

Para concluir con el modo de solo lectura, solo nos quedaría la parte de los datos
básicos del usuario, compuestos por los campos que podemos ver a continuación:

Fig. 79 - Datos básicos del usuario.

La implementación de esta sección la puedes ver en las líneas (119 a 131),


podrás observar que solo se trata de una <img> para el avatar y una serie de
<span> para los datos del usuario. Creo que a estas alturas no tendría mucho
caso entrar en los detalles, pues es algo muy simple.

En este punto ya hemos terminado el componente UserPage, o al menos el modo


de solo lectura, por lo que solo nos restaría agregar la ruta al archivo
TwitterApp.js.

1. import UserPage from './UserPage'


2. import ...
3.
4.
5. class TwitterApp extends React.Component {
6.
7. ...
8.
9. render() {
10. if (!this.state.load) {
11. return null
12. }
13.
14. return (
15. <>
16. <Toolbar profile={this.state.profile} />
17. <div id="mainApp" className="aminate fadeIn">

Página | 256
18. <Switch>
19. <Route exact path="/" component={() =>
20. <TwitterDashboard profile={this.state.profile} />} />
21. <Route exact path="/signup" component={Signup} />
22. <Route exact path="/login" component={Login} />
23. <Route exact path="/:user" component={UserPage} />
24. </Switch>
25. <div id="dialog" />
26. </div>
27. </>
28. )
29. }
30. }
31. export default TwitterApp

Hemos agregado el Route con el path (/:user) para atender las peticiones /{user}
y lo hemos ligado al componente UserPage (línea 23), así como hemos agregado
el import correspondiente de este nuevo componente (línea 1).

En este punto solo restaría agregar las clases de estilo para que los componentes
se vea correctamente, por lo que regresaremos al archivo styles.css y
agregaremos los siguientes estilos al final del archivo:

1. /** UserPage **/


2.
3. .app-container{
4. margin-top: 50px;
5. }
6.
7. #user-page{
8. }
9.
10. #user-page .user-header{
11. margin-bottom: 10px;
12. }
13.
14. #user-page .user-header .user-banner{
15. position: relative;
16. background-color: #fafafa;
17. height: 280px;
18. background-size: cover;
19. background-position: center;
20. }
21.
22. #user-page .user-header .select-banner{
23. position: absolute;
24. left: 0px;
25. right: 0px;
26. bottom: 0px;
27. top: 0px;
28. padding-top: 100px;
29. font-size: 20px;
30. font-weight: bold;
31. }
32.
33. .select-banner:hover{
34. padding-top: 90px;
35. border: 10px solid tomato;
36. }

257 | Página
37.
38. #user-page .user-header .user-summary{
39. border-bottom: 1px solid #dadada;
40. position: relative;
41. }
42.
43. #user-page .user-header .user-summary .user-avatar{
44. position: absolute;
45. display: inline-block;
46. height: 200px;
47. width: 200px;
48. border-radius: 10px;
49. left: 50px;
50. top: -100px;
51. overflow: hidden;
52. border-radius: 12px;
53. box-sizing: content-box;
54. border: 5px solid #fafafa;
55. box-shadow: 0 0 3px #999;
56. }
57.
58. #user-page .user-avatar .avatar-box{
59. position: relative;
60. height: 100%;
61. width: 100%;
62. }
63.
64. #user-page .user-header .user-summary .user-avatar img{
65. height: 100%;
66. width: 100%;
67. }
68.
69. .select-avatar{
70. position: absolute;
71. left: 0px;
72. right: 0px;
73. bottom: 0px;
74. top: 0px;
75. padding-top: 50px;
76. font-size: 20px;
77. font-weight: bold;
78. }
79.
80. .select-avatar:hover{
81. padding-top: 40px;
82. border: 5px solid tomato;
83. }
84.
85. #user-page .user-avatar{
86. display: inline-block;
87. height: 200px;
88. width: 200px;
89. border-radius: 10px;
90. left: 50px;
91. top: -100px;
92. overflow: hidden;
93. border-radius: 12px;
94. box-sizing: content-box;
95. border: 5px solid #fafafa;
96. box-shadow: 0 0 3px #999;
97. }
98.
99. #user-page .user-avatar img{
100. height: 100%;
101. width: 100%;
102. }

Página | 258
103.
104. #user-page .user-header .user-summary .user-summary-menu{
105. margin: 0px;
106. padding: 0px;
107. display: inline-block;
108. }
109.
110. #user-page .user-header .user-summary .user-summary-menu li{
111. text-align: center;
112. padding: 0px;
113. display: inline-block;
114. position: relative;
115.
116. }
117.
118. #user-page .user-header .user-summary .user-summary-menu li a{
119. padding: 15px 15px 0px;
120. display: inline-block;
121. position: relative;
122. }
123.
124. #user-page .user-header .user-summary .user-summary-menu li a::before{
125. content: "";
126. display: block;
127. position: absolute;
128. left: 0px;
129. right: 0px;
130. height: 0px;
131. bottom: 0px;
132. background: #1B95E0;
133. transition: 0.3s;
134.
135. }
136.
137. #user-page .user-header .user-summary .user-summary-menu li a:hover::before{
138. display: block;
139. height: 5px;
140. }
141.
142. #user-page .user-header .user-summary .user-summary-
menu li a.selected::before{
143. display: block;
144. height: 5px;
145. }
146.
147. #user-page .user-header .user-summary .user-summary-menu li .summary-label{
148. font-size: 11px;
149. margin: 0px;
150. }
151.
152. #user-page .user-header .user-summary .user-summary-menu li .summary-value{
153. font-weight: bold;
154. font-size: 18px;
155. color: #666;
156.
157. }
158.
159. #user-page .user-header .user-summary .edit-button{
160. margin: 15px 0px;
161. float: right;
162. }
163.
164. .tweet-footer{
165. padding-top: 10px;
166. }
167.

259 | Página
168. #user-info{
169. top: -180px;
170. position: absolute;
171. display: block;
172. position: relative;
173. padding: 10px;
174. margin-left: 35px;
175. max-width: 350px;
176. width: 280px;
177. max-width: 100%;
178. float: right;
179.
180. }
181.
182. #user-info .user-info-edit{
183. padding: 10px;
184. background-color: #E8F4FB;
185.
186. }
187.
188. #user-info .user-info-edit .user-info-username{
189. color: #1B96E0;
190. margin-top: 10px;
191. }
192.
193. #user-info .user-info-edit textarea,
194. #user-info .user-info-edit input{
195. display: block;
196. width: 100%;
197. border: 1px solid #A3D4F2;
198. outline: none;
199. border-radius: 5px;
200. padding: 5px;
201. }
202.
203. #user-info .user-info-edit textarea{
204. resize: none;
205. height: 220px;
206. }
207.
208. #user-info .user-info-name{
209. font-size: 22px;
210. font-weight: bold;
211. margin: 0px;
212. }
213.
214. #user-info .user-info-username{
215. font-size: 14px;
216. }
217.
218. #user-info .user-info-description{
219.
220. }
221.
222. @media (min-width: 576px) {
223. #user-page .user-avatar{
224. width: 150px;
225. height: 150px;
226. }
227.
228. #user-info{
229. top: -150px;
230. }
231. }
232.
233. @media (min-width: 1200px) {

Página | 260
234. #user-info{
235. top: -180px;
236. }
237.
238. #user-page .user-avatar{
239. width: 200px;
240. height: 200px;
241. }
242. }
243.
244. @media (min-width: 1000px) {
245. #user-page .user-header .user-banner{
246. height: 300px;
247. }
248. }
249.
250. @media (min-width: 1400px) {
251. #user-page .user-header .user-banner{
252. height: 400px;
253. }
254. }
255.
256. @media (min-width: 1800px) {
257. #user-page .user-header .user-banner{
258. height: 500px;
259. }
260. }

Guardamos los cambios y ya deberíamos de poder observar los resultados. Para


esto, puedes entrar a la URL de un usuario tengas registrado o el usuario test,
http://localhost:8080/test:

Fig. 80 - UserPage en modo solo lectura.

261 | Página
Edit mode

Cuando un usuario se encuentra en la página de su perfil, tendrá la opción de


editarlo, con esto, la vista tendrá que cambiar considerablemente, remplazando
los elementos visuales por controles en los que pueda interactuar el usuario.

En la siguiente imagen podemos observar los cambios al pasar a modo edición.


El nombre cambia de ser un span a un Input, la descripción cambia a un textarea
y tanto el banner como el avatar se les agrega la funcionalidad de seleccionar
una imagen con tan solo hacer click sobre ellas.

Fig. 81 - UserPage en modo edición.

Para no hacer tan compleja la explicación, iremos agregando la funcionalidad


paso a paso, al mismo tiempo que la explicamos. Iniciaremos con el orden natural
en que se dan los pasos, es decir, iniciaremos con el botón que habilita el modo
edición.

Agregaremos el bloque <If> justo después de la lista <ul> correspondiente al


menú de navegación (línea 96 aprox). Este If permitirá validar si el usuario del
perfil es el mismo usuario que esta autenticado en la aplicación, si los dos
coinciden, entonces el botón para editar perfil de habilita.

1. ... </ul>
2.
3. <If condition={profile.userName === storageUserName}>
4. <button className="btn btn-primary edit-button"
5. onClick={this.changeToEditMode.bind(this)} >
6. {this.state.edit ? "Guardar" : "Editar perfil"}</button>
7. </If>

Página | 262
Cuando el usuario presione el botón, se llamará a la función changeToEditMode la
cual también deberemos de agregar:

1. changeToEditMode(e){
2. if(this.state.edit){
3. let request = {
4. username: this.state.profile.userName,
5. name: this.state.profile.name,
6. description: this.state.profile.description,
7. avatar: this.state.profile.avatar,
8. banner: this.state.profile.banner
9. }
10.
11. APIInvoker.invokePUT('/secure/profile', request, response => {
12. if(response.ok){
13. this.setState(update(this.state,{
14. edit: {$set: false}
15. }))
16. }
17. },error => {
18. console.log("Error al actualizar el perfil");
19. })
20. }else{
21. let currentState = this.state.profile
22. this.setState(update(this.state,{
23. edit: {$set: true},
24. currentState: {$set: currentState}
25. }))
26. }
27. }

Esta función tiene dos propósitos, por un lado, habilita el modo edición, pero por
el otro lado, guarda los cambios si se ejecuta estando en modo edición. Veamos
cómo funciona.

La función inicia con un if, el cual valida la propiedad this.state.edit, que si


recordamos, se inicializa en false desde el constructor. Esto quiere decir que la
primera vez que se ejecute esta función, la condición no se cumplirá y entraremos
en el else, el cual únicamente cambia la propiedad edit a true. Lo que disparará
la actualización del componente. Por otro lado, si la condición se cumple,
entonces actualizaremos el perfil con los nuevos valores capturados mediante el
servicio /secure/profile y concluye actualizando la propiedad edit a false y
regresando el componente a modo solo lectura.

Documentación: Actualización del perfil de


usuario

Mediante este servicio es posible actualizar el perfil del


usuario (/secure/profile)

Una vez que el componente se encuentra en modo edición, se dispara la


actualización de todo el componente, y con ello la función render, en done
tendremos que agregar los cambios marcados:

263 | Página
1. <div className="user-banner" style={bannerStyle}>
2. <If condition={this.state.edit}>
3. <div>
4. <label htmlFor="bannerInput" className="btn select-banner">
5. <i className="fa fa-camera fa-2x" aria-hidden="true"></i>
6. <p>Cambia tu foto de encabezado</p>
7. </label>
8. <input href="#" className="btn"
9. accept=".gif,.jpg,.jpeg,.png"
10. type="file" id="bannerInput"
11. onChange={this.imageSelect.bind(this)} />
12. </div>
13. </If>
14. </div>

Se tendrá que agregar el bloque <If> que comprende de las líneas 2 a 13 dentro
del <div> que contiene el banner. Esto habilitará que el banner permita cambiar
la imagen mediante un click.

Para permitir la carga de la imagen, vamos a utilizar la misma estrategia que


utilizamos para la imagen del componente Reply, es decir, crearemos un Input
de tipo file oculto y un label que será lo que el usuario podrá ver. Al momento
de realizar el click, la función imagenSelect se disparará:

1. imageSelect(e){
2. let id = e.target.id
3. e.preventDefault();
4. let reader = new FileReader();
5. let file = e.target.files[0];
6.
7. if(file.size > 1240000){
8. alert('La imagen supera el máximo de 1MB')
9. return
10. }
11.
12. reader.onloadend = () => {
13. if(id == 'bannerInput'){
14. this.setState(update(this.state,{
15. profile: {
16. banner: {$set: reader.result}
17. }
18. }))
19. }else{
20. this.setState(update(this.state,{
21. profile: {
22. avatar: {$set: reader.result}
23. }
24. }))
25. }
26. }
27. reader.readAsDataURL(file)
28. }

Esta función hace exactamente lo mismo que la función de carga de imagen del
componente Reply, por lo que no nos detendremos a explicar, solo basta resumir
que la función carga una imagen seleccionada y la guarda en la propiedad avatar
o banner del estado.

Página | 264
NOTA: Podrías crear una función externa que pueda ser reutilizada tanto en
UserPage como en Reply y así evitar repetir código, sin embargo, eso te lo puedes
llevar de tarea si sientes confiado en poder hacer el cambio.

El siguiente cambio que realizaremos es referente a la imagen del avatar, el cual


consiste en habilitar el cambio de imagen mediante un click, para ello deberemos
remplazar el siguiente fragmento de código:

1. <div className="avatar-box">
2. <img src={profile.avatar} />
3. </div>

Y lo remplazaremos por el siguiente:

1. <Choose>
2. <When condition={this.state.edit} >
3. <div className="avatar-box">
4. <img src={profile.avatar} />
5. <label htmlFor="avatarInput"
6. className="btn select-avatar">
7. <i className="fa fa-camera fa-2x"
8. aria-hidden="true"></i>
9. <p>Foto</p>
10. </label>
11. <input href="#" id="avatarInput"
12. className="btn" type="file"
13. accept=".gif,.jpg,.jpeg,.png"
14. onChange={this.imageSelect.bind(this)}
15. />
16. </div>
17. </When>
18. <Otherwise>
19. <div className="avatar-box">
20. <img src={profile.avatar} />
21. </div>
22. </Otherwise>
23. </Choose>

Este cambio hace que existan dos posibles resultados. Si el componente está en
solo lectura, solo se verá la imagen (<img>) que ya teníamos (línea 19 a 21). Por
otro lado, si estamos en modo edición, se verá la misma imagen, pero con el
input file y el label que ya conocemos. En caso de seleccionar una imagen,
vamos a reutilizar la función imageSelect.

El siguiente cambio es referente a los controles para capturar los datos básicos
del perfil, por lo cual, tendremos que eliminar la siguiente sección:

1. <div>
2. <p className="user-info-name">{profile.name}</p>
3. <p className="user-info-username">@{profile.userName}</p>

265 | Página
4. <p className="user-info-description">
5. {profile.description}</p>
6. </div>

Y será remplaza por esta otra:

1. <Choose>
2. <When condition={this.state.edit} >
3. <div className="user-info-edit">
4. <input maxLength="20" type="text" value={profile.name}
5. onChange={this.handleInput.bind(this)} id="name"/>
6. <p className="user-info-username">@{profile.userName}</p>
7. <textarea maxLength="180" id="description"
8. value={profile.description}
9. onChange={this.handleInput.bind(this)} />
10. </div>
11. </When>
12. <Otherwise>
13. <div>
14. <p className="user-info-name">{profile.name}</p>
15. <p className="user-info-username">@{profile.userName}</p>
16. <p className="user-info-description">
17. {profile.description}</p>
18. </div>
19. </Otherwise>
20. </Choose>

Este cambio añade una condición para agregar un input y un textarea en caso
de estar en edición y respeta el funcionamiento anterior en caso de estar en
modo solo lectura.

También podemos observar que utilizamos la función handleInput para gestionar


la captura de datos del usuario.

1. handleInput(e){
2. let id = e.target.id
3. this.setState(update(this.state,{
4. profile: {
5. [id]: {$set: e.target.value}
6. }
7. }))
8. }

Esta función no tiene nada de especial, pues solo actualiza el campo name o
userName, según el ID del control que genera los eventos.

En este punto, el usuario podrá decidir guardar los cambios, presionando el botón
“Guardar”, el cual cambio de nombre de “Editar perfil” al momento de entrar en
modo edición.

Por otra parte, el usuario podría decidir cancelar la operación y dejar el perfil tal
y como estaba antes de la edición, para ello, tendremos que agregar un nuevo
botón:

Página | 266
1. <If condition={profile.follow != null &&
2. profile.userName !== storageUserName} >
3. <button className="btn edit-button"
4. onClick={this.follow.bind(this)} >
5. {profile.follow
6. ? (<span><i className="fa fa-user-times"
7. aria-hidden="true"></i> Siguiendo</span>)
8. : (<span><i className="fa fa-user-plus"
9. aria-hidden="true"></i> Seguir</span>)
10. }
11. </button>
12. </If>
13.
14. <If condition= {this.state.edit}>
15. <button className="btn edit-button" onClick=
16. {this.cancelEditMode.bind(this)} >Cancelar</button>
17. </If>

Este fragmento de código deberá quedar después de terminar el bloque <If> para
agregar el botón Siguiendo/Seguir.

Adicional, se agregará la función cancelEditMode, que se encargar de restaurar


los cambios.

1. cancelEditMode(e){
2. let currentState = this.state.currentState
3. this.setState(update(this.state,{
4. edit: {$set: false},
5. profile: {$set: currentState}
6. }))
7. }

Quiero que pongas atención en la línea 2, pues en ella vemos que obtiene la
propiedad currentState del estado. Esta propiedad se establece en la función
changeToEditMode antes de actualizar la propiedad edit a true, a la cual le asigna
el valor del estado actual. Con esto logramos respaldar en currentState los
valores antes de ser actualizados.

Con estos cambios, nuestro componente está terminado y ya solo guardamos los
cambios, actualiza el navegador y podremos empezar a editar nuestro perfil.

267 | Página
Fig. 82 - Estado actual del componente UserPage.

El componente MyTweets

El componente MyTeweets es utilizado como contenedor para mostrar el contenido


central de la página de perfil del usuario. Dentro del componente veremos solo
los Tweets del usuario y del lado derecho un listado de los usuarios sugeridos.

Con la finalidad de no reinventar la rueda, vamos a reutilizar los componentes


que ya tenemos, como lo son SuggestedUser y TweetsContainer.

Página | 268
Fig. 83 - MyTweets component.

Iniciemos creando el archivo MyTweets.js en el path /app, el cual deberá quedar


de la siguiente manera:

1. import React from 'react'


2. import TweetsContainer from './TweetsContainer'
3. import SuggestedUser from './SuggestedUser'
4. import PropTypes from 'prop-types'
5.
6. class MyTweets extends React.Component{
7.
8. constructor(props){
9. super(props)
10. }
11.
12. render(){
13. return(
14. <div className="row">
15. <div className="col-xs-12 col-sm-12 col-md-12
16. col-lg-8 no-padding-right">
17. <TweetsContainer profile={this.props.profile} onlyUserTweet={true}/>
18. </div>
19. <div className="hidden-xs hidden-sm hidden-md col-lg-4">
20. <SuggestedUser/>
21. </div>
22. </div>
23. )
24. }
25. }
26.
27. MyTweets.propTypes = {
28. profile: PropTypes.object
29. }
30.
31. export default MyTweets;

Podemos ver rápidamente que este nuevo componente no tiene nada nuevo que
aportar a nuestro conocimiento, pues todo lo que utilizamos aquí ya lo hemos

269 | Página
aprendido, por lo que explicaré rápidamente las partes claves y sin entrar en los
detalles.

Podemos apreciar que la función render solo muestra los componentes


TweetsContainer y SuggestedUser, estos componentes ya los conocemos por lo
que no hay nada que explicar, sin embargo, en la creación del componente
TweetsContainer, podemos apreciar que lo estamos creando con la propiedad
onlyUserTweet en true. Esta propiedad hacer que solo se vean los tweets del
usuario y no todos los Tweets en orden cronológico, como pasa en la pantalla
principal.

Esto lo logramos por medio de la función loadTweets del componente


TweetsContainer, el cual ejecuta el API con la siguiente URL:

1. let url = '/tweets' + (onlyUserTweet ? "/" + username : "")

Es decir, cuando la propiedad onlyUserTweet es false, la URL generada es


/tweets, pero cuando la propiedad es true, la URL generada es /tweets/{user}.
En la primera URL, el API entiende que se requiere todos los tweets y en la
segunda, entiende que requieres solo tweets del usuario en cuestión. También
podemos ver que le envía el objeto profile, que recibirá como prop.

Lo que seguiría será agregar un Route en el componente UserPage para que se


active por default en cuanto entremos al perfil del usuario, esta sección va dentro
del div que está casi al final del método render y que aparece marcado en
amarillo en el siguiente fragmento de código:

1. <div className="col-xs-12 col-sm-8 col-md-7


2. col-md-push-1 col-lg-7">
3. <Route exact path="/:user" component={
4. () => <MyTweets profile={this.state.profile} />} />
5. </div>

También deberemos de agregar la siguiente clase de estilo al archivo styles.css:

1. .tweet-container-header{
2. padding: 10px;
3. font-size: 19px;
4. }

Finalmente deberemos de guardar los cambios y actualizar el navegador para ver


los cambios:

Página | 270
Fig. 84 - MyTweets integrado con UserPage.

En este punto hemos terminado el componente MyTweets, y la página del perfil


del usuario ya se ve más completa, sin embargo, todavía tenemos un issue que
resolver. Y es que cuando cambiamos de perfil desde la misma página del perfil
de un usuario, la página no se actualiza. Para reproducir este error solo basta
con irnos a nuestro perfil desde el Toolbar, luego, del lado derecho nos
aparecerán algunos perfiles sugeridos, le damos click a cualquiera y veremos que
la barra de navegación si cambia, pero la pantalla permanece con los datos de
nuestro perfil. Ahora bien, si desde el nuevo perfil actualizamos el navegador,
veremos que este se carga correctamente.

Me gustaría que te detuvieras en este punto y trataras de investigar que está


pasando, no necesariamente que sepas como solucionarlo, pero al menos sí que
logres comprender la causa del error.

Si lo lograste resolver el problema, no te preocupes, pues en realidad este es un


problema fácil de resolver pero muy difícil de entender si no dominas el ciclo de
vida de los componentes. El detalle aquí es que cuando entramos a la página del
perfil del usuario la primera vez, el componente se monta, y el método
componentDidMount se ejecuta, sin embargo, cuando cambiamos de perfil, el
componente UserPage ya está montado, por lo que solo se actualizan las
propiedades, sin embargo, como el método componentDidMount solo se ejecuta
una vez durante el montaje, ya no hace nada cuando se actualiza, por lo que
permanece exactamente igual a pesar de que las propiedades han cambiado.

Para resolver este problema tenemos que agregar el método componentDidUpdate


y hacer una nueva llamada al API con el nuevo perfil, para esto, vamos a hacer
unos ajustes al componente UserPage para que puedes ver a continuación:

1. componentDidMount() {
2. let user = this.props.match && this.props.match.params.user
3. this.getUserProfile(user)
4. }

271 | Página
5.
6. componentDidUpdate(prevProps, prevState, snapshot) {
7. let newProfile = this.props.match.params.user
8. let prevProfile = prevProps.match.params.user
9. if (newProfile != prevProfile) {
10. this.getUserProfile(newProfile)
11. }
12. }
13.
14. getUserProfile(user) {
15. APIInvoker.invokeGET('/profile/' + user, response => {
16. this.setState({
17. edit: false,
18. profile: response.body
19. });
20. }, error => {
21. console.log("Error al cargar los Tweets");
22. window.location = '/'
23. })
24. }

Lo primero que tenemos que ver es que hemos actualizado el método


componentDidMount para hacer una llamada al método getUserProfile (línea 3)
en lugar de hacer allí mismo la llamada, con la finalidad de reutilizar la lógica
durante la actualización.

Por otra parte, hemos agregado el método componentDidUpdate para comparar


las propiedades anteriores con las nuevas (línea 9), de tal forma que llamaremos
nuevamente a la función getUserProfile solo si el nombre de usuario cambio.
Recuerda que el nombre de usuario llega como una prop por medio de React
Router.

Si actualizamos nuevamente el navegador, podemos ver que con este simple


cambio hemos solucionado el problema, sin embargo, algo similar pasa con el
componente TweetsContainer, pues si cambiamos de perfil, es probable que este
componente no se actualice correctamente, por lo que solo agregaremos el
método componentDidUpdate al archivo TweetsContainer.js:

1. componentDidUpdate(prevProps, prevState, snapshot) {


2. let prevUser = prevProps.profile.userName
3. let newUser = this.props.profile.userName
4.
5. let prevUserTweet = prevProps.onlyUserTweet
6. let newUserTweet = this.props.onlyUserTweet
7.
8. if (newUserTweet != prevUserTweet || prevUser != newUser) {
9. let onlyUserTweet = this.props.onlyUserTweet
10. this.loadTweets(newUser, onlyUserTweet, 0)
11. }
12. }

En este método lo que hacemos es validar si cambio la propiedad userName o


onlyUserTweet, para hacer una nueva llamada al API REST por medio del método
loadTweets que ya teníamos.

Página | 272
Actualización de los componentes

React a la medida de lo posible, buscará actualizar los


componentes en lugar de crear nuevos, En estos
casos, las funciones del ciclo de vida como el
constructor, componentWillMount y componentDidMount
no serán llamados.

Entender como React actualiza los componentes es de las partes más avanzadas,
pues requiere una comprensión completa del ciclo de vida de los componentes,
por lo que, si no logras entender esta parte, te recomiendo regresar al capítulo
de Ciclo de vida de los componentes para repasar este tema.

Actualizar el componente Tweet

Algo que nos quedó pendiente cuando creamos el componente Tweet fue habilitar
los Links del usuario que creo el tweet, para que de esta forma cualquier usuario
pueda ver ir a su perfil dando click en su nombre o nombre de usuario.

Para esto, vamos a regresar al archivo Tweets.js modificaremos el método


render para que se vea de la siguiente forma:

1. return (
2. <article className={tweetClass} id={"tweet-" + this.state._id}>
3. <img src={this.state._creator.avatar} className="tweet-avatar" />
4. <div className="tweet-body">
5. <div className="tweet-user">
6. <Link to={`/${this.state._creator.userName}`}>
7. <span className="tweet-name" data-ignore-onclick>
8. {this.state._creator.name}</span>
9. </Link>
10. <Link to={`/${this.state._creator.userName}`}>
11. <span className="tweet-username">
12. @{this.state._creator.userName}</span>
13. </Link>
14. </div>
15. <p className="tweet-message">{this.state.message}</p>
16. <If condition={this.state.image != null}>
17. <img className="tweet-img" src={this.state.image} />
18. </If>
19. <div className="tweet-footer">
20. <a className={this.state.liked ? 'like-icon liked' : 'like-icon'}
21. data-ignore-onclick>
22. <i className="fa fa-heart " aria-hidden="true"
23. data-ignore-onclick></i> {this.state.likeCounter}
24. </a>
25. <If condition={!this.props.detail} >
26. <a className="reply-icon" data-ignore-onclick>
27. <i className="fa fa-reply " aria-hidden="true"
28. data-ignore-onclick></i> {this.state.replys}
29. </a>
30. </If>
31. </div>
32. </div>

273 | Página
33. <div id={"tweet-detail-" + this.state._id} />
34. </article>
35. )

Con este cambio podremos ver que los Tweets ahora te pueden llevar al perfil
del usuario con tan solo dar click sobre su nombre.

Página | 274
Resumen

En este capítulo hemos aprendido a crear aplicaciones que siga el patrón de


Single Page Application, el cual consiste en crear aplicaciones que son creadas
en su mayoría desde FrontEnd y solo requieren del servidor para recuperar los
datos.

Por otra parte, nos hemos apoyado de la librería React Router para gestionar la
forma en que React interpreta las URL para determinar los componentes que
debe mostrar, a la vez que hemos visto como crear URL amigables apoyándonos
de los URL Params.

Con respecto al proyecto Mini Twitter hemos avanzado bastante, pues hemos
desarrollado uno de los componentes centrales de la aplicación, me refiero a
UserPage, el cual muestra el perfil de los usuarios y permite editarlo.

275 | Página
Interfaces interactivas
Capítulo 10

Una de las características que más agradecen los usuarios además de que
funcione bien la aplicación, es que sea vea bien, tenga algunos efectos o
animaciones que las haga más agradable a la vista, como lo son las animaciones
y las transacciones.

React ofrece el módulo react-transition-group que intenta ser de ayuda para


crear animaciones más fluidas para los componentes. Este módulo no intenta ser
la panacea para resolver todos los problemas de animación, en su lugar, este
módulo se ajusta al ciclo de vida de los componentes para ayudar a dar animación
cuando un componente es creado, activado y retirado de la vista, por lo que
cualquier otro tipo de animación no está cubierto.

Es muy probable que te desanime al escuchar la limitación de este módulo, sin


embargo, verás que tiene sus bondades. Pues será de gran utilidad.

Antes de iniciar con la explicación del módulo react-transition-group, es


necesario entender los conceptos CSS Transition y las animaciones mediante CSS
Keyframe.

Qué son las transiciones

Lo primero que debemos de saber, es que las transiciones no son una


característica propia de React o del módulo react-transition-group, sino que
son parte del estándar de CSS y fueron agregadas en la especificación de CSS 3,
por lo que pueden ser utilizadas en desarrollo de cualquier sitio web que uso o
no React.

Las transiciones se logran mediante la definición de la propiedad transition en


una clase de estilo o como un estilo en línea, por ejemplo:

1. .card{
2. background-color: black;
3. transition: background-color 500ms;
4. }
5.
6. .card:hover{
7. background-color: blue;

Página | 276
8.
9. }

La clase de estilo .card, define que el color de fondo del elemento deberá ser
negro (#000) y establece la propiedad transition, la cual recibe dos parámetros:

• Propertie: define la o las propiedades que va a afectar


• Time: Tiempo que dura la transición, podría ser en segundo (s) o
milisegundos (ms).

En este caso, estamos indicando que la transición solo debe de afectar la


propiedad background-color y la animación debe durar 500ms.

Fig. 85 - CSS Transition.

En la imagen podemos ver como se llevaría a cabo una animación del background,
suponiendo que ponemos la clase de estilo .card a un div. Podemos apreciar que
el color cambia de negro a azul y esta transición debería de ocurrir en 500
milisegundos.

Debido a que este no es un libro de CSS, no quisiera salirme del tema central,
que es aprender React, por lo que, si no estás al tanto de CSS transition, te
recomiendo ver la documentación que nos ofrece Mozilla, la cual está muy
completa y en español.

Qué son las animaciones

Las animaciones son más complejas que las transiciones, pero al mismo tiempo
son mucho más potentes, pues permiten crear animaciones más sofisticadas. Las
animaciones se crean mediante la instrucción @keyframe en CSS. Los key frame
permiten definir una animación partiendo de un puto a otro o dividir la animación
en fragmentos, los cuales pueden cambiar el comportamiento de la animación.

1. @keyframes card-animation{
2. from {background-color: black;}
3. to {background-color: blue;}
4. }

277 | Página
El fragmento de CSS anterior crea una animación la cual cambia el background
de negro a azul. Este tipo de animación solo tiene un estado inicial y uno final.

1. @keyframes example {
2. 0% {background-color: black;}
3. 25% {background-color: green;}
4. 50% {background-color: red;}
5. 100% {background-color: blue;}
6. }

También podemos definir animaciones por sección como la anterior, la cual crea
una animación que cambia el background inicialmente a Negro, luego al 25% de
la animación lo cambia Verde, luego a 50% a rojo y al final queda en azul.

Finalmente, para que la animación se active, se deberá definir la propiedad


animation, con el nombre de la animación (keyframe) y la duración de la
animación

1. .card {
2. animation: card-animation 5s
3. }

La clase de estilo anterior provocará que el keyframe crad-animation se ejecute


en 5 segundo, por lo que, suponiendo que se aplica a un div, tendremos una
animación como la siguiente:

Fig. 86 - Keyframe animation.

Como vemos, las 3 primeras partes de la animación se llevan a cabo en un mismo


periodo, pues tiene asignados un 25% de la animación, sin embargo, de la
tercera a la cuarta parte, tenemos un 50%, por lo que la transición de rojo a azul
va a ser más tardada (2.5 segundos).

Nuevamente, las animaciones no son el tema central de este libro, por lo que, si
quieres aprender más acerca de las animaciones, te dejo la documentación en
español de Mozilla.

Página | 278
Introducción a CSSTranstion

Seguramente te estarás preguntando qué tiene que ver las transiciones y las
animaciones con CSSTransaction, si al final, estas dos son características de CSS
y no de React. Aunque ese argumento puede ser verdad, la realidad es que el
módulo react-transition-group se apoya de estas características de CSS para
llevar a cabo las animaciones.

1. <CSSTransitionGroup
2. transitionName="card"
3. transitionEnter={true}
4. transitionEnterTimeout={500}
5. transitionAppear={false}
6. transitionAppearTimeout={0}
7. transitionLeave={false}
8. transitionLeaveTimeout={0}>
9.
10. <AnyComponent/>
11.
12. </CSSTransitionGroup>

Para crear una animación, es necesario anidar el componente a animar dentro


del componente CSSTransactionGroup, adicional, es necesario definir la propiedad
transactionName, el nombre que definamos servirá como base para determinar
las clases de estilo que se utilizará según la animación a realizar.

La animación se lleva a cabo en tres pasos:

• Appear: Se activa para todos los hijos de CSSTransactionGroup cuando


este es creado inicialmente.
• Enter: Se activa para todos los hijos de CSSTransactionGroup que se
agregan después de que CSSTransactionGroup fue creado.
• Leave: Se activa para todos los hijos de CSSTransactionGroup que son
eliminados.

Lo siguiente es definir que transiciones se deben de ejecutar y cuáles no, esto se


hace mediante las siguientes propiedades booleanas:

• transitionAppear
• transitionEnter
• transitionLeave

Finalmente, es necesario definir cuánto tiempo durará la animación en


Milisegundos, con las propiedades numéricas:

279 | Página
• transitionAppearTimeout
• transitionEnterTimeout
• transitionLeaveTimeout

Ya con todo este configurado, solo resta tener las clases de estilo
correspondientes, las cuales deben de cumplir una sintaxis muy estricta en su
nombre, de lo contrario las transiciones no se llevarán a cabo.

Lo primero que debemos de saber, es que cada transición (appear, enter y leave)
tiene dos estados (inactivo y activo), en el primero, se deberá definir como se
deberá ver el componente al iniciar la transición y en el segundo, como debería
de terminar el componente una vez que la transición termino.

Dicho esto, entonces se entiende que podría existir 3 paredes de clases de estilo,
las cuales deberá tener el siguiente formato:

• {transitionName}-{appear|enter|leave}
• {transitionName}-{appear|enter|leave}.{transitionName}-
{appear|enter|leave}-active.

El primero representa el estado inactivo y el segundo el activo, pero veamos un


ejemplo para entender cómo quedaría, suponiendo que el valor de
transitionName = card.

1. .card-enter{
2. }
3.
4. .card-enter.card-enter-active{
5. }
6.
7. .card-leave {
8. }
9.
10. .card-leave.card-leave-active {
11. }
12.
13. .card-appear {
14. }
15.
16. .card-appear.card-appear-active {
17. }

Las clases de estilo pertenecientes al estado inactivo se establecerá primero, es


decir, las de la línea 1, 7 y 13. Esto es así para preparar el componente antes de
la animación. Seguido, las animaciones se activarán, mediante las clases de estilo
de las líneas 4, 10 y 16. Esto quiero decir que los elementos quedarán con los
estilos que aquí hemos definido una vez que la animación ha concluido.

Veamos un ejemplo de cómo quedaría una transición para un componente, el


cual cambie de color de negro a azul al crearse por primera vez.

Página | 280
1. .card-appear {
2. background-color: black;
3. }
4.
5. .card-appear.card-appear-active {
6. background-color: blue;
7. transition: background-color 0.5s;
8. }

Notemos que usamos el atributo transition para determinar la propiedad que


debe de animarse y el tiempo de la misma (línea 7).

Cabe resaltar no es requerido definir los estilos para todas las transiciones, si no
solo las que habilitemos desde el componente CSSTransactionGroup.

Mini Twitter (Continuación 4)

Como parte de la continuación del proyecto Mini Twitter, vamos a implementar


lo que hemos aprendido en esta unidad, con la intención de fortalecer los
conocimientos que hemos adquirido

El componente UserCard

Una de las partes que no hemos abordado en la página del perfil del usuario es,
ver sus seguidores y las personas a las que seguimos. Dado que en estas dos
secciones los usuarios se representan de la misma forma, hemos decidido crear
un componente que represente a un usuario, y ese es UserCard. Veamos cómo
se ve este componente:

281 | Página
Fig. 87 - UserCard component.

Iniciaremos creando el archivo UserCard.js en el path /app, el cual deberá quedar


de la siguiente manera:

1. import React from 'react'


2. import { Link } from 'react-router'
3. import PropTypes from 'prop-types'
4.
5. const UserCard = ({user}) => {
6.
7. return (
8. <article className="user-card" >
9. <header className="user-card-banner"
10. style={{backgroundImage: 'url(' + user.banner + ')'}}>
11. <img src={user.avatar} className="user-card-avatar" />
12. </header>
13. <div className="user-card-body">
14. <Link to={"/" + user.userName} className="user-card-name" >
15. <p>{user.name}</p>
16. </Link>
17. <Link to={"/" + user.userName} className="user-card-username">
18. <p>@{user.userName}</p>
19. </Link>
20. <p className="user-card-description">{user.description}</p>
21. </div>
22. </article>
23. )
24. }
25.
26. UserCard.propTypes = {
27. user: PropTypes.object.isRequired
28. }
29.
30. export default UserCard

Con tan solo observar el componente te darás cuenta que no hay nada nuevo
que analizar, pues el componente es muy simple y utiliza cosas que ya sabes
hasta este momento, por lo que solo mencionare las cosas relevantes. Primero

Página | 282
que nada, observemos que el componente recibe como prop el objeto user (línea
5), el cual está marcado como obligatorio en los PropTypes.

Para concluir, solo faltaría agregar las clases de estilo correspondientes al archivo
styles.css:

1. /** UserCard component **/


2. .user-card{
3. margin-bottom: 20px;
4. position: relative;
5. border: 1px solid #E6ECF0;
6. border-radius: 5px;
7. overflow: hidden;
8. }
9.
10. .user-card .user-card-banner{
11. background-position: center;
12. background-size: cover;
13. height: 100px;
14. border-bottom: 1px solid ##E6ECF0;
15. }
16.
17. .user-card .user-card-avatar{
18. width: 70px;
19. height: 70px;
20. border-radius: 5px;
21. position: absolute;
22. top: 70px;
23. left: 10px;
24. }
25.
26.
27. .user-card .user-card-body{
28. padding-top: 100px;
29. background-color: #FFF;
30. padding: 10px 10px 20px;
31.
32. }
33.
34. .user-card .user-card-body .user-card-username p{
35. font-size: 12px;
36. color: #66757f;
37. }
38.
39. .user-card .user-card-body .user-card-name{
40. font-weight: bold;
41. font-size: 18px;
42. display: block;
43. position: relative;
44. margin-top: 40px;
45. }
46.
47. .user-card .user-card-body .user-card-name p {
48. margin: 0px;
49. }
50.
51. .user-card .user-card-body .user-card-description{
52. font-size: 14px;
53. color: #66757f;
54. }
55.
56.

283 | Página
57. .user-card .user-card-body .user-card-username:hover,
58. .user-card .user-card-body .user-card-name:hover{
59. text-decoration: underline;
60. }

El componente Followings

Ya con el objeto UserCard podemos empezar con la creación de la sección de las


personas a las que seguimos (Following), la cual tiene la siguiente apariencia:

Fig. 88 - Followings Component.

Antes de empezar con la implementación, será necesario instalar el módulo


React-transition-group mediante el comando

npm install --save react-transition-group@1.1.3

Ya con eso, vamos a crear el archivo Followings.js en el path /app, el cual deberá
tener la siguiente estructura:

1. import React from 'react'


2. import UserCard from './UserCard'
3. import APIInvoker from './utils/APIInvoker'
4. import PropTypes from 'prop-types'
5. import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'
6.
7. class Followings extends React.Component{
8.
9. constructor(props){
10. super(props)
11. console.log(props);
12. this.state={
13. users: []
14. }
15. }

Página | 284
16.
17. componentWillMount(){
18. this.findUsers(this.props.profile.userName)
19. }
20.
21. componentWillReceiveProps(props){
22. this.setState({
23. tab: props.route.tab,
24. users: []
25. })
26. this.findUsers(props.profile.userName)
27. }
28.
29. findUsers(username){
30. APIInvoker.invokeGET('/followings/' + username, response => {
31. this.setState({
32. users: response.body
33. })
34. },error => {
35. console.log("Error en la autenticación");
36. })
37. }
38.
39. render(){
40. return(
41. <section>
42. <div className="container-fluid no-padding">
43. <div className="row no-padding">
44. <CSSTransitionGroup
45. transitionName="card"
46. transitionEnter = {true}
47. transitionEnterTimeout={500}
48. transitionAppear={false}
49. transitionAppearTimeout={0}
50. transitionLeave={false}
51. transitionLeaveTimeout={0}>
52. <For each="user" of={ this.state.users }>
53. <div className="col-xs-12 col-sm-6 col-lg-4"
54. key={this.state.tab + "-" + user._id}>
55. <UserCard user={user} />
56. </div>
57. </For>
58. </CSSTransitionGroup>
59. </div>
60. </div>
61. </section>
62. )
63. }
64. }
65.
66. Followings.propTypes = {
67. profile: PropTypes.object
68. }
69.
70. export default Followings;

En este componente si podemos observar algunas cosas nuevas, pues estamos


utilizando el componente CSSTransactionGroup (línea 44) para englobar una serie
de componentes UserCard (línea 55). La idea es que cuando los UserCard
aparezcan en pantalla, se vea como que aparecen gradualmente, en lugar de
aparecer de golpe.

285 | Página
Para ello hemos habilitado únicamente las transiciones de entrada
(transitionEnter=true), y el resto las hemos deshabilitado (false), pues no las
vamos a requerir.

Con respecto a los UserCard, estos deben de recibir como parámetro el usuario
que van a representar, el cual es obtenido como un array en la función
componentWillMount. Para recuperar a las personas que siguen un usuario, se
utiliza el servicio /followings/{user} mediante el método GET.

Documentación: Consulta de personas que


seguimos

El siguiente servicio es utilizado para recuperar el perfil


de los usuario que estamos siguiendo
(/followings/:username)

El siguiente paso es crucial para que la animación se lleve a cabo, en el cual


tendremos agregar las clases de estilo para la animación en el archivo
styles.css:

1. .card-enter{
2. opacity: 0;
3. }
4.
5. .card-enter.card-enter-active{
6. opacity: 1;
7. transition: opacity 500ms ease-in;
8. }
9.
10. /*.card-leave {
11. opacity: 0;
12. }
13.
14. .card-leave.card-leave-active {
15. opacity: 1;
16. transition: opacity 500ms ease-in;
17. }
18.
19. .card-appear {
20. opacity: 0;
21. }
22.
23. .card-appear.card-appear-active {
24. opacity: 1;
25. transition: opacity 500ms ease-in;
26. }*/

Para este ejemplo solo requerimos las dos primeras clases de estilo (líneas 1 a
8), sin embargo, he dejado comentadas las clases necesarias para leave y appear
en caso de que quieres realizar algunos experimentos.

Página | 286
Te explico cómo funciona, cuando el UserCard entre en escena, tomara el estilo
de la clase (.card-enter), lo que implica que se le establezca una opacidad de 0
(Totalmente transparente). Seguido de eso, la animación iniciará y le establecerá
los estilos de (.card-enter.card-enter-active) lo que implica establecer una
opacidad de 1 (Totalmente visible), pero adicional, se establece transition para
crear una transición de 500 milisegundos. Esto quiere decir que pasará de ser
totalmente transparente a totalmente visible en 0.5 segundos.

Finalmente, solo restaría agregar el componente Followings a nuestras reglas de


Router al final del método render del archivo UserPage.js:

1. <div className="col-xs-12 col-sm-8 col-md-7


2. col-md-push-1 col-lg-7">
3. <Switch>
4. <Route exact path="/:user" component={
5. () => <MyTweets profile={this.state.profile} />} />
6. <Route exact path="/:user/followers" component={
7. () => <Followers profile={this.state.profile} /> } />
8. </Switch>
9. </div>

Debido a que ahora podemos mostrar los tweets, los seguidores o las personas
que nos siguen, ya no basta con tan solo poner un Route para cada componte,
sino a que además, es necesario agregar un Switch para asegurarnos de que solo
un componente se muestra a la vez.

Todavía no probaremos los cambios, sino hasta que tengamos el componente


Followers que sigue a continuación.

El componente Followers

Este componente es exactamente igual al anterior, sin embargo, este consume


un servicio diferente para recuperar los seguidores.

Crearemos el archivo Followers.js en el path /app, el cual deberá tener el


siguiente contenido:

1. import React from 'react'


2. import UserCard from './UserCard'
3. import APIInvoker from './utils/APIInvoker'
4. import PropTypes from 'prop-types'
5. import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'
6.
7. class Followers extends React.Component{
8.
9. constructor(props){
10. super(props)
11. this.state={
12. users: []
13. }

287 | Página
14. }
15.
16. componentWillMount(){
17. this.findUsers(this.props.profile.userName)
18. }
19.
20. componentWillReceiveProps(props){
21. this.setState({
22. tab: props.route.tab,
23. users: []
24. })
25. this.findUsers(props.profile.userName)
26. }
27.
28. findUsers(username){
29. APIInvoker.invokeGET('/followers/' + username, response => {
30. this.setState({
31. users: response.body
32. })
33. },error => {
34. console.log("Error en la autenticación");
35. })
36.
37. }
38.
39. render(){
40. return(
41. <section>
42. <div className="container-fluid no-padding">
43. <div className="row no-padding">
44. <CSSTransitionGroup
45. transitionName="card"
46. transitionEnter = {true}
47. transitionEnterTimeout={500}
48. transitionAppear={false}
49. transitionAppearTimeout={0}
50. transitionLeave={false}
51. transitionLeaveTimeout={0}>
52. <For each="user" of={ this.state.users }>
53. <div className="col-xs-12 col-sm-6 col-lg-4"
54. key={this.state.tab + "-" + user._id}>
55. <UserCard user={user} />
56. </div>
57. </For>
58. </CSSTransitionGroup>
59. </div>
60. </div>
61. </section>
62. )
63. }
64. }
65.
66. Followers.propTypes = {
67. profile: PropTypes.object
68. }
69.
70. export default Followers;

Nos ahorraremos las explicaciones de este componente, pues es exactamente


igual al anterior, con la única diferencia que este consume el servicio de
seguidores (followers)

Página | 288
Documentación: Consulta de seguidores

El servicio de seguidores permite consultar el perfil de


todos los usuarios que nos siguiente
(/followeres/:username)

Nuevamente actualizaremos el componente UserPage para agregar la Ruta al


componente Followers:

1. <div className="col-xs-12 col-sm-8 col-md-7


2. col-md-push-1 col-lg-7">
3. <Switch>
4. <Route exact path="/:user" component={
5. () => <MyTweets profile={this.state.profile} />} />
6. <Route exact path="/:user/followers" component={
7. () => <Followers profile={this.state.profile} /> } />
8. <Route exact path="/:user/following" component={
9. () => <Followings profile={this.state.profile} /> } />
10. </Switch>
11. </div>

En este punto ya deberíamos de poder ver las secciones de seguidores y las


personas que nos siguen, sin embargo, cuando le damos click a siguiendo o
seguidores podrás ver que la página se pone en blanco y esto se debe a un
problema de rutas con React Router. Me gustaría que en este punto dieras una
pausa a la lectura y trataras por ti mismo de identificar que está pasando.

Si no diste con el problema no te preocupes, puede llegar a ser confuso para


alguien que va comenzando. Como mencionamos antes, el problema es de rutas.
Podrías pensar que el problema está en las rutas para los componentes Following
y Followers, sin embargo, si esto fuera cierto, al menos se viera el perfil del
usuario, lo que quiere decir que el problema viene de más atrás.

Si regresamos al método render del componente TwitterApp y analizamos la ruta


del componente UserPage podemos ver que esta es exacta (exact), lo que
quieres decir que únicamente se activará cuando estemos en /:user, por lo tanto,
cuando damos click en seguidores o siguiendo, la ruta cambia a /:user/following
y /:user/followers lo que hace que la regla no se cumpla, por lo tanto el
componente se desmonta y es por ello que vemos la pantalla en blanco.

1. return (
2. <>
3. <Toolbar profile={this.state.profile} />
4. <div id="mainApp" className="animated fadeIn">
5. <Switch>
6. <Route exact path="/" component={() =>
7. <TwitterDashboard profile={this.state.profile} />} />
8. <Route exact path="/signup" component={Signup} />
9. <Route exact path="/login" component={Login} />
10. <Route exact path="/:user" component={UserPage} />
11. </Switch>
12. <div id="dialog" />
13. </div>
14. </>
15. )

289 | Página
Para solucionar este problema basta con quitar el atributo exact, con lo cual todas
las rutas que comiencen con /:user serán válida y el componente se montará.

1. return (
2. <>
3. <Toolbar profile={this.state.profile} />
4. <div id="mainApp" className="animated fadeIn">
5. <Switch>
6. <Route exact path="/" component={() =>
7. <TwitterDashboard profile={this.state.profile} />} />
8. <Route exact path="/signup" component={Signup} />
9. <Route exact path="/login" component={Login} />
10. <Route path="/:user" component={UserPage} />
11. </Switch>
12. <div id="dialog" />
13. </div>
14. </>
15. )

Finalmente, solo quedaría guardar los cambios y ver los resultados. Mediante una
imagen es complicado ver una animación, por lo que te pido que te dirijas al
perfil del usuario que gustes y cambies entre las pestañas de Seguidores y
Siguiendo para comprobar los resultados.

Página | 290
Resumen

Sé que este capítulo no ha llegado a ser tan impresionante como pensabas, pues
a lo mejor esperabas librerías de interacción más sofisticadas, pero la realidad es
que con CSS es posible hacer que las aplicaciones luzcan realmente bien, incluso
con animaciones. Sin embargo, buscare que en próximas ediciones esta sección
se pueda ampliar con más cosas interesantes y ese es el motivo por el cual he
decidido crear esta pequeña sección por separado.

En este capítulo hemos hablado acerca de las animaciones y transiciones de CSS,


que si bien, no es el tema central de este libro, sí que te hemos dado las bases
para que tú mismo puedas profundizar en el tema.

Hemos hablado del módulo react-transition-group que nos ayuda a crear


animaciones cuando un componente es creado, agregado o eliminado, cosa que
es muy difícil mediante CSS.

291 | Página
Componentes modales
Capítulo 11

Existen ocasiones en las que requerimos crear componentes que se vean en


forma modal, es decir, que se vea sobre toda la aplicación y que impiden que el
usuario realice otra acción hasta que atienda lo que el componente modal está
solicitando.

Este tipo de comportamiento suelen ser lo más complicados para el programador


novato de React, no porque sea complicado en sí, sino porque no saben cómo
hacerlo, y recurren a diversos ejemplos de internet que nos confunde incluso más
de lo que ya estábamos.

En la actualidad existen diversos módulos para crear componentes modales o los


famosos alerts con estilos gráficos más modernos. Pero en esta ocasión quiero
explicarte cómo hacerlo por ti mismo de una forma muy simple

Algunas librerías existentes

En la actualidad existe una gran cantidad de librerías listas para ser usadas, las
cuales solo requieren de su instalación con npm y posteriormente su
implementación. Algunas librerías son fáciles de usar otras más complejas, pero
más configurables.

Debido a que estas librerías no son el punto focal de este libro y que evolucionan
constantemente, es complicado enseñarte a usar cada una de ellas, por lo que,
en su lugar, te vamos a nombrar algunas con su respectiva documentación para
que seas tú mismo quien determine que librerías se adapta mejor a tus
necesidades. La lista es:

• react-modal (https://github.com/reactjs/react-modal)
• react-modal-dialog (https://www.npmjs.com/package/react-modal-
dialog)
• react-modal-bootstrap: (https://www.npmjs.com/package/react-modal-
bootstrap)

Página | 292
Estas son las 3 librerías más populares para la implementación de componentes
modales, pero sin duda hay muchísimos más. Te invito a que los revises y veas
si alguno te llama la atención para usarlas como parte de tu pila de librerías.

Implementando modal de forma nativa

Adicional a la creación de componentes modales por medio de librerías, es posible


crear tus propios componentes modales sin necesidad de usar ninguna librería
existente, con la ventaja de que la puedes crear a tu gusto y que se adapte mejor
a la necesidad de tu proyecto.

En este punto podrías pensar, si ya existen librerías para hacerlo, para que
reinventar la rueda implementando mi propio sistema de componentes modales,
y puede que tengas razón, incluso, te alentamos a elegir una librería existente si
esta cumple a la perfección lo que requieres, sin embargo, crear tus propias
pantallas modales te da más control sobre los componentes, además que, es tan
simple que realmente no requiera mucho esfuerzo.

Como te comenté hace un momento, si decides utilizar una librería está bien, sin
embargo, como este es un libro para aprender React, queremos enseñarte a
hacerlo por ti mismo, para que de esta forma puedes dominar mejor la
tecnología. Como siempre, la decisión es solo tuya.

Antes de comenzar a crear componentes modales, es necesario conocer la teoría,


la cual se base en su totalidad en estilos CSS, por lo que si no te sientes confiado,
no te preocupes, aquí si vamos a detendremos a explicar lo necesario de CSS
para implementarlo.

Primer principio, un componente modal debe verse sobre todos los


componentes de la pantalla. Si solo agregáramos un componen dentro de otro,
este se vería como un hijo de este otro componte:

Fig. 89 - Agregar un componente dentro de otro.

293 | Página
Para lograr que mi componente se vea por encima, incluso de su componente
padre, es necesario encapsular nuestro componente dentro de elemento con
posición fija en la pantalla (position: fixed), el cual, abarcara toda el área visible
con ayuda de las propiedades (top, right, left, bottom) en cero:

Fig. 90 - Fixed element

Para asegurarnos que el elemento fixed se encuentre por encima del resto de
componentes, será necesario utiliza la propiedad z-index en un número superior
al resto de los elementos. Esta propiedad moverá nuestro elemento en el eje Z,
es decir lo traerá hasta el frente.

Adicional, agregamos las propiedades height y overflow en auto, con la finalidad


de que, si nuestro componente es más grande que la pantalla, se pueda realizar
un scroll.

Y eso es todo, lo que seguirá es solo agregar nuestro componente dentro del
elemento fixed y listo. Solo una cosa más, te sugiero que el contendedor de tu
componente (primer elemento) utilice la propiedad margin: auto, con la finalidad
de realizar un centrado horizontal perfecto en la pantalla:

Si estás pensando que esto es muy complicado y que mejor optarás por una
librería, espera a ver como lo implementamos en el proyecto Mini Twitter, para
que veas lo fácil que es.

Página | 294
React Portal

Durante todo este libro hemos hablado y explicado que React organiza los
componentes en forma jerárquica, donde un componente padre se compone de
otros componentes hijos y así sucesivamente, de tal forma que para que un
componente sea hijo de otro, debe de de ser un descendiente directo del padre,
sin embargo, existe ocasiones donde necesitamos que un componente hijo sea
montado sobre un nodo del DOM que está fuera de la jerarquía.

Un caso típico de esto son los modales, pues por lo general utilizamos un
elemento que está en lo más alto de la jerarquía del DOM para poder hacer que
se sobre ponga al resto de los elementos y es justo aquí donde entran los
portales.

Según la documentación oficial de React, un Portal es:

Nuevo concepto: React Portal

Los portales proporcionan una opción de primera clase


para renderizar hijos en un nodo DOM que existe por
fuera de la jerarquía del DOM del componente padre.

Debido a que vamos a estar utilizando un Portal para seguir avanzado en nuestro
proyecto, vamos a explicar cómo funcionan los portales para después pasar a
nuestro proyecto Mini Twitter.

1. import ReactDOM from 'react-dom'


2.
3. ReactDOM.createPortal(
4. child,
5. container
6. )

Para crear un Portal, tenemos que hacer uso del método createPortal que
proporciona el paquete react-dom, el cual recibe dos parámetros, el primero
corresponde al componente que vamos a montar dentro del portal, y el segundo
corresponde a un elemento DOM en donde se va a montar el portal.

Cuando creamos un portal, este retorna un elemento renderizable por React, por
lo que es normal retornarlo desde la función render de un componente. Un
ejemplo de cómo utilizarlo es crear un componente especial que retorno el portal:

1. import ReactDOM from 'react-dom'


2.
3.
4. import React from 'react'
5. import { createPortal } from 'react-dom'
6.

295 | Página
7. class Modal extends React.Component {
8.
9. render() {
10. return createPortal(child, container)
11. }
12. }
13. export default Modal

Cuando abordemos el proyecto Mini Twitter retornaremos a los portales para


explicar con ejemplo React como funcionan.

Mini Twitter (Continuación 5)

Ya con la teoría fundamental para crear nuestras propias componentes modales,


no hay nada que nos detenga para empezar a implementarlo en nuestro
proyecto.

El componente TweetDetail

El último componente que nos falta por ver es TweetDetail y con él, estaríamos
concluyendo el proyecto Mini Twitter. Este es un componte modal que nos
permite visualizar un tweet con todas las interacciones de los demás usuarios.

Fig. 91 - Componente TweetDetail terminado.

Si prestamos atención al componente TweetDetail podrás observar que está


compuesto del componente Tweet y Reply, por lo que ya tenemos parte del
recorrido completado.

Página | 296
Antes de comenzar, quiero decir que este componente a pesar de ser visualizado
de forma modal, no es modal per se, sí no que lo diseñaremos como si fuera un
componente cualquiera y al final utilizaremos por Portales para hacerlo modal.

Dicho lo anterior, iniciaremos creando el archivo TweetDetail.js en el path /app,


el cual se verá de la siguiente forma:

1. import React from 'react'


2. import Reply from './Reply'
3. import Tweet from './Tweet'
4. import APIInvoker from './utils/APIInvoker'
5. import update from 'immutability-helper'
6. import browserHistory from './History'
7.
8. class TweetDetail extends React.Component{
9.
10. componentWillMount(){
11. let tweet = this.props.match.params.tweet
12. APIInvoker.invokeGET('/tweetDetails/'+tweet, response => {
13. this.setState( response.body)
14. },error => {
15. console.log("Error al cargar los Tweets");
16. })
17. }
18.
19. addNewTweet(newTweet){
20. let oldState = this.state;
21. let newState = update(this.state, {
22. replysTweets: {$splice: [[0, 0, newTweet]]}
23. })
24. this.setState(newState)
25.
26. let request = {
27. tweetParent: this.props.match.params.tweet,
28. message: newTweet.message,
29. image: newTweet.image
30. }
31.
32. APIInvoker.invokePOST('/secure/tweet', request, response => {
33. },error => {
34. console.log("Error al crear los Tweets");
35. })
36. }
37.
38. handleClose(){
39. $( "html" ).removeClass( "modal-mode");
40. browserHistory.goBack()
41. }
42.
43. render(){
44. return(
45. <>
46. <Choose>
47. <When condition={this.state == null}>
48. <div className="tweet-detail">
49. <i className="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
50. </div>
51. </When>
52. <Otherwise>
53. <div className="tweet-detail">
54. <i className="fa fa-times fa-2x tweet-close"
55. aria-hidden="true"
56. onClick={this.handleClose.bind(this)}/>
57. <Tweet tweet={this.state} detail={true} />

297 | Página
58. <div className="tweet-details-reply">
59. <Reply profile={this.state._creator}
60. operations={{addNewTweet: this.addNewTweet.bind(this)}}
61. key={"detail-" + this.state._id} newReply={false}/>
62. </div>
63. <ul className="tweet-detail-responses">
64. <If condition={this.state.replysTweets != null} >
65. <For each="reply" of={this.state.replysTweets}>
66. <li className="tweet-details-reply" key={reply._id}>
67. <Tweet tweet={reply} detail={true}/>
68. </li>
69. </For>
70. </If>
71. </ul>
72. </div>
73. </Otherwise>
74. </Choose>
75. </>
76. )
77. }
78. }
79. export default TweetDetail

Para comprender mejor lo que está pasando, vamos a explicarlo en el orden en


que suceden los eventos, en decir, siguiendo el ciclo de vida, por ello, lo primero
en ejecutarse es el método render, en el cual determinamos si el Tweet a mostrar
ya ha sido cargado, de lo contrario mostramos un indicador de “cargando” (línea
47).

Una vez que el componente es montado, el método componentWillMount se


ejecuta, iniciando la cargar del detalle del tweet desde el API REST (línea 12).
Para esto, requerirá el id del tweet como un URL Param que recuperamos en la
línea (11).

Documentación: Consultar el detalle de un Tweet

Este servicio consulta un solo Tweet por medio del ID


y retorna el Tweet solicitado junto con todas las
respuestas (/tweetDetails/:tweetId)

Una vez que el Tweet es cargado y el estado actualizado, el método render es


ejecutado nuevamente, con la diferencia de que esta vez ya tenemos el detalle
que mostrar, por lo que se muestra la información del tweet (línea 52 en
adelante).

Para mostrar el tweet principal, utilizamos el componente Tweet (línea 57), al


cual se le manda como parámetro el tweet que acabos de cargar desde el API y
una bandera llamada detail que le servirá al componente para visualizarse mejor
cuando sea visualizado desde un modal.

Justo debajo del tweet podemos ver el componente Reply (línea 59), que
utilizaremos para que los usuarios puedan contestar el tweet.

Página | 298
Como parte de la respuesta del tweet, tenemos el detalle, es decir, todos los
comentarios que han dejado los demás usuarios en el tweet, por lo que cada
respuesta es tratada como un tweet más. En la línea 65 podemos ver que
iteramos las respuestas para representarlas como un componente tweet (línea
67).

En este punto el componente ya se ha renderizado por completo, por lo que ya


solo nos queda analizar los métodos que le dan interacción al componente. En
primer lugar tenemos el método addNewTweet, el cual pasamos como parámetro
al componente Reply, y será ejecutado cuando un usuario deje un comentario.
Este método manda llamar al API para guardar la respuesta y luego agregar la
respuesta a la vista.

Finalmente el método handleClose se ejecutará cuando el usuario cierre la


ventana modal. Donde agregaremos ciertos estilos al navegador (línea 39) y
luego navegar hacia atrás en el historial del navegador.

En este punto no podemos visualizar aun el componente pues todavía falta


implementar el Portal, el cual implementaremos en la siguiente sección.

El componente Modal

Para visualizar el componente TweetDetails hace falta implementar el Portal, el


cual es una característica de React para crear componentes hijos en elementos
DOM que están fuera de la jerarquía, y eso es lo que veremos ahora.

Antes que nada, realizaremos algunos ajustes al archivo App.js para agregar un
div sobre el cual se mostraran las pantallas modales.

1. import React from 'react'


2. import { render } from 'react-dom'
3. import TwitterApp from './TwitterApp'
4. import { Router } from "react-router-dom";
5. import history from './History'
6.
7. render((
8. <Router history={history}>
9. <div id="dialog" />
10. <TwitterApp />
11. </Router>
12. ), document.getElementById('root'));

Observa que hemos agregado el div de la línea 9, el cual tiene un id=dialog,


este id es importante, porque más adelante lo necesitaremos.

299 | Página
El siguiente paso será crear el archivo Modal.js en el path /app:

1. import React from 'react'


2. import { createPortal } from 'react-dom'
3.
4. class Modal extends React.Component {
5. constructor(props) {
6. super(props)
7. this.dialog = document.getElementById('dialog');
8. this.el = document.createElement('div');
9. }
10.
11. componentDidMount() {
12. this.dialog.appendChild(this.el);
13. }
14.
15. componentWillUnmount() {
16. this.dialog.removeChild(this.el);
17. }
18.
19. render() {
20. return createPortal(
21. <div name="portal" className="fullscreen">
22. {this.props.children}
23. </div>,
24. this.el
25. )
26. }
27. }
28. export default Modal

Este componente será el encargado de crear el Portal y luego montarlo sobre el


div que creamos hace un momento, para esto, en el constructo se hace una
referencia al div dialog (línea 7) y se asocia a la propiedad this.dialog, luego,
creamos un nuevo div (línea 8).

En el método render creamos un Portal mediante el método createPortal


provisto por la librería react-dom, al cual le indicamos que el componente que
debe de mostrar será todos aquellos elementos que le mandemos como hijos
(línea 22) y le decimos que el contenedor de este Portal será el div que creamos
en la línea 8 del constructor. Finalmente el portal es retornado por la función
render.

Cuando el método render termina, habrá una inconsistencia en entre el Virtual


DOM y el DOM del navegador, pues el Portal no creará elementos visibles en el
navegador, sino más bien, solo creará una asociación a nivel del Virtual DOM, es
por ello que en el método componentDidMount forzamos para que el div que
creamos en el constructor sea agregado al DOM (línea 12) y finalmente, hacemos
lo inverso cuando el componente es desmontado (línea 16).

Los portales pueden llegar a ser confusos, pues rompen la forma de trabajar de
React para permitir estructuras no jerárquicas a nivel del DOM, pero estoy seguro
que si te detienes un momento a analizar lo que está pasando podrás
comprenderlo mejor.

Página | 300
El siguiente paso será utilizar el componente Modal y TwitterDetail, para esto
tendremos que modificar el componente UserPage para agregar una nueva Route:

1. <div className="col-xs-12 col-sm-8 col-md-7


2. col-md-push-1 col-lg-7">
3. <Switch>
4. <Route exact path="/:user" component={
5. () => <MyTweets profile={this.state.profile} />} />
6. <Route exact path="/:user/followers" component={
7. () => <Followers profile={this.state.profile} />} />
8. <Route path="/:user/following" component={
9. () => <Followings profile={this.state.profile} />} />
10. <Route exact path="/:user/tweet/:tweet" component={
11. (params) => <Modal> <TweetDetail {...params} /> </Modal>} />
12. </Switch>
13. </div>

Las líneas 10 y 11 son importantes, porque son claves para entender cómo es
que el Portal se crea, para empezar, puedes ver que el componente TweetDetail
se crea dentro del componente Modal, por lo tanto, TweetDetail es un hijo de
Modal, es por ello que en Modal utilizamos la instrucción this.props.children.
this.props.children es una instrucción especial de React para hacer referencia
a los elementos hijos de un componente, por lo tanto, cuando desde Modal la
utilizamos, estamos obteniendo la referencia a TweetDetail. Finalmente, puedes
ver que le pasamos unos argumentos mediante {…params}, con esto, lo que
hacemos es pasarle los parámetros de React Router, para que tenga acceso a los
Path params.

También agregaremos los imports correspondientes:

1. import TweetDetail from './TweetDetail'


2. import Modal from './Modal'

Finalmente agregaremos algunos estilos adicionales a nuestro archivo


styles.css:

1. .fullscreen{
2. position: fixed;
3. background-color: rgba(0,0,0,0.5);
4. top: 0;
5. right: 0;
6. left: 0;
7. height: auto;
8. z-index: 999999;
9. overflow: auto;
10. bottom: 0;
11. }

301 | Página
El estilo anterior es utilizado para crear un elemento que cubra por completo la
pantalla con un color negro transparente, de tal forma que permita ver el fondo
pero sin poder interactuar con él.

Por otra parte, agregaremos los estilos:

1. html.modal-mode{
2. overflow-y: hidden;
3. }
4.
5. .fullscreen{
6. position: fixed;
7. background-color: rgba(0,0,0,0.5);
8. top: 0;
9. right: 0;
10. left: 0;
11. height: auto;
12. z-index: 999999;
13. overflow: auto;
14. bottom: 0;
15. }
16.
17. .fullscreen .tweet-detail{
18. max-width: 700px;
19. overflow: hidden;
20. border-radius: 5px;
21. margin: auto;
22. margin-top: 50px;
23. margin-bottom: 100px;
24. width: 60%;
25. background-color: #fff;
26. }
27.
28. .fullscreen .tweet-detail .tweet-close{
29. position: absolute;
30. display: inline-block;
31. right: 15px;
32. top: 10px;
33. }
34.
35. .fullscreen .tweet-detail .tweet-close:hover{
36. cursor: pointer;
37. }
38.
39. .tweet-detail .tweet-detail-responses{
40. list-style: none;
41. margin: 0px;
42. padding: 0px;
43. }
44.
45. .tweet-detail .tweet-details-reply{
46. border-bottom: 1px solid #E6ECF0;
47. }

El último ajuste que nos falta para terminar será sobre el componente Tweet, al
cual le agregaremos el evento de onClick para que nos lleve al detalle al
momento de darle click:

1. render(){

Página | 302
2. let tweetClass = null
3. if(this.props.detail){
4. tweetClass = 'tweet detail'
5. }else{
6. tweetClass = this.state.isNew ? 'tweet fadeIn animated' : 'tweet'
7. }
8.
9. return (
10. <article className={tweetClass} onClick={this.props.detail ? '' :
11. this.handleClick.bind(this)} id={"tweet-" + this.state._id}>
12. <img src={this.state._creator.avatar} className="tweet-avatar" />
13. <div className="tweet-body">
14. <div className="tweet-user">

Y agregamos el método handleClick:

1. handleClick(e){
2. if(e.target.getAttribute("data-ignore-onclick")){
3. return
4. }
5. let url = "/" + this.state._creator.userName + "/" + this.state._id
6. browserHistory.push(url)
7. }

No olvidemos el import correspondiente al objeto BrowserHistory:

1. import { browserHistory } from 'react-router'

Si seguimos correctamente todos los pasos, ya podríamos ir a nuestra aplicación


y dar click sobre el tweet para ver su detalle, o incluso, dar click en el botón de
responder (flecha azul) para que salga el detalle del tweet en forma modal.

Una última cosa interesante es, analizar el inspector de elementos del navegador,
para ver que el modal se encuentra fuera de la jerarquía:

303 | Página
Observa que el componente TwitterApp se monta con el id=mainApp, y es un
hermando del nodo dialog.

Por otra parte, si nos vamos al debuger de React, podremos ver la jerarquía de
otra forma, donde Modal es hijo de UserPage:

Observa que para React, Modal sí es un componente hijo, aunque en el inspector


de elementos, vemos que el DOM nos cuenta otra historia, pues en realidad nos
dice que es hermano de TwitterApp.

Página | 304
Últimos retoques al proyecto

Implementando la funcionalidad de Like

Otra funcionalidad que no implementamos, es darle like a los tweets, por lo que
si quieres terminar de implementar esta funcionalidad solo falta hacer los
siguientes ajustes al componente Tweet.

1. <a className={this.state.liked ? 'like-icon liked' : 'like-icon'}


2. onClick={this.handleLike.bind(this)} data-ignore-onclick>
3. data-ignore-onclick>
4. <i className="fa fa-heart " aria-hidden="true"
5. data-ignore-onclick></i> {this.state.likeCounter}
6. </a>

Al enlace del botón de like (corazón) le agregaremos la llamada a la función


handleLike.

1. handleLike(e){
2. e.preventDefault()
3. let request = {
4. tweetID: this.state._id,
5. like: !this.state.liked
6. }
7.
8. APIInvoker.invokePOST('/secure/like', request, response => {
9. let newState = update(this.state,{
10. likeCounter : {$set: response.body.likeCounter},
11. liked: {$apply: (x) => {return !x}}
12. })
13. this.setState(newState)
14. },error => {
15. console.log("Error al cargar los Tweets", error);
16. })
17. }

La función handleLike se encarga de llamar al API para registrar el like al Tweet


mediante el servicio /secure/like, el cual se ejecuta mediante el método POST y
recibe como parámetro el ID del tweet y un booleano que indica si es un Like o
Dislike.

No olvidemos el import de la función update:

1. import update from 'immutability-helper'

Documentación: Like/Dislike

Este servicio se utiliza para darle like a un Tweet, o en


su caso, retirar el like (/secure/like).

305 | Página
Página | 306
Resumen

En este punto deberíamos estar muy contentos pues hemos terminado nuestro
proyecto Mini Twitter en su totalidad. Después de un gran trabajo se ve
recompensado nuestro esfuerzo, pues hemos terminado de principio a fin un
proyecto completo y en lo personal yo diría que no es NADA amateur, pues ha
sido una aplicación completa que ha aplicado absolutamente todo lo que hemos
aprendido en el libro.

Si en este punto del libro eres capaz de comprender todo lo que hemos aprendido,
seguramente ya estás listo para enfrentarte a un proyecto real. Pues cuentas con
todas las bases y el conocimiento requerido para desarrollar una aplicación
estándar.

Si bien, en este punto hemos terminado todo el proyecto, todavía existen mejoras
que implementar, como es el caso de agregar Hooks, Context y Redux, los cuales
los estaremos analizando más adelante.

307 | Página
Context
Capítulo 12

Uno de los principales problemas de trabajar con React, es que para poder llevar
datos globales de la aplicación a todos los componentes, es necesario replicar
estos valores por toda la estructura de la aplicación mediante props, de tal forma
que los componentes padres deben de replicar estos valores a sus hijos y así
sucesivamente.

Para analizar mejor esta problemática, analicemos la imagen anterior, donde se


puede ver que el componente A sabe cuál es el usuario autenticado en la
aplicación, luego, este debe de ser propagado a diversos componentes en la
estructura. El problema con esto es que, absolutamente todos los componentes
en la jerarquía deben recibir y a su vez propagar el usuario a sus hijos, ya que
de lo contrario, no habrá forma de tener una referencia a él.

Otro de los problemas es que muchas veces estas propiedades no son requeridas
por todos los componentes, pero solo el hecho de que alguno de sus
descendientes lo necesite, implica modificar toda la cadena de componentes
hacia arriba para recibir y replicar los valores globales.

Para solucionar este tipo de problemas, tenemos el Context, un nuevo API


agregado a partir de la versión 16.3.0, la cual nos permite pasar datos a través
del árbol de componentes sin tener que pasar props manualmente en cada nivel.

Página | 308
Si bien, el Context nos permite compartir cualquier datos a toda la aplicación, no
fue diseñado para utilizarse arbitrariamente para pasar lo que sea, en su lugar,
se utiliza únicamente para compartir datos que se consideren “globales”, es decir,
datos que pueden ser necesario en varios componentes de la aplicación sin
importar el nivel en la jerarquía en que se encuentre, como puede ser el usuario
autenticado, el tema, idioma o incluso formatos de fecha o moneda predefinidos
por el usuario.

createContext

El primer paso para utilizar el Context es crear uno, para esto, React proporciona
el método createContext, que recibe como parámetro el valor por default del
dato global o que queremos compartir en toda la aplicación. Veamos un ejemplo
de cómo sería:

1. import React from 'react'


2.
3. const context = React.createContext( {
4. val1: "val1",
5. val2: "val2"
6. } )
7.
8. export default context

Obseva que la función createContext puede recibir un valor, el cual pertenece al


valor por default del contexto al momento de iniciarse, sin embargo, en muchas
ocasiones no es necesario mandarle nada, pues no podemos definir un valor por
default hasta no ir al API para recupera los datos, en estos casos, simplemente
podemos crear el contexto sin un valor por default.

Algo importante a tomar en cuenta es que, el contexto deberá ser creado como
un archivo independiente, pues eso permite que lo podamos referencias desde
cualquier parte, lo que hace que sea independiente de la estructura de
componentes.

309 | Página
Podríamos decir que en este punto estamos como en la imagen anterior, donde
tenemos un contexto (azul) independiente de la estructura y con un valor inicial
por default, sin embargo, podemos ver que el contexto aún está aislado de la
aplicación.

Una aplicación puede tener más de un Context, donde cada uno puede guardar
valores globales independientes, por lo que podemos crear diferentes Context
con createContext.

Provider

Lo siguiente por implementar es el Provider, un componente que viene con cada


Context y que permite que los componentes se suscriban a los cambios en el
Context, permitiendo detectar los cambios y renderizarse nuevamente para
reflejar los cambios.

El componente Provider viene por defecto en cada Context, por lo que para
crearlo, lo hacemos atreves de la referencia del contexto que creamos
previamente, y recibirá como parámetro la propiedad value, la cual se utiliza para
establecer el valor actual del Context. Veamos un ejemplo:

1. import React from 'react'


2. import Context from './context/Context'
3.
4. class MyComponent extends React.Component {
5.

Página | 310
6. constructor(props) {
7. super(props)
8. this.state = {
9. ...
10. }
11. }
12.
13. componentDidMount() {
14. ...
15. setState({
16. //new state
17. })
18. }
19.
20. render() {
21. return (
22. <Context.Provider value={this.state}>
23. <MyApp />
24. </Context.Provider>
25. )
26. }
27. }
28. export default MyComponent

Lo primero que tenemos que observar es que para crear el Provider, es necesario
importar el Contexto (línea 2) que creamos en la sección pasada con el método
createContext, una vez con la referencia, podemos ver que creamos el Provider
por medio de la referencia al Context (línea 22).

Lo segundo interesante es la propiedad value, la cual la utilizamos para indicarle


al Provider el nuevo valor del Context, en este caso, observa que le asignamos
el valor actual del estado. Algo importante a considerar es que al momento de
establecer el value, estamos sobrescribiendo el valor por default que
establecimos al momento de crear el contexto con createContext.

311 | Página
En este punto podemos decir que estamos como en la imagen anterior, donde el
Context ya está creado y estamos sincronizando el valor del Context por medio
del estado del componente. De esta forma, cada vez que cambie el estado del
componente, el Provider tomará este nuevo estado como el nuevo valor del
contexto.

Hay que tomar en cuenta que, así como podemos tener múltiples Context,
podemos tener múltiples Providers, por lo que solo habría que anidarlos de la
siguiente manera:

1. import React from 'react'


2. import UserContext from './context/UserContext'
3. import ThemeContext from './context/ThemeContext'
4.
5. class MyComponent extends React.Component {
6.
7. render() {
8. return (
9. <UserContext.Provider value={this.state.user}>
10. <ThemeContext.Provider value={this.state.theme}>
11. <MyApp />
12. </ThemeContext.Provider>
13. </UserContext.Provider>
14. )
15. }
16. }
17. export default MyComponent

Observa que cada Provider se crear por medio de la referencia al Context


correspondiente, además, cada uno puede definir el valor de la propiedad value
de diferentes fuentes.

Consumer
El último paso es crear el Consumer, el cual es un componente provisto también
por el Context, que permite suscribirse a los cambios del Context.

Como regla, los Consumer deberán ser descendientes en cualquier nivel del
Provider, ya que será este último quien notifique los cambios del contexto al
Consumer.

Para crear un Consumer también deberemos utilizar la referencia al Context y


deberá de tener como hijo una función:

1. import React from 'react'


2. import Context from './context/Context'
3.
4. class MyChildComponent extends React.Component {
5.

Página | 312
6. render() {
7. return (
8. <Context.Consumer>
9. {context =>
10. <p>{context.value}</p>
11. }
12. </Context.Consumer>
13. )
14. }
15. }
16. export default MyChildComponent

La sintaxis del Consumer puede llegar a ser un poco confusa la primera vez, pero
verás que ahora que la expliquemos será más fácil. Lo primero que tenemos que
ver es que el Consumer se crear por medio del Context y no requiere de ninguna
propiedad. En segundo lugar, podrás ver que el Consumer no recibe un
componente directamente como hijo, sino más bien un arrow function, el cual
recibe como parámetro el valor actual del context, además deberá de retornar
un elemento renderizable.

El arrow function que recibe el Consumer será ejecutado cada vez que el Provider
actualice el valor del contexto mediante la propiedad value, de esta forma, todos
los componentes se podrán actualizar con el nuevo valor del Context, pero con
la única diferencia de que cuando el Contexto es actualizado, el método
shouldComponentUpdate es ignorado.

Finalmente, cabe mencionar que de la misma forma que podemos tener múltiples
Context y Providers, también podemos anidar múltiples Consumers para
recuperar el valor de múltiples Contexts. Veamos un ejemplo:

1. import React from 'react'


2. import UserContext from './context/UserContext'
3. import ThemeContext from './context/ThemeContext'
4.
5. class MyComponent extends React.Component {
6.
7. render() {
8. return (
9. <UserContext.Consumer>
10. {user =>
11. <ThemeContext.Consumer>
12. {theme =>
13. <p>{user.name}</p>
14. <p>{theme.color}</p>
15. }
16. </ThemeContext.Consumer value={}>
17. }
18. </UserContext.Consumer>
19. )
20. }
21. }
22. export default MyComponent

313 | Página
En este punto, se podrá decir que estamos como en la imagen siguiente:

En esta nueva imagen podemos ver que todo el ciclo se cierra, pues ya tenemos
el Context creado, el Provider está siendo actualizado y los componentes hijos
están recibiendo las actualizaciones del contexto por medio de los Consumers.

contextType

Hace un momento veíamos como suscribirnos al contexto mediante el


componente Consumer, pero también nos dimos cuenta que la sintaxis es algo
rebuscada, por tal motivo, React ofrece una segunda forma de obtener el
contexto con una sintaxis más simple, que es mediante contextType.

Antes de continuar, debo de resaltar dos limitantes de este nuevo método, y es


que solo funciona en compontes de clase, es decir que extienden de
React.Component y la segunda es que solo nos permite utilizar un Context a la
vez, de tal forma que si nuestro componente es de función (sin estado) o
requieres utilizar más de un contexto a la vez, no podremos utilizarlo.

Para implementar contextType veamos el siguiente ejemplo:

1. import React from 'react'


2. import Context from './context/Context'
3.
4. class MyComponent extends React.Component {
5.
6. render() {
7. return (

Página | 314
8. <p>{this.context.name}</p>
9. )
10. }
11. }
12.
13. MyComponent.contextType = Context
14.
15. export default MyComponent

Analicemos las partes de este componente. Primero que nada, tendremos que
importar el Context como ya lo veníamos haciendo, luego, tendremos que asignar
el contexto a la propiedad contextType de la clase (línea 13). Observa que esta
asignación se hace a nivel de clase y fuera de la declaración de la misma. Esto
hace que el contexto esté disponible en la propiedad this.context, tal y como
podemos ver que lo utilizamos en la línea 8.

Mini Twitter (Continuación 6)

En esa sección vamos a implementar los conocimientos que acabos de adquirir


sobre el API Context para mejorar aún más nuestro proyecto.

Creado el UserContext

El único dato que podríamos considerar global en nuestro proyecto de Mini Twitter
es el usuario, y es utilizado en varios componentes de la aplicación, por lo que
tenerlo en Context nos podría ayudar mucho, facilitando su acceso a todos los
componentes de la aplicación, por tal motivo, comenzaros creando el archivo
UserContext.js en el path /app/context, el cual se verá de la siguiente forma:

1. import React from 'react'


2.
3. const userContext = React.createContext()
4.
5. export default userContext

Podrás notar que hemos inicializado en Context sin ningún dato por default, ya
que o sabes cuál es el usuario por medio del API o no lo tenemos, por lo que no
podemos tener un dato por default.

Implementando muestro primer Provider

315 | Página
Una vez creado el Context, seguirá crear un Provider, al cual nos podremos
suscribir para recibir los cambios en el Context, por ello, deberemos modificar el
archivo TwitterApp.js, ya que es el punto desde el cual obtenemos el usuario
autenticado por medio del API REST:

1. import UserContext from './context/UserContext'


2. ...
3.
4. class TwitterApp extends React.Component {
5.
6. ...
7.
8. render() {
9. if (!this.state.load) {
10. return null
11. }
12.
13. return (
14. <UserContext.Provider value={this.state.profile}>
15. <Toolbar profile={this.state.profile} />
16. <div id="mainApp" className="animated fadeIn">
17. <Switch>
18. <Route exact path="/" component={TwitterDashboard} />
19. <Route exact path="/signup" component={Signup} />
20. <Route exact path="/login" component={Login} />
21. <Route path="/:user" component={UserPage} />
22. </Switch>
23. </div>
24. </UserContext.Provider>
25. )
26. }
27. }
28. export default TwitterApp;

Primero que nada, tendremos que importar el UserContext (línea 1), luego,
tendremos que englobar toda la aplicación con el Provider que creamos a partir
del UserContext (línea 14), finalmente, utilizamos el state del componente para
definir la propiedad value del Provider. Recordemos que el Perfil del usuario es
cargado en el método componentDidMount y el resultado guardado en el estado
bajo la propiedad profile.

Quiero que observar que hemos modificado el Route para el componente


TwitterDashboard (línea 18), pues al tener un Context, ya no hace falta que le
mandemos el profile como prop.

Finalmente, hemos removido la propiedad profile del Toolbar (línea 15), ya que
ahora en adelante, obtendrá el usuario por medio del Context.

Implementando nuestros Consumers

El primer componente que conectaremos al Context será TwitterDashboard, el


cual dejaremos de la siguiente manera:

Página | 316
1. import React from 'react'
2. import Profile from './Profile'
3. import TweetsContainer from './TweetsContainer'
4. import SuggestedUser from './SuggestedUser'
5. import PropTypes from 'prop-types'
6. import UserContext from './context/UserContext'
7.
8. const TwitterDashboard = (props) => {
9.
10. return (
11. <UserContext.Consumer>
12. {context => <div id="dashboard" className="animated fadeIn">
13. <div className="container-fluid">
14. <div className="row">
15. <div className="hidden-xs col-sm-4 col-md-push-1
16. col-md-3 col-lg-push-1 col-lg-3" >
17. <Profile profile={context} />
18. </div>
19. <div className="col-xs-12 col-sm-8 col-md-push-1
20. col-md-7 col-lg-push-1 col-lg-4">
21. <TweetsContainer profile={context} />
22. </div>
23. <div className="hidden-xs hidden-sm hidden-md
24. col-lg-push-1 col-lg-3">
25. <SuggestedUser />
26. </div>
27. </div>
28. </div>
29. </div>}
30. </UserContext.Consumer>
31. )
32. }
33.
34. export default TwitterDashboard

Lo primero es importar el UserContext (línea 6), y luego utilizarlo para crear el


Consumer (línea 12). El siguiente paso será crear un arrow function y dentro de
él, retornar el resto del componte.

También podrás ver que hemos cambiado la fuente de donde tomamos el usuario
para enviárselo al componente Profile (línea 17) y TweetsContainer (línea 21),
ya que antes replicábamos el usuario que llegaba como prop y ahora lo
obtenemos del Context.

Aquí hay un punto importante que me gustaría recalcar, y es que podrás ver que
el componente TwitterDashboard en realidad no utiliza para el nada el Context,
más bien solo lo recupera para luego pasarlo al componente Profile y
TweetsContainer, porque quizás sería mejor que estos dos componentes
recuperaran el contexto por sí mismo, sin embargo, una de las mejores prácticas
para hacer componentes reutilizables es, hacer los componentes reutilizables
independientes de la ubicación de los datos, de esta forma, si el día de mañana
quieres mostrar un usuario distinto al perfil autenticado solo basta con marlo
como prop, del mismo modo, si quieres mostrar los tweets de un usuario
diferente, solo se lo mandas, es por ello que hemos decidido hacerlo de esta
manera, sin embargo, siente libre de agregar directamente el Context sobre estos
dos componentes si lo crees más conveniente.

317 | Página
El Toolbar.js será el siguiente archivo a modificar, el cual requiere del usuario
para ver nuestra foto de perfil o para llevarnos a la página de nuestro perfil:

1. import React from 'react'


2. import { Link, NavLink } from 'react-router-dom'
3. import PropTypes from 'prop-types'
4. import UserContext from './context/UserContext'
5.
6. const Toolbar = (props) => {
7.
8. const logout = (e) => {
9. e.preventDefault()
10. window.localStorage.removeItem("token")
11. window.localStorage.removeItem("username")
12. window.location = '/login';
13. }
14.
15. return (
16. <UserContext.Consumer>
17. {context => <nav className="navbar navbar-default
18. navbar-fixed-top">
19. <span className="visible-xs bs-test">XS</span>
20. <span className="visible-sm bs-test">SM</span>
21. <span className="visible-md bs-test">MD</span>
22. <span className="visible-lg bs-test">LG</span>
23.
24. <div className="container-fluid">
25. <div className="container-fluid">
26. <div className="navbar-header">
27. <Link className="navbar-brand" to="/">
28. <i className="fa fa-twitter"
29. aria-hidden="true"></i>
30. </Link>
31. <ul id="menu">
32. <li id="tbHome" className="selected">
33. <NavLink to="/" activeClassName="selected">
34. <p className="menu-item">
35. <i className="fa fa-home
36. menu-item-icon"
37. aria-hidden="true" />
38. <span className="hidden-xs
39. hidden-sm">
40. Inicio</span>
41. </p>
42. </NavLink>
43. </li>
44. </ul>
45. </div>
46. <If condition={context != null} >
47. <ul className="nav navbar-nav navbar-right">
48. <li className="dropdown">
49. <a href="#" className="dropdown-toggle"
50. data-toggle="dropdown" role="button"
51. aria-haspopup="true"
52. aria-expanded="false">
53. <img className="navbar-avatar"
54. src={context.avatar}
55. alt={context.userName} />
56. </a>
57. <ul className="dropdown-menu">
58. <li>
59. <Link to={`/${context.userName}`}>
60. Ver perfil</Link>

Página | 318
61. </li>
62. <li role="separator" className="divider">
63. </li>
64. <li>
65. <Link to="#" onClick={logout}>
66. Cerrar sesión</Link>
67. </li>
68. </ul>
69. </li>
70. </ul>
71. </If>
72. </div>
73. </div>
74. </nav>}
75. </UserContext.Consumer>
76. )
77. }
78.
79. Toolbar.propTypes = {
80. profile: PropTypes.object
81. }
82.
83. export default Toolbar

Como siempre, importamos el UserContext (línea 4), lo utilizamos para crear el


Consumer (línea 16), y retornamos el resto del componente por medio del arrow
function. Finalmente reemplazamos todas las referencias a la propieda profile
por la variable context.

Debido a que el Toolbar ya no recibirá la propiedad profile, ya no será necesario


el PropTypes, así que eliminamos toda la sección (línea 79 en adelante).

El último cambio que haremos será sobre el componente Login, el cual lo hago
más que nada para demostrar cómo utilizar el contextType más que para otra
cosa. Lo que vamos a hacer es que si un usuario autenticado intenta entrar a la
página /login, sea automáticamente redireccionado al home (/), pues como ya
está autenticado, no tiene sentido que llegue al login.

1. import UserContext from './context/UserContext'


2. import {Redirect} from 'react-router-dom'
3. ...
4.
5. class Login extends React.Component{
6.
7. ...
8.
9. render(){
10.
11. if(this.context != null){
12. return <Redirect to="/"/>
13. }
14.
15. return(
16. <div id="signup">
17. <div className="container" >
18. <div className="row">
19. <div className="col-xs-12">
20. </div>

319 | Página
21. </div>
22. </div>
23. <div className="signup-form">
24. <form onSubmit={this.login.bind(this)}>
25. <h1>Iniciar sesión en Twitter</h1>
26.
27. <input type="text" value={this.state.username}
28. placeholder="usuario" name="username" id="username"
29. onChange={this.handleInput.bind(this)}/>
30. <label ref="usernameLabel" id="usernameLabel"
31. htmlFor="username"></label>
32.
33. <input type="password" id="passwordLabel"
34. value={this.state.password} placeholder="Contraseña"
35. name="password" onChange={this.handleInput.bind(this)}/>
36. <label ref="passwordLabel" htmlFor="passwordLabel"></label>
37.
38. <button className="btn btn-primary btn-lg " id="submitBtn"
39. onClick={this.login.bind(this)}>Regístrate</button>
40. <label ref="submitBtnLabel" id="submitBtnLabel"
41. htmlFor="submitBtn"className="shake animated hidden "></label>
42. <p className="bg-danger user-test">Crea un usuario o usa el usuario
43. <strong>test/test</strong></p>
44. <p¿No tienes una cuenta? <Link to="/signup">Registrate</Link> </p>
45. </form>
46. </div>
47. </div>
48. )
49. }
50. }
51.
52. Login.contextType = UserContext
53.
54. export default Login

Lo primero será importar el UserContext y el componente Redirect de React


Router (líneas 1 y 2).

El siguiente paso será establecer el contexto mediante la propiedad contextType


(línea 52), lo que hará que el contexto esté automáticamente disponible en
this.context.

Finalmente, en la línea 11 validamos si el contexto es diferente de null, lo que


indicaría que el usuario ya está autenticado, por lo que retornamos el
componente Redirect para enviar al usuario de regreso al home.

Página | 320
Conclusiones

En esta unidad hemos descubierto el poder del contexto, el cual nos permite
guardar y acceder a toda aquella información que consideramos global dentro de
la aplicación, evitando tener que pasar como propiedad los datos por toda la
estructura.

Algo a tomar en cuenta cuando utilizamos el contexto, es que no debemos de


utilizarlos para pasar datos de un componente a otro solo porque sí, debemos
reservarlo únicamente para aquella información que es considerada global.

Si lo que queremos es pasar información común entre diferentes componentes,


puedes seguir utilizando las propiedades, o podías utilizar Redux, una librería
para administrar el estado de la aplicación, sin embargo este lo analizaremos un
poco más adelante.

321 | Página
Hooks
Capítulo 13

Los hooks es una de las características más aplaudida por la comunidad de React,
pues ha venido a revolucionar por completo la forma en que construimos
componentes, al mismo tiempo que ha corregido ciertos problemas que React
arrastraba desde su lanzamiento, el cual tenía una sintaxis con poco confusa y
un ciclo de vida algo complejo.

Si no dirigimos a la documentación oficial de React, podremos ver que los hooks


con:

Nuevo concepto: Hooks

Hooks son una nueva característica en React 16.8.


Estos te permiten usar el estado y otras características
de React sin escribir una clase.

Si bien, esta descripción es correcta, la realidad es que es imprecisa, pues es una


descripción de marketing para que todos podamos entender de forma simple
que son los hooks, sin embargo, los hooks vienen a resolver problemas más
profundos que solo poder agregar estado a un componente de función.

Para comprender los hooks hace falta entender la historia de React y como este
ha venido evolucionando para adaptarse a las nuevas versiones de ECMAScript
(especificación sobre la cual está construido JavaScript).

Si investigamos un poco, te darás cuenta que la primera versión de React fue


liberada el 29 de mayo del 2013 y en aquel entonces JavaScript estaba apenas
en la versión de ECMAScript 5, lo que implicaba que no existían las clases de
forma nativa en el lenguaje, lo que obligaba a crear clases mediante el método
createClass provisto por React:

1. import React from 'react';


2.
3. const MyComponent = React.createClass({
4. render() {
5. return (
6. <h1>Hola mundo</h1>
7. );

Página | 322
8. }
9. });
10.
11. export default Contacts;

En aquel entonces esto era revolucionario, pues permitía simular la creación de


clases sin que el lenguaje lo soportara. Sin embargo, el destino nos llevó a la
siguiente fecha importante para React, 17 de enero de 2015, ECMAScript 6 ya
era una realidad y con ello, JavaScript ya soportaba clases de forma nativa, por
lo que el equipo de React decidió que las clases nativas es el camino correcto,
por lo que el 27 de enero de 2015 se lanza la versión 0.13.0 beta 1 de React, la
cual cambiaba la forma de crear clases, migrando del método createClass a crear
clases nativas que extiendan de React.Component:

1. import React from 'react';


2.
3. class MyComponent extends React.Component{
4.
5. render() {
6. return (
7. <div></div>
8. )
9. }
10. }
11.
12. export default MyComponent

Este cambio sin duda fue un acierto en el rumbo de React, pues permitía explotar
las características nativas del lenguaje en lugar de solo simularlas, sin embargo,
algo inesperado pasó, y es que la comunidad rápidamente se comenzó a topar
con ciertos “problemas” al momento de desarrollar, los cuales describo a
continuación:

Constructor

Cuando la comunidad migra al esquema de clases nativas, se da cuenta que el


lenguaje nos obligar a llamar la instrucción super en la primera línea del
constructor:

1. import React from 'react';


2.
3. class MyComponent extends React.Component{
4. constructor(args){
5. super(args) //🤮
6. }
7. }
8.
9. export default MyComponent

323 | Página
Autobinding

Uno de los problemas que más se reportaban con frecuencia entre la comunidad
fue referente a como referenciar a métodos dentro de un componente de clase,
pues teníamos siempre que utilizar la instrucción this, y en algunos casos,
this.xxx.bind(this), tal como podemos ver en el siguiente ejemplo:

1. import React from 'react';


2.
3. class MyComponent extends React.Component{
4. constructor(args){
5. super(args) //🤮
6. this.state = 0
7. }
8.
9. click(){
10. this.setState(this.state + 1 ) //🤮
11. }
12.
13. render(){
14. return(
15. <button onClick={this.click.bind(this)} //🤮
16. )
17. }
18. }
19.
20. export default MyComponent

Ciclo de vida

El siguiente problema que se presentaba con regularidad entre la comunidad era


el complejo ciclo de vida de React, pues se tenían que hacer acciones antes de
montar, después de montar, antes de actualizar, después de actualizar, indicarle
si necesitábamos una actualización, acciones de desmontaje, etc. El ciclo de vida
tan complejo, hacía que sobre todos los nuevos que se adentraban a React
tuvieran una curva de aprendizaje mucho más pronunciada, incluso hay
programadores con experiencia que todavía no logran comprender al 100% el
ciclo de vida, lo que los lleva a varios errores en tiempo de ejecución y muchos
errores de performance, pues hacen operaciones de más, fuera de sitio o incluso,
ciclan el ciclo de vida. Pero el problema más destacado de todos es la lógica
duplicada para controlar el montaje y la actualización del componente:

1. import React from 'react';


2.
3. class MyComponent extends React.Component{
4. constructor(args){
5. super(args) //🤮
6. this.state = 0
7. }
8.
9. click(){
10. this.setState(this.state + 1 ) //🤮
11. }
12.
13. componentDidMount(){
14. let user = this.props.user
15. fetchUser(user) //🤮

Página | 324
16. }
17.
18. componentDidUpdate(prevProps) {
19. if (this.props.user !== prevProps.user) {
20. fetchUser(this.props.user) //🤮
21. }
22. }
23.
24. fetchUser(user){
25. let users = fetch(`/users/${user}`)
26. .then(res => res.json())
27. .then(res => this.setState(res))
28. }
29.
30. render(){
31. return(
32. <button onClick={this.click.bind(this)}
33. )
34. }
35. }
36.
37. export default MyComponent

En este nuevo caso se puedes ver cómo hay que separar la lógica para inicializar
el componente para poder luego ser llamada en el método componentDidMount y
componentDidUpdate.

El infierno de los envoltorios

Otro de los problemas que tiene React es que no tiene un mecanismo para
reutilizar la lógica no visual, lo que nos obliga a crear componentes que retornan
otro componente o lo que conocemos como high order component. Un ejemplo
claro de esto el Context como tal, en el cual necesitamos envolver un componente
dentro de otro y luego ese dentro de otro más y así susevimente, lo que hace
una sintaxis demasiado compleja:

1. import React from 'react'


2. import UserContext from './context/UserContext'
3. import ThemeContext from './context/ThemeContext'
4.
5. class MyComponent extends React.Component {
6.
7. render() {
8. return (
9. <UserContext.Consumer> //🤮
10. {user =>
11. <ThemeContext.Consumer> //🤮
12. {theme =>
13. <p>{user.name}</p>
14. <p>{theme.color}</p>
15. }
16. </ThemeContext.Consumer value={}>
17. }
18. </UserContext.Consumer>
19. )
20. }

325 | Página
21. }
22. export default MyComponent

O también se puede apreciar de la siguiente forma:

1. import React from 'react';


2.
3. class MyComponent extends React.Component{
4.
5. render(){
6. return(
7. <button onClick={this.click.bind(this)}
8. )
9. }
10. }
11. export default withTheme( //🤮
12. withUser( //🤮
13. withPreferences(MyComponent) //🤮
14. )
15. )

En resumen, podemos ver varios detallitos que si bien no son de gran


importancia, la realidad en que en su conjunto hace que el lenguaje sea difícil de
comprender para los inexpertos, además, la reutilización de la lógica no visible
sigue siendo uno de los problemas menos atendidos por los componentes de
clase.

Todo es que acabo de explicar lo explico con lujo en el video que dejo a
continuación: https://www.youtube.com/watch?v=beR07AOppkk

Introducción a los Hooks

Página | 326
Como ya lo mencionamos anteriormente, se dijo que los hooks permite agregar
estados a los componentes de función, sin embargo, ese es solo un comunicado
de Marketing para que la gente pueda entender rápidamente que es, pero como
también analizamos hace un momento, los componentes de clase tiene una serie
de inconvenientes que son resueltos con los hooks, entonces, podríamos decir
que los hooks son en realidad una nueva forma de escribir componentes que
mejora la composición y reutilización del código.

Estado

Sin lugar a duda, usar estados en componentes de función es la característica


que más nos han vendido, por lo que simplemente comenzaremos por allí.

Lo primero que tienes que saber sobre los Hooks es que absolutamente todo lo
relacionado con ellos aplican sobre componentes de función, los que cuales
anteriormente conocíamos como componentes sin estado (Stateless).

1. import React from 'react';


2.
3. function Example() {
4. // Declara una nueva variable de estado, que llamaremos "count".
5. const [count, setCount] = React.useState(0);
6.
7. return (
8. <div>
9. <p>You clicked {count} times</p>
10. <button onClick={() => setCount(count + 1)}>
11. Click me
12. </button>
13. </div>
14. );
15. }

El ejemplo que vemos arriba es el ejemplo más ampliamente usado para


demostrar el uso de los estados con los Hooks, y lo hemos obtenido directamente
de la documentación oficial de React.

Lo primero que veremos será el Hook useState, el cual nos permite definir un
estado para el componente. useState recibe como parámetro el valor inicial del
estado y retorna un array de dos posiciones, donde la primera posición
corresponde al valor actual del estado, mientras que la segunda posición
corresponde a una función para actualizar estado.

La línea 5 puede ser especialmente confusa, por que utiliza algo llamada
desestructuración, el cual consiste en declarar y asignar los valores del array
según su posición. Para entender mejor que está pasando veamos un ejemplo
totalmente equivalente al anterior:

1. import React, { useState } from 'react';


2.
3. function Example() {
4.
5. const state = useState(0)

327 | Página
6. const count = state[0]
7. const setCount = state[1]
8.
9. return (
10. <div>
11. <p>You clicked {count} times</p>
12. <button onClick={() => setCount(count + 1)}>
13. Click me
14. </button>
15. </div>
16. );
17. }

Observa como useState regresa un array y luego asignamos el valor de cada


posición en una variable diferente. Eso que vemos es exactamente lo que
evitamos hacer con la desestructuración, para evitar un código muy verboso.

Una vez comprendido lo anterior, es importante mencionar que la posición 1


corresponde al valor del estado y la segundo es una función para actualizar dicho
estado, sin embargo, el nombre de las constantes creadas es totalmente
arbitrario, ya que puedes tener algo parecido a lo siguiente y dará el mismo
resultado:

1. const [state, setState] = useState(0)


2.
3. Ó
4.
5. const [manzana, setManzana] = useState(0)
6.
7. Ó
8.
9. const [estado, establecerEstado] = useState(0)

Observa que el nombre que le pongamos a cada valor es totalmente irrelevante,


lo único realmente importante es que regresa un array de dos posiciones, donde
el primer valor es el valor del estado actual y el segundo es una función para
actualizar el estado.

Dicho lo anterior, regresemos al ejemplo original:

1. import React from 'react';


2.
3. function Example() {
4. // Declara una nueva variable de estado, que llamaremos "count".
5. const [count, setCount] = React.useState(0);
6.
7. return (
8. <div>
9. <p>You clicked {count} times</p>
10. <button onClick={() => setCount(count + 1)}>
11. Click me
12. </button>
13. </div>

Página | 328
14. );
15. }

Observa que el componente renderiza un botón que cuando le damos click hace
una llamada a la función setCount, a la cual le incrementamos el valor del
contador de clicks.

Lo primero que podemos observar es la ausencia de un constructor para


inicializar el valor del estado, pues de define desde el momento de crear el estado
con useState, en segundo lugar, ya no hace falta utilizar la instrucción this para
hacer referencia a la función para actualizar el estado.

Un detalle importante al momento de actualizar el estado mediante Hooks, es


que, el estado anterior no se mescla con el nuevo estado, por lo tanto, siempre
que actualicemos el estado deberemos enviarle el valor final con el que quedará.
Por ejemplo, vemos que pasa cuando actualizamos el estado con un componente
de clase:

1. // constructor
2. this.state = {
3. a: 1,
4. b: 2
5. }
6.
7. // componentDidMount
8. this.setState({
9. a: 5
10. })
11. console.log(this.state) // {a: 5, b: 2}

Observa que el estado se mescla para crear un nuevo estado de la unión del
anterior con el nuevo, sin embargo, en los Hooks no pasa esto. Veamos un
ejemplo:

1. const [state, setState] = React.useState({


2. a: 1,
3. b: 2
4. })
5.
6. // Any section
7. setState({
8. a: 5
9. })
10. console.log(state) // {a: 5}

Para realizar una correcta actualización del estado respetando los valores
anteriores podemos utilizar el operador de propagación (Spread operator), el cual
consiste en anteponer 3 puntos (…) al estado para extraer sus propiedades y
agregarlas al nuevo estado, veamos un ejemplo:

329 | Página
1. const [state, setState] = React.useState({
2. a: 1,
3. b: 2
4. })
5.
6.
7. setState({
8. ...state,
9. a: 5
10. })
11. console.log(state) // {a: 5, b: 2}

Este pequeño truco hace que todas las propiedades del estado actual pasan al
nuevo estado, y luego actualizo puntualmente la propiedad que queremos
actualizar.

Un último detalle interesante de los hooks es que permite agregar múltiples


estados:

1. import React from 'react';


2.
3. function Example() {
4. // Declara una nueva variable de estado, que llamaremos "count".
5. const [count, setCount] = React.useState(0) //😍
6. const [load, setLoad] = React.useState(false) //😍
7.
8. return (
9. <div>
10. <p>You clicked {count} times</p>
11. <button onClick={() => setCount(count + 1)}>
12. Click me
13. </button>
14. </div>
15. );
16. }

Observa que hemos creado un segundo estado que guarda una bandera para
saber si la página ha terminado de cargar, la cual tiene su propio método set.

Ciclo de vida

Otra de las limitantes de los compontes de función era que no tenía un ciclo de
vida, por lo tanto, solo funcionaban como componente de representación, es
decir, tomaban los props y luego renderizaban la vista a partir de ellos.

Con los Hooks eso cambia, pues podemos agregar algo llamado efectos
especiales o simplemente efectos (effects), los cuales son un tipo especial de
hooks que permite agregar comportamiento a los componentes de función. De
forma resumida, un efecto combina los métodos componentDidMount,

Página | 330
componentDidUpdate y componentWillUnmount en una sola API, lo que simplifica
significativamente el ciclo de vida. Veamos un ejemplo:

1. import React, { useState, useEffect } from 'react';


2.
3. const MyComponent = (props) => {
4.
5. const [user, setUser] = useState()
6.
7. // Similar a componentDidMount y componentDidUpdate:
8. useEffect( () => {
9. fetch(`/users/${props.user}`)
10. .then(res => res.json())
11. .then(res => setUser(res))
12. })
13.
14. return(
15. <p>{user.name}</p>
16. )
17. }
18. export default MyComponent

En la línea 8 podemos ver como se declara un effect, y podemos ver también


que, recibe como parámetro un arrow function, que es justo donde pondremos
nuestra lógica. Cabe mencionar que los effects se ejecutan justo después del
renderizado del componente, ya sea cuando se monta por primera vez o se
actualiza, es por ello que cuando se ejecuta un effect, todos los elementos del
DOM ya están presentes en el navegador.

Si analizamos el ejemplo anterior, podemos ver que este efecto actúa como el
método componendDidMount y componentDidUpdate al mismo tiempo, pues se
ejecutará justo después del primer renderizado, pero también se ejecutará
durante la actualización.

Un detalle muy importante a tomar en cuenta es que, los effects se ejecutarán


después del renderizado del componente, pero también se ejecutarán cuando el
estado cambie, por esta razón, si ejecutamos el código anterior, veremos que el
effect se ciclará, pues se ejecutará la primera vez después del montado inicial,
luego llamará al API para consultar un usuario, luego, actualizará el estado. Con
la actualización del estado el componente nuevamente se renderizará, con lo que
el effect será nuevamente ejecutado y así de forma indeterminada.

Para evitar este comportamiento, los efectos reciben un segundo argumento, el


cual es un arreglo con valores que serán evaluado para determinar si el efecto
requiere ser ejecutado de nuevo. Por default un efecto siempre se ejecutará la
primera vez, pero seguido de esto, los valores que sean enviados en este arreglo
serán comparados con el valor cuando se ejecuten la segunda vez, si algún valor
es diferente al de la ejecución anterior, entonces el efecto se ejecutará de nuevo,
y si todos son igual, el efecto ya no se ejecuta. Veamos el ejemplo anterior con
este pequeño ajuste:

1. import React, { useState, useEffect } from 'react';


2.

331 | Página
3. const MyComponent = (props) => {
4.
5. const [user, setUser] = useState()
6.
7. // Similar a componentDidMount y componentDidUpdate:
8. useEffect( () => {
9. fetch(`/users/${props.user}`)
10. .then(res => res.json())
11. .then(res => setUser(res))
12. }, [props.user] )
13.
14. return(
15. <p>{user.name}</p>
16. )
17. }
18. export default MyComponent

En la línea 12 hemos agregado un arreglo con el nombre del usuario, lo que hará
que el efecto se ejecute la primera vez, pero después de esto, solo se volverá a
ejecutar cuando el valor de la propiedad cambie.

Toma en cuente que este array puede tener varias posiciones, permitiendo tener
múltiples razones por las que el efecto debería de ejecutarse.

Error común

Un error común es agregar expresiones para validar,


por ejemplo props.user!=null, ya que el efecto se
ejecutará cuando el valor cambie con respecto a la
ejecución anterior, por lo tanto, si la expresión retorna
true y pero el resultado de la expresión pasada era
false, entonces el valor ha cambiado y el efecto se
ejecuta, y en el caso contrario pasaría lo mismo.

De la misma forma que podemos tener varios estados, también podemos tener
diferentes efectos, y cada efecto puede tener reglas diferentes para que se
ejecuten bajo diferentes circunstancias, solo toma en cuenta que los efectos
serán ejecutados en el orden en que están declarados:

1. import React, { useState, useEffect } from 'react';


2.
3. const MyComponent = (props) => {
4.
5. const [user, setUser] = useState()
6. const [tweets, setTweets] = useState([])
7.
8. // Similar a componentDidMount y componentDidUpdate:
9. useEffect( () => {
10. fetch(`/users/${props.user}`)
11. .then(res => res.json())
12. .then(res => setUser(res))
13. }, [props.user])
14.

Página | 332
15. // Similar a componentDidMount y componentDidUpdate:
16. useEffect( () => {
17. fetch(`/tweets`)
18. .then(res => res.json())
19. .then(res => setTweets(res))
20. }, [ ])
21.
22. return(
23. <p>{user.name}</p>
24. )
25. }
26. export default MyComponent

Observe que en esta ocasión tenemos dos efectos, con la única diferencia de que
las reglas para ejecutarlos cambian y aquí es donde quiero darles un tip, si
quieres que un efecto solo se ejecuta una vez, agrega un arreglo vacío, así los
valores nunca cambiarán y dará como resultado la ejecución inicial justo después
del primer render.

Tip

Un arreglo vacío garantiza que un efecto solo se


ejecuta una vez, justo después del primer renderizado.

Si prestante atención, dijimos que los efectos funcionan como el método


componentDidMount, componentDidUpdate y componentWillUnmount, sin embargo,
creo que hasta ahora hemos comprobado que funciona como los primeros, pero
que pasa con el componentWillUnmount, ¿será que también se va a ejecutar el
efecto antes de desmontarse?, la respuesta es sí y no, me explico.

Un efecto puede retornar una Function, la cual conocemos como funcione de


limpieza, y esta funcion se ejecutan cuando el componente es desmontado, sin
embargo, recordemos que un efecto se puede ejecutar varias veces, lo que nos
lleva a que React en realidad ejecute la función de limpieza antes del siguiente
renderizado, de esta forma, es como si el componente se desmontara justo antes
del renderizado para luego ejecutar el efecto. Veamos mejor un ejemplo de esto:

1. import React, { useState, useEffect } from 'react';


2.
3. const MyComponent = (props) => {
4.
5. const [user, setUser] = useState()
6. const [tweets, setTweets] = useState([])
7.
8. // Similar a componentDidMount y componentDidUpdate:
9. useEffect( () => {
10. fetch(`/users/${props.user}`)
11. .then(res => res.json())
12. .then(res => setUser(res))
13.
14. return () => {
15. console.log("clean action")
16. //any actions
17. }

333 | Página
18. }, [props.user])
19.
20. return(
21. <p>{user.name}</p>
22. )
23. }
24. export default MyComponent

Utilizar funciones de limpieza es poco común, así como utilizar el método


componentWillUnmount, sin embargo tiene algunos usos, como eliminar algunos
listener, limpiar algún elemento de la pantalla, etc.

Creando nuestros propios Hooks

Al inicio de esta unidad hablamos de que una de las principales problemáticas de


React era que no tenía un mecanismo simple para reutilizar la lógica no visual,
lo que nos llevaba al infierno de los envoltorios, donde teníamos que crear
componentes que retornaban otro componente, pues bien, uno de los principales
objetivos de los Hooks es precisamente proporcionar un mecanismo que nos
permita crear nuestros propios Hooks, los cuales pueden tener cierta lógica no
visual y reutilizable.

Antes de continuar, es importante comprender algo, un Hook es una función


común y corriente la cual no tiene una firma específica, es decir, se puede llamar
como sea, recibir los argumentos que sea y regresar lo que sea, sin embargo,
hay una regla no escrita que dice que todos los hooks deben de comenzar con la
palabra “use”, como es el caso de useState, useEffect, useContext, etc. Podrás
ver entonces que todas estas funciones también son Hooks.

Cuando digo que es una regla no escrita me refiero a que React o JavaScript no
podrán impedir que les pongas otro nombre, sin embargo, si está definido en la
documentación de React, por lo tanto, se podría decir que debemos de comenzar
los hooks con “use” aunque nadie nos puede impedir que lo hagamos.

En esencia un hooks es una función que encapsula una lógica no visual


encapsulada en una función que podemos reutilizar sin necesidad de caer en el
infierno de los envoltorios o los conocidos como High Order Components. Pero
vasta de palabrerías y vamos a un ejemplo de cómo crear nuestro propio hook:

1. import { useState, useEffect } from 'react';


2. import APIInvoker from '../utils/APIInvoker'
3.
4. function useTweets(username) {
5. const [tweets, setTweets] = useState([]);
6.
7. useEffect(() => {
8. let url = username ? `/tweets/${username}` : '/tweets'
9. APIInvoker.invokeGET(url, response => { setTweets(response.body)})
10. }, [username]);
11.
12. return tweets;

Página | 334
13. }
14. export default useTweets

Este hook nos permite recuperar los últimos tweets o los tweets de un solo
usuario. Para lograr esto, requiere que se le envié el usuario del cual se buscarán
los tweets, pero si ese valor no se envía, se buscan los últimos tweets de todos
los usuarios. Ahora bien, para utilizar el hook anterior, solo tenemos que
mandarlo llamar:

1. import React, { useState, useEffect } from 'react';


2. import useTweets from './hooks/useTweets'
3. const MyComponent = (props) => {
4.
5. const tweets = useTweets("oscar")
6.
7. return(
8. <div>
9. {tweets.map(x => <p key={x._id}>{x.message}</p>)}
10. </div>
11. )
12. }
13. export default MyComponent

Justo antes de que el componente sea renderizarse, el hook useTweets será


ejecutado, y con ello, el valor por defecto será asignado, es decir un array en
blanco, luego el efecto del hook se ejecutará, actualizará el valor del estado
(setTweets) y luego el nuevo valor será asignado a la variable tweets.

Para reutilizar el hook useTweets solo hace falta llamarlo desde otro componente,
y con esto, estaríamos reutilizando lógica no visual de forma simple.

Utilizando el Context con los Hooks

Uno de los hooks más populares es useContext, el cual nos permite utilizar el
Context sin la necesidad de encapsular nuestro componente dentro de un
Consumer, evitando el famoso infierno de las envolturas, veamos un ejemplo:

1. import React, { useContext } from 'react'


2. import UserContext from './context/UserContext'
3.
4. const MyComponent = (props) => {
5.
6. const userContext = useContext(UserContext)
7.
8. return(
9. <p>{userContext.username}</p>
10. )
11. }
12. export default MyComponent

335 | Página
Si observas la línea 6, podrás ver que es posible recuperar el contexto con tan
solo utilizar el hook useContext, el cual recibe como parámetro el contexto que
queremos recuperar.

El equivalente a este componente sin hooks sería algo así:

1. import React, { useContext } from 'react'


2. import UserContext from './context/UserContext'
3.
4. const MyComponent = (props) => {
5.
6. return(
7. <UserContext.Consumer>
8. {userContext =>
9. <p>{userContext.username}</p>
10. }
11. <UserContext.Consumer/>
12. )
13. }
14. export default MyComponent

Ahora bien, si necesitáramos utilizar más de un Context, entonces tendríamos


que envolver nuestro componente en otro Consumer, lo que va haciendo cada vez
más complicado el componente. En cambo con hooks, simplemente utilizamos
useContext con todos los contextos que necesitemos.

Algo a tomar en cuenta, es que a pesar de utilizar los hooks para obtener el
contexto, es que solo nos ahorra la creación del Consumer, pero seguiremos
necesitando crear el Provider, de por lo que cuando usemos useContext, deberá
ser sobre un componente que sea descendiente del Provider.

El futuro de las clases

Muchos se ha especulado que ahora con la llegada de los hooks, los componentes
de clase pasaran a ser posteriormente desaconsejados (deprecados), sin
embargo, el mismo equipo de React ha salido a desmentir esto, incluso, insisten
en que no hay planes para desaconsejar el uso de clases en el futuro, por lo que
podemos seguir usando clases en nuestros proyectos la garantía de que seguirán
por mucho tiempo más.

Otro de los comentarios que puedes ver mucho en Internet es personas diciendo
que hora debemos de mirar todos nuestros componentes para utilizar Hooks, sin
embargo, pueden existir proyectos que combinen componentes de clases y
componentes de función, incluso, es normal ver proyectos donde existen
componentes de función y de clase al mismo tiempo.

Página | 336
Mi recomendación es que solo migremos a hooks solo los nuevos compontes que
creemos y migrar uno que otro componente de clase que se nos complique por
el uso de clases, pero fuera de eso, no tiene caso hacer una migración total del
proyecto.

Mini Twitter (Continuación 7)

En esta unidad vamos a aplicar todos los conocimientos que hemos adquirido de
los hooks para mejorar nuestro proyecto Mini Twitter, por lo que comenzaremos
con los componentes más simples e iremos pasando a los más complejos a
medida que avancemos.

Migrando el componente Followers

Si recordamos, el componente Followers es el que muestra los seguidores de un


usuario. Veamos como quedaría utilizando Hooks y luego pasaremos a explicarlo:

1. import React, { useEffect, useState } from 'react'


2. import UserCard from './UserCard'
3. import APIInvoker from './utils/APIInvoker'
4. import PropTypes from 'prop-types'
5. import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'
6.
7. const Followers = (props) => {
8.
9. const [state, setState] = useState([])
10.
11. useEffect(() => {
12. let username = props.profile.userName
13. APIInvoker.invokeGET(`/followers/${username}`, response => {
14. setState(response.body)
15. }, error => {
16. console.log("Error en la autenticación");
17. })
18. }, [props.profile.userName])
19.
20.
21. return (
22. <section>
23. <div className="container-fluid no-padding">

337 | Página
24. <div className="row no-padding">
25. <CSSTransitionGroup
26. transitionName="card"
27. transitionEnter={true}
28. transitionEnterTimeout={500}
29. transitionAppear={false}
30. transitionAppearTimeout={0}
31. transitionLeave={false}
32. transitionLeaveTimeout={0}>
33. <For each="user" of={state}>
34. <div className="col-xs-12 col-sm-6 col-lg-4"
35. key={user._id}>
36. <UserCard user={user} />
37. </div>
38. </For>
39. </CSSTransitionGroup>
40. </div>
41. </div>
42. </section>
43. )
44. }
45.
46. Followers.propTypes = {
47. profile: PropTypes.object
48. }
49.
50. export default Followers

El primer cambio es convertir el componente de clase a un componente de


función, es por ello que en la línea 7 en lugar de crear una clase que entienda de
React.Componente, creamos una función de flecha y definimos las propiedades
como parámetro.

Al tratarse ahora de un componente de función, ya no podemos tener un


constructor en donde inicializar el estado, por lo que utilizamos el hook useState
para crear el estado del componente (línea 9). Lo inicializamos con un array vacío
para garantizar que el render del componente no truene al momento de intentar
iterar el estado.

Debido a que este componente ya no es una clase, los métodos del ciclo de vida
ya no funcionarán, por lo que el siguiente paso es extraer la lógica que teníamos
en el método componentDidMount y pasarla dentro de un useEffect, tal y como
podemos ver en la línea (11). Para garantizar que el efecto no se ejecute de
forma indeterminada, le decimos que no se ejecute solo cuando la propiedad
props.profile.userName cambien, en otro caso, solo se ejecutará la primera vez
cuando el componente se monte.

Finalmente, remplazamos el método render por un simple return, ya que si


recordamos, ahora estamos en un componente de función, por lo que los
métodos del ciclo de vida como es el caso del método render, ya no funcionará.
Observa también que hemos cambiado todas las referencias al this.state (línea
33) por el provisto por useState (observa que ya no es necesario utilizar el
operador this).

Página | 338
Migrando el componente Followings

El siguiente paso es migrar el componente Followings, el cual es prácticamente


igual al componente anterior, con la única diferencia de que en lugar de mostrar
las personas que nos siguen, es muestra las personas que nosotros seguimos.

Debido a que este componente es prácticamente igual al anterior, no nos


detendremos mucho en la explicación.

1. import React, { useState, useEffect } from 'react'


2. import UserCard from './UserCard'
3. import APIInvoker from './utils/APIInvoker'
4. import PropTypes from 'prop-types'
5. import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'
6.
7. const Followings = (props) => {
8.
9. const [state, setState] = useState([])
10.
11. useEffect(() => {
12. let username = props.profile.userName
13. APIInvoker.invokeGET(`/followings/${username}`, response => {
14. setState(response.body)
15. }, error => {
16. console.log("Error =>", error);
17. })
18. }, [props.profile.userName])
19.
20. return (
21. <section>
22. <div className="container-fluid no-padding">
23. <div className="row no-padding">
24. <CSSTransitionGroup
25. transitionName="card"
26. transitionEnter={true}
27. transitionEnterTimeout={500}
28. transitionAppear={false}
29. transitionAppearTimeout={0}
30. transitionLeave={false}
31. transitionLeaveTimeout={0}>
32. <For each="user" of={state}>
33. <div className="col-xs-12 col-sm-6 col-lg-4"
34. key={user._id}>
35. <UserCard user={user} />
36. </div>
37. </For>
38. </CSSTransitionGroup>
39. </div>
40. </div>
41. </section>
42. )
43. }
44.
45. Followings.propTypes = {
46. profile: PropTypes.object
47. }
48.
49. export default Followings

339 | Página
Nuevamente, convertimos el componente de clase a componente de función y
agregamos las props como parámetro de entrada (línea 7). Seguido, declaramos
el estado del componente con useState (línea 9). Pasamos la lógica del método
componentDidMount a un useEffect (línea 11) y nos aseguramos de que solo se
ejecute cuando el usuario cambie (línea 18). Finalmente cambiamos el método
render por un simple return.

Migrando el componente SuggestedUser

El siguiente componente a migrar será SuggestedUser, un componente que nos


muestra algunos perfiles recomendados para seguir. Veamos como quedaría y
luego lo analizamos paso a paso.

1. import React, { useState, useEffect } from 'react'


2. import APIInvoker from './utils/APIInvoker'
3. import { Link } from 'react-router-dom'
4.
5. const SuggestedUser = (props) => {
6.
7. const [state, setState] = useState(null)
8.
9. useEffect(() => {
10. APIInvoker.invokeGET('/secure/suggestedUsers', response => {
11. setState(response.body)
12. }, error => {
13. setState([])
14. console.log("Error al actualizar el perfil", error);
15. })
16. },[])
17.
18.
19. return (
20. <aside id="suggestedUsers" className="twitter-panel">
21. <span className="su-title">A quién seguir</span>
22. <If condition={state != null} >
23. <For each="user" of={this.state.users}>
24. <div className="sg-item" key={user._id}>
25. <div className="su-avatar">
26. <img src={user.avatar} alt={user.name} />
27. </div>
28. <div className="sg-body">
29. <div>
30. <Link to={"/" + user.userName}>
31. <span className="sg-name">{user.name}</span>
32. <span className="sg-username">@{user.userName}</span>
33. </Link>
34. </div>
35. <Link to={"/" + user.userName}
36. className="btn btn-primary btn-sm">
37. <i className="fa fa-user-plus" aria-hidden="true"></i>
38. Ver perfil</Link>
39. </div>
40. </div>
41. </For>
42. </If>
43. </aside>
44. )

Página | 340
45. }
46. export default SuggestedUser;

Lo primero que hacemos es convertir el componente de clase a componente


funcional (línea 5), seguido, eliminamos el constructor y utilizamos useState para
definir el estado del componente (línea 7). El siguiente paso es reemplazar el
método componentDidMount por un useEffect que realizará el trabajo de la carga
de los datos desde el API.

Finalmente, reemplazamos el método render por un simple return y


reemplazamos cualquier referencia a this.state por el estado provisto por el
hook useState.

Migrando el componente Toolbar

Para ponerle un poco más de acción a esto, vamos a utilizar otra de las
características de los hooks, que es la posibilidad de utilizar el Context sin la
necesidad de declarar un componente Consumer. Veamos a migrar el componente
Toolbar para ver como quedaría:

1. import React from 'react'


2. import { Link, NavLink } from 'react-router-dom'
3. import UserContext from './context/UserContext'
4. import { useContext } from 'react'
5.
6. const Toolbar = (props) => {
7.
8. const userContext = useContext(UserContext)
9.
10. const logout = (e) => {
11. e.preventDefault()
12. window.localStorage.removeItem("token")
13. window.localStorage.removeItem("username")
14. window.location = '/login';
15. }
16.
17. return (
18. <nav className="navbar navbar-default navbar-fixed-top">
19. <span className="visible-xs bs-test">XS</span>
20. <span className="visible-sm bs-test">SM</span>
21. <span className="visible-md bs-test">MD</span>
22. <span className="visible-lg bs-test">LG</span>
23.
24. <div className="container-fluid">
25. <div className="container-fluid">
26. <div className="navbar-header">
27. <Link className="navbar-brand" to="/">
28. <i className="fa fa-twitter"
29. aria-hidden="true"></i>
30. </Link>
31. <ul id="menu">
32. <li id="tbHome" className="selected">
33. <NavLink to="/" activeClassName="selected">
34. <p className="menu-item">

341 | Página
35. <i className="fa fa-home menu-item-icon"
36. aria-hidden="true" />
37. <span className="hidden-xs hidden-sm">
38. Inicio</span>
39. </p>
40. </NavLink>
41. </li>
42. </ul>
43. </div>
44. <If condition={userContext != null} >
45. <ul className="nav navbar-nav navbar-right">
46. <li className="dropdown">
47. <a href="#" className="dropdown-toggle"
48. data-toggle="dropdown" role="button"
49. aria-haspopup="true" aria-expanded="false">
50. <img className="navbar-avatar"
51. src={userContext.avatar}
52. alt={userContext.userName} />
53. </a>
54. <ul className="dropdown-menu">
55. <li>
56. <Link to={`/${userContext.userName}`}>
57. Ver perfil</Link>
58. </li>
59. <li role="separator" className="divider"/>
60. <li>
61. <Link to="#" onClick={logout}>
62. Cerrar sesión</Link>
63. </li>
64. </ul>
65. </li>
66. </ul>
67. </If>
68. </div>
69. </div>
70. </nav>
71. )
72. }
73.
74. export default Toolbar

Como siempre, podrás ver que hemos pasado de un componente de clase a un


componente de función y hemos hecho los ajuste de siempre, por lo que esta vez
no nos detendremos a explicarlos, lo que sí, es que nos centraremos en lo
realmente interesante, que es lo referente al Context.

Si recuerdas la unidad pasada, explicamos que para suscribirnos a los cambios


en el Context, era necesario crear un componente de tipo Consumer, el cual tenía
como hijo una función algo extraña, pues bien, con los hooks ya no es necesario
hacer nada de eso, y en su lugar, simplemente utilizamos el hook useContext
(línea 8) que recibe como parámetro el contexto que queremos utilizar, como
resultado, nos devolverá el valor actual del contexto, y además, la vista se
actualizará cada vez que el Context cambie.

Otra de las ventajas que ya habíamos platicado, es que, ya no es necesaria la


incómoda instrucción this.xxxx.bind (this), en su lugar, podemos hacer la
llamada a las funciones de flecha definidas directamente, como es el caso de la
llamada que tenemos en la línea 61, que se ejecuta cuando cerramos la sesión.

Página | 342
Migrando el componente TwitterDashboard

Otro componente que también requiere los mismos pasos que los anteriores es
TwitterDashboard, donde pasaremos de un componente de clase a uno de
función:

1. import React from 'react'


2. import Profile from './Profile'
3. import TweetsContainer from './TweetsContainer'
4. import SuggestedUser from './SuggestedUser'
5. import UserContext from './context/UserContext'
6. import { useContext } from 'react'
7.
8.
9. const TwitterDashboard = (props) => {
10.
11. const userContext = useContext(UserContext)
12.
13. return (
14. <div id="dashboard" className="animated fadeIn">
15. <div className="container-fluid">
16. <div className="row">
17. <div className="hidden-xs col-sm-4 col-md-push-1
18. col-md-3 col-lg-push-1 col-lg-3" >
19. <Profile profile={userContext} />
20. </div>
21. <div className="col-xs-12 col-sm-8 col-md-push-1
22. col-md-7 col-lg-push-1 col-lg-4">
23. <TweetsContainer profile={userContext} />
24. </div>
25. <div className="hidden-xs hidden-sm hidden-md
26. col-lg-push-1 col-lg-3">
27. <SuggestedUser />
28. </div>
29. </div>
30. </div>
31. </div>
32. )
33. }
34.
35. export default TwitterDashboard;

No te voy a aburrir nuevamente con la explicación, pues creo que ya hemos visto
varios ejemplos de lo que tenemos que hacer, por lo que solo te marcaré los
cambios para que sea tú mismo quien analice los cambios.

Migrando el componente TwitterApp

343 | Página
Desde este punto, los cambios se comienzan a poner interesantes, pues vamos
a comenzar a crear nuestros propios hooks con la intención de reutilizar
funcionamiento.

1. import React, {useEffect} from 'react'


2. import browserHistory from './History'
3. import { Route, Switch, Redirect } from "react-router-dom";
4. import Signup from './Signup'
5. import Login from './Login'
6. import UserPage from './UserPage'
7. import TwitterDashboard from './TwitterDashboard'
8. import Toolbar from './Toolbar';
9. import UserContext from './context/UserContext'
10. import useLogin from './hooks/useLogin'
11. import AuthRoute from './AuthRoute'
12.
13. const TwitterApp = (props) => {
14.
15. const [load, user] = useLogin()
16.
17. const render = () => {
18. if (!load) return null
19.
20. return (
21. <UserContext.Provider value={user}>
22. <Toolbar profile={user} />
23. <div id="mainApp" className="animated fadeIn">
24. <Switch>
25. <AuthRoute isLoged={user != null} exact path="/"
26. component={TwitterDashboard} />
27. <Route exact path="/signup" component={Signup} />
28. <Route exact path="/login" component={Login} />
29. <AuthRoute isLoged={user != null} path="/:user"
30. component={UserPage} />
31. </Switch>
32. </div>
33. </UserContext.Provider>
34. )
35. }
36.
37. return render()
38. }
39. export default TwitterApp

Para explicar los cambios en este componente, vamos a omitir los pasos que ya
sabemos de pasar de clase a función y todo eso, para centrarnos en la línea 15.
Observa que estamos utilizando un nuevo hook llamado useLogin.

Lo primero que nos debemos de cuestionar es, ¿cómo sabemos que se trata de
un hook? Bueno, si recuerdas la teoría al inicio de este capítulo, dijimos que los
hooks comienza con “use”, por lo tanto, podemos determinar que se trata de un
hook, ahora bien, si vemos el import de ese hook, veremos que no es estándar
de React, y es que este es un hook que yo he creado para reutilizar la lógica de
autenticación.

Ahora bien, useLogin es un nuevo archivo que deberemos de crear en la carpeta


/app/hooks con el nombre de useLogin.js y se verá de la siguiente manera:

1. import React, { useEffect, useState } from 'react'

Página | 344
2. import APIInvoker from '../utils/APIInvoker'
3. import browserHistory from '../History'
4.
5. const useLogin = () => {
6. const [loginStatus, setLoginStatus] = useState({
7. load: false,
8. user: null
9. })
10.
11.
12. useEffect(() => {
13. let token = window.localStorage.getItem("token")
14. if (token == null) {
15. setLoginStatus({
16. load: true,
17. user: null
18. })
19. } else {
20. APIInvoker.invokeGET('/secure/relogin', response => {
21. setLoginStatus({
22. load: true,
23. user: response.profile
24. })
25. window.localStorage.setItem("token", response.token)
26. window.localStorage.setItem("username",response.profile.userName)
27. }, error => {
28. console.log("Error al autenticar al autenticar al usuario ");
29. window.localStorage.removeItem("token")
30. window.localStorage.removeItem("username")
31. setLoginStatus({
32. load: true,
33. user: null
34. })
35. })
36. }
37. }, [window.localStorage.getItem("token")])
38.
39. return [loginStatus.load, loginStatus.user]
40. }
41.
42. export default useLogin

¿Recuerdas que dijimos que uno de los propósitos de los hooks es reutilizar la
lógica no visual? Pues es justo lo que estamos haciendo aquí. Si prestas mucha
atención, el contenido del efecto declarado en la línea 12, es lo mismo que
teníamos en el método componentDidMount del componente TwitterApp. Entonces
podemos decir que este hooks lo que hace es hacer la llamada al API para validar
el token y regresarnos el usuario autenticado (si es que tenemos un token).

Los hooks personalizados pueden ser complicados de comprender, por lo que


puede que de momento le entiendas poco o incluso nada, por lo que tendrás que
ser paciente y tomarte un buen tiempo para analizar el código, trata de entender
que está pasando en cada línea e imagina que pasaría si ejecutaras cada línea,
solo así podrás comprender bien que es un hook personalizado.

Para ayudarte a comprender que es un hook, te daré una pista, dijimos que los
hooks permiten reutilizar la lógica no visual, por lo tanto, un hook no retorna una
interfaz, en su lugar retorna datos (o incluso nada), dicho lo anterior, quiero que
prestes atención para que te des cuenta que un hook es casi lo mismo que un

345 | Página
componente de función, solo que no regresa un elemento gráfico. Imagina que
la línea 39 regresara un elemento renderizable… te das cuenta… sería un
componte de función. Solo revisa sus elementos, es una función, puede tener un
estado con useState (línea 6), tiene efectos (línea 12) y en lugar de retornar un
elemento renderizable, retorna datos, es por eso que decimos que los hooks
permite reutilizar lógica no visual.

Un detalle importante a tomar en cuenta es que el hook useLogin tiene un efecto


(useEffect) el cual cada vez que se ejecuta, actualiza el estado, por este motivo,
cada vez que el efecto se ejecute, actualizará el estado del hook, lo que dispara
una actualización también en el componente TwitterApp y el nuevo valor será
asignado.

Por el momento solo utilizaremos este hook en TwitterApp, pero debido a que
ahora es un hook, ya lo podríamos reutilizar en cualquier otra parte de la
aplicación para logear al usuario por medio del token y que nos retorne el
usuario autenticado.

Migrando el componente UserPage

Finalmente, ha llegado la hora de migrar el componente UserPage, el componente


que sin lugar a duda, es el más complejo de la aplicación, no solo por los
elementos que contiene, si no porque puede pasar de modo de solo lectura a
edición, y todo eso lo tendremos que controlar ahora mediante hooks. Veamos
como quedarían los cambios y luego procederemos a explicarlos.

1. import React, {useEffect} from 'react'


2. import update from 'immutability-helper'
3. import APIInvoker from './utils/APIInvoker'
4. import { NavLink } from 'react-router-dom'
5. import MyTweets from './MyTweets'
6. import { Route, Switch } from 'react-router-dom'
7. import Followings from './Followings'
8. import Followers from './Followers'
9. import TweetDetail from './TweetDetail'
10. import Modal from './Modal'
11. import UserContext from './context/UserContext'
12.
13. const UserPage = (props) => {
14.
15. const userContext = React.useContext(UserContext)
16.
17. const [profile, setProfile] = React.useState({
18. name: "",
19. description: "",
20. avatar: null,
21. banner: null,
22. userName: ""
23. })
24.
25. const [edit, setEdit] = React.useState(false)
26.

Página | 346
27. useEffect(() => {
28. let username = props.match.params.user
29. APIInvoker.invokeGET(`/profile/${username}`, response => {
30. setEdit(false)
31. setProfile(response.body)
32. }, error => {
33. console.log("Error al cargar los Tweets");
34. window.location = '/'
35. })
36. }, [props.match.params.user])
37.
38.
39. const follow = (e) => {
40. let request = {
41. followingUser: props.match.params.user
42. }
43. APIInvoker.invokePOST('/secure/follow', request, response => {
44. if (response.ok) {
45. setProfile(update(profile, {
46. follow: { $set: !response.unfollow }
47. }))
48. }
49. }, error => {
50. console.log("Error al actualizar el perfil");
51. })
52. }
53.
54. const changeToEditMode = (e) => {
55. if (edit) {
56. let request = {
57. username: profile.userName,
58. name: profile.name,
59. description: profile.description,
60. avatar: profile.avatar,
61. banner: profile.banner
62. }
63.
64. APIInvoker.invokePUT('/secure/profile', request, response => {
65. if (response.ok) {
66. setEdit(false)
67. }
68. }, error => {
69. console.log("Error al actualizar el perfil");
70. })
71. } else {
72. let currentState = profile
73. setEdit(true)
74. setProfile({
75. ...profile,
76. currentState
77. })
78. }
79. }
80.
81. const imageSelect = (e) => {
82. let id = e.target.id
83. e.preventDefault();
84. let reader = new FileReader();
85. let file = e.target.files[0];
86.
87. if (file.size > 1240000) {
88. alert('La imagen supera el máximo de 1MB')
89. return
90. }
91.
92. reader.onloadend = () => {

347 | Página
93. if (id == 'bannerInput') {
94. setProfile(update(profile, {
95. banner: { $set: reader.result }
96. }))
97. } else {
98. setProfile(update(profile, {
99. avatar: { $set: reader.result }
100. }))
101. }
102. }
103. reader.readAsDataURL(file)
104. }
105.
106. const handleInput = (e) => {
107. let id = e.target.id
108. setProfile(update(profile, {
109. [id]: { $set: e.target.value }
110. }))
111. }
112.
113. const cancelEditMode = (e) => {
114. setEdit(false)
115. setProfile(profile.currentState)
116. }
117.
118.
119. return (
120. <div id="user-page" className="app-container">
121. <header className="user-header">
122. <div className="user-banner"
123. style={{ backgroundImage: 'url(' + (profile.banner) + ')' }}>
124. <If condition={edit}>
125. <div>
126. <label htmlFor="bannerInput" className="btn select-banner">
127. <i className="fa fa-camera fa-2x" aria-hidden="true"></i>
128. <p>Cambia tu foto de encabezado</p>
129. </label>
130. <input href="#" className="btn"
131. accept=".gif,.jpg,.jpeg,.png"
132. type="file" id="bannerInput"
133. onChange={imageSelect} />
134. </div>
135. </If>
136. </div>
137. <div className="user-summary">
138. <div className="container-fluid">
139. <div className="row">
140. <div className="hidden-xs col-sm-4 col-md-push-1
141. col-md-3 col-lg-push-1 col-lg-3" >
142. </div>
143. <div className="col-xs-12 col-sm-8 col-md-push-1
144. col-md-7 col-lg-push-1 col-lg-7">
145. <ul className="user-summary-menu">
146. <li>
147. <NavLink to={`/${profile.userName}`} exact
148. activeClassName="selected">
149. <p className="summary-label">TWEETS</p>
150. <p className="summary-value">{profile.tweetCount}</p>
151. </NavLink>
152. </li>
153. <li>
154. <NavLink to={`/${profile.userName}/following`}
155. activeClassName="selected">
156. <p className="summary-label">SIGUIENDO</p>
157. <p className="summary-value">{profile.following}</p>
158. </NavLink>

Página | 348
159. </li>
160. <li>
161. <NavLink to={`/${profile.userName}/followers`}
162. activeClassName="selected">
163. <p className="summary-label">SEGUIDORES</p>
164. <p className="summary-value">{profile.followers}</p>
165. </NavLink>
166. </li>
167. </ul>
168.
169. <If condition={profile.userName === userContext.userName}>
170. <button className="btn btn-primary edit-button"
171. onClick={changeToEditMode} >
172. {edit ? "Guardar" : "Editar perfil"}</button>
173. </If>
174.
175.
176. <If condition={profile.follow != null &&
177. profile.userName !== userContext.userName} >
178. <button className="btn edit-button"
179. onClick={follow} >
180. {profile.follow
181. ? (<span><i className="fa fa-user-times"
182. aria-hidden="true"></i> Siguiendo</span>)
183. : (<span><i className="fa fa-user-plus"
184. aria-hidden="true"></i> Seguir</span>)
185. }
186. </button>
187. </If>
188.
189. <If condition={edit}>
190. <button className="btn edit-button" onClick=
191. {cancelEditMode} >Cancelar</button>
192. </If>
193. </div>
194. </div>
195. </div>
196. </div>
197. </header>
198. <div className="container-fluid">
199. <div className="row">
200. <div className="hidden-xs col-sm-4 col-md-push-1 col-md-3
201. col-lg-push-1 col-lg-3" >
202. <aside id="user-info">
203. <div className="user-avatar">
204. <Choose>
205. <When condition={edit} >
206. <div className="avatar-box">
207. <img src={profile.avatar} />
208. <label htmlFor="avatarInput"
209. className="btn select-avatar">
210. <i className="fa fa-camera fa-2x"
211. aria-hidden="true"></i>
212. <p>Foto</p>
213. </label>
214. <input href="#" id="avatarInput"
215. className="btn" type="file"
216. accept=".gif,.jpg,.jpeg,.png"
217. onChange={imageSelect}
218. />
219. </div>
220. </When>
221. <Otherwise>
222. <div className="avatar-box">
223. <img src={profile.avatar} />
224. </div>

349 | Página
225. </Otherwise>
226. </Choose>
227. </div>
228. <Choose>
229. <When condition={edit} >
230. <div className="user-info-edit">
231. <input maxLength="20" type="text" value={profile.name}
232. onChange={handleInput} id="name" />
233. <p className="user-info-username">
234. @{profile.userName}
235. </p>
236. <textarea maxLength="180" id="description"
237. value={profile.description}
238. onChange={handleInput} />
239. </div>
240. </When>
241. <Otherwise>
242. <div>
243. <p className="user-info-name">{profile.name}</p>
244. <p className="user-info-username">
245. @{profile.userName}
246. </p>
247. <p className="user-info-description">
248. {profile.description}</p>
249. </div>
250. </Otherwise>
251. </Choose>
252. </aside>
253. </div>
254. <div className="col-xs-12 col-sm-8 col-md-7
255. col-md-push-1 col-lg-7">
256. <Switch>
257. <Route exact path="/:user" component={
258. () => <MyTweets profile={profile} />} />
259. <Route exact path="/:user/followers" component={
260. () => <Followers profile={profile} />} />
261. <Route path="/:user/following" component={
262. () => <Followings profile={profile} />} />
263. <Route exact path="/:user/tweet/:tweet" component={
264. (params) => <Modal> <TweetDetail {...params} /> </Modal>} />
265. </Switch>
266. </div>
267. </div>
268. </div>
269. </div>
270. )
271. }
272. export default UserPage

Bueno, podrás ver que es un componente muy extenso como para explicar cada
cambio, por lo que haremos un resumen de los cambios más simples y después
pasaremos a explicar aquellos más complicados.

Lo primero que hacemos como siempre es, convertir el componente de clase a


componente de función (línea 13), eliminamos el constructor y creamos dos
estados diferentes, profile (línea 17) para administrar el perfil del usuario y edit
(línea 25) para determinar si el componente está en edición. Recuerda que con
los hooks es posible tener más de un estado con useState. Por otra parte,
también hemos utilizado el hook useContext para recuperar el usuario
autenticado, eliminando la dependencia al componente Consumer.

Página | 350
El siguiente paso es eliminar el método componentDidMount y pasar la
funcionalidad al hook useEffect (línea 27), el cual lo hemos limitado para que se
ejecute solo cuando el URL param user cambie (línea 36).

Otro de los cambios simple que realizamos fue reemplazar todas las funciones de
la clase a funciones de flecha y de esta forma, dejar de utilizar el this para
acceder a la funcionalidad.

Otro de los cambios también simples pero demasiado numerosos como para
detenernos a explicar uno por uno, es que hemos eliminado la referencia al
this.state para reemplazarlo los dos estados definidos con useState, me refiero
a profile y edit, también hemos cambiado las referencias a los métodos,
eliminando la necesidad de utilizar this.xxxx.bind(this).

351 | Página
Conclusiones

Como hemos analizando en esta unidad, los hooks son una nueva forma de crear
componentes de función que permitan tener estado, pero también mencionamos
que el estado es solo una parte de lo que realmente nos ofrecen los hooks, pues
analizamos como mediante los hooks es posible evitar algunas cosas incomodas,
como tener que inicializar la super clase en el constructor, evitamos el uso de
this y bind, nos permite reutilizar lógica no visual y nos permite evitar el famoso
infierno de los envoltorios.

Sin embargo, si tuviéramos que definir qué es lo más importante que nos traen
los hooks, yo diría que son 3 cosas, la posibilidad de tener estado, añadir efectos
como una sustitución al complicado ciclo de vida de los componentes de clase y
la posibilidad de reutilizar lógica no visual.

Página | 352
Redux
Capítulo 14

Una de las mayores problemáticas de React es la forma en que se administran


los estados y como los cambios pueden afectar a otros componentes. Como ya
hemos venido viendo, React propaga los cambios entre sus componentes de
arriba-abajo y de abajo-arriba, es decir, un componente solo se puede comunicar
con sus hijos directos y con su padre.

Fig. 92 - React change state

Para comprender este problema, analicemos la imagen anterior, e maginemos


que un cambio producido en el componente J, deberá reflejarce en el
componente G. Primero que nada, nos queda claro que no es posible realizar una
actualización directa, pues J y G no son decendientes directos, por lo que la única
forma de hacer llegar el cambio hasta G, es propagar los cambios hacia arriba,
hasta encontrar el primer pariente en común y luego replicar los cambios hacia
abajo, hasta llegar al componete G.

Para lograr que un cambio se propague de esta forma, es necesario que todos
los componentes involucrados en la cadena, conozcan la propiedad y las
repliquen a sus descendientes, sin embargo, pasar propiedades solo aplica para
replicar los cambios de arriba abajo, y para replicar los cambios de abajo arriba,
es necesario que los padres maden funciones como props para que los hijos las
ejecuten para notificar al padre de los cambios.

353 | Página
Este problema se puede repetir varias veces en una aplicación, sobre todo en
aquellas páginas muy complejas donde hay una gran cantidad de elementos, lo
que puede convertirse rápidamente un problema. Ya que administrar props y
funciones por toda la estructuractura crea componentes sumamente complejos y
difíciles de entender y mantender.

Introducción a Redux

Redux es una herramienta que nos ayuda a gestionar la forma en que accedemos
y actualizamos el estado de la aplicación de una forma centralizada y controlada.
Mediante Redux es posible centralizar el estado general de la aplicación en algo
llamado store, liberando a los componentes la responsabilidad de administrar un
estado interno.

Fig. 93 - Cambio del estado con Redux.

Redux funciona prácticamente igual que el Context, pues permite gestionar el


estado desde una estructura externa a la jerarquía de componentes, aunque
tiene una diferencia importante, el Context es utilizado para guardar datos
globales, es decir, que son de interés para toda la aplicación, mientras que
Redux, permite gestionar datos comúnes que son relevamente para una página
determinada.

Este último pude resultar confuso, por que no queda claro cuando algo de global
y cuando no, sin embargo, hay una forma de saberlo, si un determinado dato es
relevante para toda la aplicación, entonces se considera global, por otro lado, si
un determinado dato, solo es relevante solo para una determinada página,
entonces es un dato que podríamos gestionar con Redux.

Página | 354
Como funciona Redux

Antes que nada, es importante aclarar que Redux es una librería que nace para
gestionar el estado de cualquier aplicación de una sola página (SPA) basada en
JavaScript, por lo que posible utilizarla con React, JQuery, Angular o una simple
página que utiliza JavaScript puro. Dicho lo anterior, pasemos a explicar como
funciona Redux.

Lo primero que debemos aprender son los componentes que conforma Redux y
como estos encajan para administrar el estado de la aplicación:

• Store: Representa el estado de la aplicación, es conocido dentro de


Redux como “la única fuente de la verdad”.
• Reducers: Son funciones JavaScript puras que determinan como deberá
ser actualizado el store en función de las acciones (actions).
• Actions: Son objetos JavaScript que describen una intención para
cambiar el estado del store.
• Dispatch: Es una función que permite lanzar acciones (actions) al
store, con la intención de afectar el estado.

Ahora bien, de nada sirve listar los componentes que conforman Redux sin no
entendemos como interactúan, por lo que vamos a ver con una serie de
imágenes, cual es el procesos desde el cual se crea el store, se lanza una acción,
se actualiza el estado con los reducers y finalmente, los cambios son propagados
por los componentes.

Paso 1: Creando el store

El primer paso para utilizar Redux es crear el store, el cual dijimos que es un
objeto independiente a la estructura de la jerarquía de componentes, el cual está
totalmente desconectado de los componentes al momento de su creación:

355 | Página
Para crear el store, Redux nos proporciona la función createStore, el cual recibe
como parámetro un reducer, o un conjunto de estos. Si nuestro store solo esta
compuesto de un reducer, lo podemos pasar directamente como parámetro la
función createStore, sin embargo, la mayoría de las veces, es necesario utilizar
más de un reducer, por lo que utilizamos la función combineReducer para
agruparlos.

1. import { createStore, combineReducers } from 'redux'


2. import reducer1 from '../redux/reducers/reducer1'
3. import reducer2 from '../redux/reducers/reducer2'
4. import reducer3 from '../redux/reducers/reducer3'
5.
6. const store = createStore(
7. combineReducers({
8. reducer1,
9. reducer2,
10. reducer3
11. })
12. )
13.
14. export default store

Observa que para crear el store estamos utilizando 3 reducers, los cuales
analizaremos más adelante.

Página | 356
Paso 2: Registrandonos al store

El siguiente paso es hacer que nuestros componentes se registren al store, con


la intención de que reciban las actualizaciones en caso de alguien actualice el
store.

Para esto, es necesesario realizar dos pasos, el primero: crear un componente


Provider que deberá englobar toda la aplicación.

1. import { Provider } from 'react-redux'


2. import store from './redux/store'
3.
4.
5. const MyApp = (props) => {
6.
7. return (
8. <Provider store={store} >
9. <MyComponent />
10. </Provider>
11. )
12. }
13. export default TwitterApp

El componente Provider es provisto por la librería react-redux, la cual es una


librería que hace un wrapper de la librería redux para facilitar su uso con React.
Provider recibe como parámetro el store, el cual creamos en el paso anterior.

El Provider es importante por que es el componente al cual se registran los


componentes para recibir las actualizaciones cuando el store cambie, es por este
motivo que cualquier componente que quiera registrarce al store, tendrá que ser
descendiente de Provider.

El segundo paso es registrar registrar nuestros componentes para recibir las


actualizaciones del store, para esto, utilizamos el hook useSelector en todos los
componentes donde queremos sincronizar con el store.

1. import { useSelector } from 'react-redux'


2.
3. const MyComponent = (props) => {
4.
5. const state = useSelector(state => state.myState)
6.
7. return (
8. <MyComponent>
9. <p>{state.name}</p>
10. </MyComponent/>
11. )
12.
13. }
14. export default MyComponent

357 | Página
Para conectar un componente al store, tenemos el hook useSelector, el cual
recibe como parámetro una función, dicha función recibirá como parámetro el
estado general del store, y la función deberá de retornar la sección del store que
nos interesa, de esta forma, cada vez que el store cambie, el hook actualizará el
componente con los nuevos valores, lo que detonará un nuevo renderizado del
componente para reflejar los nuevos valores.

En este punto, la aplicación se verá como en la imagen anterior, donde los


componentes interesados en recibir actualizaciones se registraran al store por
medio del componente Provider. Utilizaremos los componentes en color purpura
para identificar los componentes que están registrados al store.

Paso 3: Despachando acciones

Una vez que los componentes se registran al store, podrán despachar


(dispatcher) acciones (actions), con la intenación de manifestar una intención
de actualizar el estado del store.

Para despachar una acción son necesarios dos cosas, un referencia al dispatcher
y el objeto action que describe los cambios a realizar en el store. Para recuperar
el dispatcher contamos con el hook useDispatch:

1. import { useDispatch, useSelector } from 'react-redux'


2.
3. const MyComponent = (props) => {
4.
5. const dispatch = useDispatch()
6.
7. const state = useSelector(state => state.myState)
8.
9. return (
10. <MyComponent>

Página | 358
11. <p>{state.name}</p>
12. </MyComponent/>
13. )
14.
15. }
16. export default MyComponent

El siguiente paso es despachar acciones para actulizar el estado:

1. import { useDispatch, useSelector } from 'react-redux'


2.
3. const MyComponent = (props) => {
4.
5. const dispatch = useDispatch()
6.
7. const state = useSelector(state => state.myState)
8.
9. const dispatchAction = () => {
10. dispatch({
11. type: "ACTION_NAME",
12. value: {...}
13. })
14. }
15.
16. return (
17. <MyComponent>
18. <p>{state.name}</p>
19. <button onClick={dispatchAction} >Update</button>
20. </MyComponent/>
21. )
22.
23. }
24. export default MyComponent

Para despachar una acción, es necesario enviar un objeto que tenga al menos la
propiedad type, la cual es utilizada por los reducers para identificar el cambio
que se quiere realizar, además, es posible enviar cualquier otra propiedad para
complementar la acción, el cual puede tener los nuevos datos para el store.

359 | Página
En este punto, podemos ver que el componente I ha despachado una acción con
la intención de actualizar el estado.

Importante

Los actions deben de tener al menos la propiedad


type, ya que el valor de esta propiedad le indica al
reducer que como debe de actualizar el estado.

Tambien es importante resaltar que el action solo es


una intención para cambiar el estado, por lo que no
hay garantía de que lo actualice, sino que es el reducer
el que determina si deberá hacer algo con el action.

Paso 4: Procesar los actions con los reducers

Una vez que un action es despachado, el store lo recibirá y lo enviará a los


reducers, los cuales, basado en la propiedad type, determinarán, si es necesario
modificar el estado.

Página | 360
Si bien solo puede existir un solo Store con un solo estado, el Store puede tener
varios reducers que modifican el estado. Un reducer es básicamente una función
JavaScript pura, que recibe como entrada el estado actual de la aplicación y el
Action que describe el intento por actualizar el estado, de esta forma, es el
reducer quien determina como deberá ser actualizado el estado basado en el
action.

Algo importante a mencionar es que el estado se estructura basado en los


reducers, de tal forma que, cada reducer modifica una sección del estado, es por
ello que un mismo action es recibido por todos los reducers y cada uno de ellos
podrá determinar si es necesario hacer algún cambio a su parte del estado, por
lo tanto, es posible que un solo action tenga efectos en varias partes del estado.
Pero veamos un ejemplo de un reducer para entender de que estamos hablando:

1. const initialState = {
2. values: []
3. }
4.
5. export const myReducer = (state = initialState, action) => {
6. switch (action.type) {
7. case "INIT":
8. return {
9. values: action.value
10. }
11. case "CREAR":
12. return initialState
13. default:
14. return state
15. }
16. }
17. export default myReducer

361 | Página
Si observas el reducer anterior, te podrás dar cuenta que se trata de función
JavaScript como y corriente, la cual recibe como entrada el state actual y el
action, también podrás ver que el state es igualado a la constante initialValue,
lo que significa que declaramos el estado inicial del store.

Dentro del cuerpo del reduce podemos ver un switch utilizado para realizar una
acción diferente basado en el valor de la propiedad type del action. Si el type del
action corresponde con alguno de los cases, entonces procederemos ha aplicar
algún cambio en el estado del estore, de esta forma, el valor que retornemos
será el que se guardará en el nuevo estado del store, pero si por otra parte, el
type no corresponde con ninguna acción, entonces simplemente retornamos el
mismo estado que recibimos para indicarle al store que no se ha aplicando
ningún cambio.

Un punto que no hemos abordado es que, la estructura del estado dentro del
reducer es determianda por los reducers, de esta forma, un store tiene una forma
de árbol, donde cada reducer crea una rama, por ejemplo, si tenermos 3
reducers, estos crearían 3 ramas con el nombre establecidos al momento de crear
el store. Veamos un ejemplo de como se crear el store:

1. import { createStore, combineReducers } from 'redux'


2. import tweetsReducer from '../redux/reducers/tweetsReducer'
3. import userReducer from '../redux/reducers/userReducer'
4. import configReducer from '../redux/reducers/configReducer'
5.
6. const store = createStore(
7. combineReducers({
8. tweets: tweetsReducer,
9. user: userReducer,
10. config: configReducer
11. })
12. )
13.
14. export default store

Página | 362
Este store dará como resultado un estado con la siguiente estructura:

1. {
2. tweets: {
3. ...
4. },
5. user: {
6. ...
7. },
8. config: {
9. ...
10. }
11. }

Si observar, el nombre de las propiedades que tiene el estado, corresponde con


el nombre con el que creamos el store, por otra parte, la estructura interna de
cada sección será determinado por el valor que retorne cada reducer, por lo
tanto, el tweetsReducer solo podrá modificar la sección tweets, userReducer solo
podrá modicar la sección user y configReducer solo podrá modificar la sección
config. Entonces podemos decir que cada reducer solo puede modificar una
subparte del estado general.

Paso 5: Replicando los cambios a los componentes

Finalmente, cuando los reducer producen el siguiente estado, el store es


actualizado con el nuevo estado y todos los componentes previamente suscritos
al store por medio del componente Provider será notificado de los cambios.

1. import { useSelector } from 'react-redux'


2.
3. const MyComponent = (props) => {
4.
5. const state = useSelector(state => state.myState)
6.
7. return (
8. <MyComponent>
9. <p>{state.name}</p>
10. </MyComponent/>
11. )
12.
13. }
14. export default MyComponent

Recordemos que la suscripción al store se hace mediante el hook useSelector,


por lo tanto, cuando el store se actualizado, el hook se actualizará ejecutando la
función definida y el nuevos estado será retornado y asignado a la variable
definida. Al mismo tiempo, el componente será nuevamente actualizado para
reflegar los nuevos cambios.

363 | Página
Finalmente, cabe mencionar que durante todo el tiempo de vida de la aplicación,
cualquier componente podrá despachar acciones, lo que implicaría que los pasos
3, 4 y 5 se podrían dar decenas, cientos o miles de veces, todo depende de que
con que frecuencia la aplicación cambie.

En resumen, el ciclo de vida de Redux es el siguiente:

Página | 364
365 | Página
Los tres principios de Redux

Adicional al ciclo de vida, es necesario conocer los 3 principios de Redux, los


cuales se deberán cumplir siempre:

Una única fuente de la verdad

Redux funciona únicamente con un solo Store para toda la aplicación, es por eso
que, se le conoce como la única fuente de la verdad. La estructura que
manejemos dentro del Store dependerá totalmente de nosotros, por lo que
somos libre de diseñarla a como se acomode mejor a nuestra aplicación, debido
a esto, suele ser una estructura con varios niveles de anidación.

El estado es de solo lectura (read-only)

Una de las restricciones de Redux es que, no existe una forma para actualizar el
estado directamente, en su lugar, es necesario enviar un action al Store,
describiendo las intenciones de actualizar el estado.

Los cambios se realizan con funciones puras

Como ya lo habíamos mencionado, solo es posible actualizar el estado mediante


un action, el cual manifiesta la intención de cambiar el estado al mismo tiempo
que describe el cambio que quiere realizar. Cuando la acción llega al Store, este
redirige la petición a los reducers.

Los reducers deberán ser siempre funciones puras. Para que una función sea
pura, debe de cumplir con las siguientes características:

1. No deberá de llamar a ningún recurso externo, como pueden ser bases


de datos o servicios.
2. Los valores retornados dependerán únicamente de los parámetros de
entrada, lo que nos lleva al siguiente punto:
3. Ante los mismos parámetros de entrada, deberá dar siempre el mismo
resultado.
4. Los argumentos deben de considerarse inmutables, lo que implica que
no podemos actualizarlos por ninguna razón.

Estos se llaman "puros" porque no hacen más que devolver un valor basado en
sus parámetros. Además, no tienen efectos secundarios en ninguna otra parte
del sistema.

Página | 366
Algo que probablemente no quedo muy claro con respecto al cuarto punto, es
que, dado que el estado actual es un parámetro de entrada para el reducer, no
deberíamos modificarlo, en su lugar, tendríamos que hacer una copia de él y
sobre ese agregar los nuevos cambios. Esto es exactamente lo mismo que
hacíamos con la función update del módulo immutability-helper, por lo que no
debería de presentar una sorpresa para nosotros.

Otro de las cosas a tomar en cuenta es que, los Action deben de tener una
estructura mínima, en la cual debe de existir la propiedad type, seguido de esto,
puede venir lo que sea, incluso, podríamos mandar solo la propiedad type, por
ejemplo:

1. //Option 1
2. {
3. type: "LOGIN"
4. }
5.
6. //Option 2
7. {
8. type: "LOGIN",
9. profile: {
10. id: "1234",
11. userName: "oscar",
12. name: "oscar blancarte"
13. }
14. }

La propiedad type es importante, porque le dice a los reducers la acción que


quieres realizar sobre el estado.

367 | Página
Redux sin hooks

Algo de lo que no hablamos es, como utilizar Redux sin hooks, lo cual puede ser
importante si estamos dando mantenimiento a un proyecto más antiguo o que
por alguna razón se decidio trabajar con componentes de clase.

Antes que nada, la forma de trabajar con clases es muy parecido a como si lo
hiciéramos con hooks, con la única diferencia en que cambiamos la forma en que
nos suscribimos al store y como despachamos las acciones, veamos un ejemplo:

1. import { connect } from 'react-redux'


2.
3. class MyComponent extends React.Component {
4. render(){
5. return {
6. <div>
7. <p>{this.props.name}</p>
8. <button onClick={this.props.myAction}>Click here!!</button>
9. </div>
10. }
11. }
12. }
13.
14. const mapStateToProps = state => {
15. return {
16. name: state.user.name
17. }
18. }
19.
20. const mapDispatchToProps = (dispatch, ownProps) => {
21. return {
22. myAction: () => {
23. dispatch(setVisibilityFilter(ownProps.filter))
24. }
25. }
26. }
27.
28. export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

Lo primero que cambia es que ahora, tenemos que usar el fomoso infierno de los
envoltorios para retornar nuetro componente envuelto en el componenten
connect (línea 28). Connect es una función que retorna otra función, la cual a su
vez, recibe un componte como parámetro y finalmente retorna un nuevo
componente que envuelve a nuestro componente.

Connect recibe dos parámetros, mapStateToProps, la cual es una función que


deberá indicar que secciones del estado del store deberán ser pasadas al
componente como props y finalmente, mapDispatchToProps, permite determinar
que operaciones deberán ser enviadas al componente como props, con la
intención de despachar eventos. Si observar, connect pasa tanto el estado del
store como las funciones para despachar acciones como props.

Podríamos decir que mapStateToProps es muy parecidos a usar useSelector, con


la única diferencia de que mapStateToProps vacia los resultados como props.

Página | 368
Por otra parte, mapDispatchToProps es muy parecido a usar useDispatch, con la
única diferencia de que mapDispatchToProps vaciará las operaciones como props.

Por lo demás, Redux funciona exactamente igual tanto en componentes de clase


como con el uso de hooks.

369 | Página
Mini Twitter (Continuación 8)

Una vez que hemos explicado como funciona Redux, vamos a pasar a
implementarlo en nuestro proyecto. Sin embargo, me gustaría comentar algo,
hay personas que les gusta utilizar Redux en absolutamente toda la aplicación y
hay personas que lo reservan únicamente para las pantallas mas complejas. En
lo particular, a mi gusta utilizar Redux solo en las pantallas más complejas, donde
se requiere que varios componentes se comuniquen entre sí y dejar las páginas
más simples con el estado tradicional, pues utilizar Redux para todas las páginas
agrega una complejidad no necesaria.

Instalando las dependencias

Para completar lo que haremos en esta unidad, será necesario instalar las
siguiente dependencias.

npm install --save redux@4.0.5


npm install --save react-redux@7.2.0
npm install --save redux-logger@3.0.6
npm install --save redux-thunk@2.3.0

Por ahora, simplemente las instalaremos y mas adelante regresaremos a ellas


para explicar para que sirve cada una de ellas.

Página | 370
Creando el Store y los reducers

El primer paso para comenzar a migrar nuestro proyecto a Redux es crear el


store, es por ello que comensaremos creando el archivo store.js en el path
/app/Redux.

1. import { createStore, applyMiddleware, compose } from 'redux'


2. import thunk from 'redux-thunk'
3. import { createLogger } from 'redux-logger'
4. import reducers from '../redux/reducers/index'
5.
6. const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
7.
8. const middleware = [thunk];
9. if (process.env.NODE_ENV !== 'production') {
10. middleware.push(createLogger());
11. }
12.
13. const store = createStore(
14. reducers,
15. composeEnhancers(
16. applyMiddleware(...middleware)
17. )
18. )
19. export default store

El store tiene varios elementos adicionales que no son obligatorios, pero los
hemos agregado para habilitar alguna herramientas de debuger muy
interesantes que vamos a explicar más adelante, por lo que procederemos a
explicar que estamos haciendo.

Para la creación del store solo es necesario la línea 13, donde utilizamos el
método createStore para su creación, además, requeriría al menos un reducer
como parámetro, por ello le enviamos el reducer de la línea 14, fuera de esto, lo
demás es para agregar algunas características adicionales. Por ejemplo, la línea
6 la utilizamos para habilitar un plugin de Chrome para analizar en tiempo real
el estado del store, la línea 8 la utilizamos para agregar redux-thunk, una librería
que nos permite agregar acciones más avanzadas que explicaremos más
adelante, finalmente, la línea 10 es para agregar redux-logger, otra herramienta
de debuger para Redux. A medida que avancemos en el proyecto, iremos
analizando estas herramientas adicionales.

Si nos fijamos en la línea 14, vemos que estamos creando un reducer a partir del
archivo index.js que importamos en la línea 4, el cual es un nuevo archivo que
deberemos de crear en el path /app/Redux/reducer, el cual se ve de la siguiente
forma:

1. import { combineReducers } from 'redux'


2. import userPageReducer from './userPageReducer'
3. import tweetsReduce from './tweetsReduce'
4.
5. export default combineReducers({
6. userPage: userPageReducer,

371 | Página
7. tweets: tweetsReduce
8. })

Debido a que nuestro proyecto requiere de más de un reducer, es necesario


combinarlos con el método combineReducers, el cual recibe como párametro un
objeto los reducer que vamos a registrar.

La estructura de este objeto es importante, por que determinará la estructura de


nuestro reducer, por lo que en nuestro caso, el estado del store será:

1. {
2. userPage: {
3. ...
4. }
5. tweets: {
6. ...
7. }
8. }

Ahora bien, la estructura interna del atributo UserPage y tweets dependerá del
valor retornado por el reducer correspondiente. Hora bien, este reducer se crea
a patir de userPageReducer y tweetsReducer, los cuales será necesario definir.

Comenzaremos con crear el archivo userPageReducer en el path


/app/redux/reducers, y ser verá de la siguiente manera:

1. import {
2. USERPAGE_RESET,
3. LOAD_USERPROFILE,
4. LOAD_FOLLOWERS,
5. LOAD_FOLLOWINGS,
6. TOGGLE_EDIT_MODE,
7. FOLLOW_USER,
8. EDIT_PROFILE,
9. SAVE_PROFILE,
10. } from '../consts'
11. import update from 'immutability-helper'
12.
13. const initialState = {
14. edit: false,
15. profile: null,
16. followers: null,
17. followings: null
18. }
19.
20. export const userPageReducer = (state = initialState, action) => {
21. switch (action.type) {
22. case USERPAGE_RESET:
23. return initialState
24. case TOGGLE_EDIT_MODE:
25. if (!state.edit) {
26. //change to edit mode and backup current state
27. return update(state, {
28. edit: { $set: !state.edit },
29. profileBackup: { $set: state.profile }
30. })
31. } else {
32. //cancel edir mode and restore previous state

Página | 372
33. return update(state, {
34. edit: { $set: !state.edit },
35. profile: { $set: state.profileBackup }
36. })
37. }
38. case SAVE_PROFILE:
39. return update(state, {
40. edit: { $set: false },
41. profileBackup: { $set: undefined }
42. })
43. case LOAD_USERPROFILE:
44. return update(state, {
45. profile: { $set: action.value }
46. })
47. case FOLLOW_USER:
48. return update(state, {
49. profile: {
50. follow: { $set: action.value }
51. }
52. })
53. case EDIT_PROFILE: {
54. return update(state, {
55. profile: {
56. [action.value.id]: { $set: action.value.value }
57. }
58. })
59. }
60. case LOAD_FOLLOWERS: {
61. return update(state, {
62. followers: { $set: action.value }
63. })
64. }
65. case LOAD_FOLLOWINGS: {
66. return update(state, {
67. followings: { $set: action.value }
68. })
69. }
70. default:
71. return state
72. }
73. }
74. export default userPageReducer

Podemos ver claramente la estructura que ya habíamos platicado en el pasado,


donde un reducer es una función JavaScript pura que recibe el state y el action,
y apartir del action (línea 21) determina como actualizar el state. Tambien
podemos observar que siempre se debe de retornar un nuevo estado, este nuevo
estado será el que el store tomará para crear el nuevo estado de la aplicación,
sin embargo, si determinamos que no debemos hacer nada con el action,
entonces simplemente retornamos el mismo estado que recibimos como
parámetro de entrada.

Ahora procederemos con la creación tweetsReducer.js en el path


/app/redux/reducers:

1. import {
2. LOAD_TWEETS,
3. ADD_NEW_TWEET,

373 | Página
4. LIKE_TWEET_REQUEST,
5. RESET_TWEETS
6. } from '../consts'
7.
8. import update from 'immutability-helper'
9.
10. const initialState = {
11. tweets: [],
12. hasMore: false
13. }
14.
15. export const tweetsReducer = (state = initialState, action) => {
16. switch (action.type) {
17. case LOAD_TWEETS:
18. let newState = action.reset
19. ? action.tweets
20. : update(state.tweets, { $push: action.tweets })
21.
22. return {
23. tweets: newState,
24. hasMore: action.tweets.length >= 10
25. }
26. case LIKE_TWEET_REQUEST:
27. let targetIndex =
28. state.tweets.map(x => { return x._id }).indexOf(action.tweetId)
29. return update(state, {
30. tweets: {
31. [targetIndex]: {
32. likeCounter: { $set: action.likeCounter },
33. liked: { $apply: (x) => { return !x } }
34. }
35. }
36. })
37. case ADD_NEW_TWEET:
38. return update(state, {
39. tweets: { $splice: [[0, 0, action.value]] }
40. })
41. case RESET_TWEETS:
42. return initialState
43. default:
44. return state
45. }
46. }
47.
48. export default tweetsReducer

Nuevamente observamos las mismas cosas, la función reducer que regresa un


nuevo estado basado en el action.

Algo de lo que tenemos que tener claro es que, cuando un action sea despachado,
todos los reducers recibirán el action, por lo que es posible que más de un
reducer pueda actuar en consecuencia, aunque lo más normal es que un solo
reducer procesa cada action.

Quiero que observes que hemos estado utilizando una serie de constantes para
los cases del switch, esto lo hacemos así para asegurarnos de tener un mejor
control de las acciones que podemos mandar y no tener un error al mandar el
type, es por ello que vamos a crear el archivo consts.js en el path /app/redux:

Página | 374
1. // UserPage
2. export const USERPAGE_RESET = 'USERPAGE_RESET'
3. export const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE'
4. export const LOAD_USERPROFILE = 'LOAD_USERPROFILE'
5. export const LOAD_FOLLOWERS = 'LOAD_FOLLOWERS'
6. export const LOAD_FOLLOWINGS = 'LOAD_FOLLOWINGS'
7. export const FOLLOW_USER = 'FOLLOW_USER'
8. export const EDIT_PROFILE = 'EDIT_PROFILE'
9. export const SAVE_PROFILE = 'SAVE_PROFILE'
10.
11. // Tweets
12. export const RESET_TWEETS = 'RESET_TWEETS'
13. export const LOAD_TWEETS = 'LOAD_TWEETS'
14. export const ADD_NEW_TWEET = 'ADD_NEW_TWEET'
15. export const LIKE_TWEET_REQUEST = 'LIKE_TWEET_REQUEST'

La importancia de hacerlo como constantes es que vamos a necesitar estos


mismo types cuando despachemos los eventos, de esta forma podemos tener un
mejor control sobre los tipos de acciones que podemos lanzar.

Implementando el Provider

Una vez que el store ya está listo, procederemos a crear el objeto Provider, con
la intención de que nuestros componentes se puedan registrar a los cambios en
el estado del store. Para esto, vamos a regresar al archivo TwitterApp.js y
agregar las siguientes líneas:

1. import React, { useEffect } from 'react'


2. import browserHistory from './History'
3. import { Route, Switch, Redirect } from "react-router-dom";
4. import Signup from './Signup'
5. import Login from './Login'
6. import UserPage from './UserPage'
7. import TwitterDashboard from './TwitterDashboard'
8. import Toolbar from './Toolbar';
9. import UserContext from './context/UserContext'
10. import useLogin from './hooks/useLogin'
11. import AuthRoute from './AuthRoute'
12. import { Provider } from 'react-redux'
13. import store from './redux/store'
14.
15.
16. const TwitterApp = (props) => {
17.
18. const [load, user] = useLogin()
19.
20. const render = () => {
21. if (!load) return null
22.
23. return (
24. <UserContext.Provider value={user}>
25. <Provider store={store} >
26. <Toolbar />
27. <div id="mainApp" className="animated fadeIn">
28. <Switch>

375 | Página
29. <AuthRoute isLoged={user != null} exact path="/"
30. component={TwitterDashboard} />
31. <Route exact path="/signup" component={Signup} />
32. <Route exact path="/login" component={Login} />
33. <AuthRoute isLoged={user != null} path="/:user"
34. component={UserPage} />
35. </Switch>
36. </div>
37. </Provider>
38. </UserContext.Provider>
39. )
40. }
41.
42. return render()
43. }
44. export default TwitterApp

Recordemos que el componente Provider (línea 25) recibe como parámetro el


store, pero como ya lo hemos creado previamente, simplemente será necesario
importarlo (línea 13) y asignarlo.

En este punto ya deberíamos de poder ver los cambios en el navegador, sin


embargo, como solo cambio la forma en que se administra el estado de la
aplicación, no podremos ver un cambio visual en ella, por lo que podrá parecer
que no hemos hecho nada, sin embargo, por dentro, hemos eliminado el estado
del componente para administrarlo desde el store. Para analizar como esta
funcionando Redux, vamos a implementar dos herramientas para debugear los
cambios, la primera es redux-logger y la otra es un plugin de Chrome llamdo
Redux DevTool.

Redux logger

Redux logger es un módulo que permite debugear el store a media que los
actions son depachados al store. De esta forma, el plugin imprimirá en la consola
el valor del estado previo a un action, el action despachado y el nuevo valor del
estado una vez que el action ha sido aplicado.

La instalación de redux-logger ya la hicimos al comienzo de esta unidad, pero


vamos a reperir cuales fueron los pasos a realizar. Lo primero fue instalar la
librería con:

npm install --save redux-logger@3.0.6

El siguiente paso es registrar la librería al momento de crear el store, si


recordamos el archivo store.js, se ve de la siguiente manera:

1. import { createStore, applyMiddleware, compose } from 'redux'


2. import thunk from 'redux-thunk'

Página | 376
3. import { createLogger } from 'redux-logger'
4. import reducers from '../redux/reducers/index'
5.
6. const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
7.
8. const middleware = [thunk];
9. if (process.env.NODE_ENV !== 'production') {
10. middleware.push(createLogger());
11. }
12.
13. const store = createStore(
14. reducers,
15. composeEnhancers(
16. applyMiddleware(...middleware)
17. )
18. )
19. export default store

Lo que hay que hacer el momento de crear el store es muy simple, ya que solo
tenemos que importar el método createLogger (línea 3), y condicionar su registro
(línea 10) para que solo se ejecute cuando la aplicación corra en un entorno no
productivo (Al final del libro hablaremos de como correr la aplicación en
producción). Finalmente, agregamos el logger cuando creamos el store (línea
16). Y eso será todo.

Para comprobar como esta librería funciona, basta con ir al navegador, abrir la
consola del navegador y dirigirse al perfil de cualquier usuario:

Ahora bien, si expandimos cada uno de los tres nodos, podremos ver el detalle,
de cada uno:

377 | Página
Estos registros se imprimierán en la consola cada vez que una acción sea
depachada al store, así que es una excelente forma de saber que está pasando
dentro del store y como el estado se esta afectando a medida que las acciones
son despachadas.

Redux DevTool

Otra forma de debugear Redux es mediante el plugin de Chrome llamado Redux


DevTool, el cual tenemos que instalar desde la Chrome Web Store y realizar un
pequeñoa juste al momento de crear el store.

Desde la página le damos click en Anadir a Chrome y veremos como se agrega


el ícono de la extensión en la barra de navegación del navegador. De momento
se verá innactiva (gris), pero en cuanto configuremos nuestra página para utilizar
el plugin, veremos que se activará.

Para configurar el plugin tenemos que registrar el plugin al momento de crear el


store, por lo que nuevamente tendremos que regresar al archivo store.js:

1. import { createStore, applyMiddleware, compose } from 'redux'


2. import thunk from 'redux-thunk'
3. import { createLogger } from 'redux-logger'
4. import reducers from '../redux/reducers/index'

Página | 378
5.
6. const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
7.
8. const middleware = [thunk];
9. if (process.env.NODE_ENV !== 'production') {
10. middleware.push(createLogger());
11. }
12.
13. const store = createStore(
14. reducers,
15. composeEnhancers(
16. applyMiddleware(...middleware)
17. )
18. )
19. export default store

La clave de la configuración de este plugin esta en la línea 6, ya que el plugin


Redux DevTool inyecta un objeto al navegador llamado
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__, el cúal intentamos recuperar, sin
embargo, como puede que el plugin no este instalado en todos los equipos, es
importante hacer algo en caso de no encontrarlo, es por ello que si no está
disponible ese objeto, vamos a regresar compose. Finalmente, registramos el
plugin en la línea 15.

Si hicimos todo bien, deberemos ver como el ícono del plugin se habilita en el
navegador, y al dar click en, podremos ver en tiempo real el valor del store:

En esta nueva pantalla podremos hacer mucho más cosas que solo ver el estado
actual, si no que del lado izquierdo podemos ver las acciones lanzadas, y en la
parte de abajo tenemos como un reproductor, que nos permite retroceder y
adelantar entre los diferentes estado de la aplicación, de esta forma podemos
analizar con más cuidado y ver como la aplicación se actualiza a medida que
retrocedemos o adelantamos el estado.

379 | Página
Migrando el componente UserPage

Para comenzar la migración a Redux, iniciaremos con el componente UserPage,


ya que este es un componente complejo que tiene varias secciones. Al migrar a
Redux buscaremos que los components UserPage, Followers, Followings y
MyTweets, compartan el mismo estado, de esta forma, se podrán compartir
información.

Para realizar la migración de un componente hay que seguir una serie de pasos,
los cuales se repetirán para todos los componentes que migremos a Redux, los
cuales son:

4. Conectar el componente con Redux, por lo que deberemos utilizar el hook


useSelector en el caso de componentes de función, o la función connect en
el caso de componentes de clase.
5. Separar toda la lógica de negocio que afecte el estado en funciones de
acciones independientes. Por lo general migramos todo lo que este en el
método componentDidMount, componentDidUpdate como una función de
acción.
6. Deberemos de dejar el estado (this.state o useState) y dejar que el store
administre el estado. Al mismo tiempo que deberemos implementar los
reducers correspondientes para atualizar el estado.
7. Para afectar el estado deberemos despachar acciones.

Ahora bien, aplicaremos los pasos anteriores para el componente UserPage:

1. import React, { useEffect } from 'react'


2. import update from 'immutability-helper'
3. import APIInvoker from './utils/APIInvoker'
4. import { NavLink } from 'react-router-dom'
5. import MyTweets from './MyTweets'
6. import { Route, Switch } from 'react-router-dom'
7. import Followings from './Followings'
8. import Followers from './Followers'
9. import TweetDetail from './TweetDetail'
10. import Modal from './Modal'
11. import UserContext from './context/UserContext'
12. import { useDispatch, useSelector } from 'react-redux'
13. import { loadProfile, toggleEditMode, follow, handleInput, save, rest } from './r
edux/actions/userPageActions'
14.
15. import {resetTweets} from './redux/actions/tweetsActions'
16.
17. const UserPage = (props) => {
18.
19. const userContext = React.useContext(UserContext)
20.
21. const dispath = useDispatch()
22. const profile = useSelector(state => state.userPage.profile)
23. const edit = useSelector(state => state.userPage.edit)
24.
25. useEffect(() => {

Página | 380
26. dispath(resetTweets())
27. dispath(rest())
28. dispath(loadProfile(props.match.params.user))
29.
30. return () => {
31. dispath(rest())
32. }
33. }, [props.match.params.user])
34.
35. const handleImageChange = (e) => {
36. e.preventDefault();
37.
38. let file = e.target.files[0];
39. if (file.size > 1240000) {
40. alert('La imagen supera el máximo de 1MB')
41. return
42. }
43.
44. let reader = new FileReader()
45. reader.onloadend = () => {
46. dispath(handleInput(e.target.id, reader.result))
47. }
48. reader.readAsDataURL(file)
49. }
50.
51.
52. const handleInputChange = (e) => {
53. let id = e.target.id
54. let value = e.target.value
55. dispath(handleInput(id, value))
56. }
57.
58. const render = () => {
59. if(profile === null) return null
60.
61. return (
62. <div id="user-page" className="app-container animated fadeIn">
63. <header className="user-header">
64. <div className="user-banner" style={{
65. backgroundImage: 'url(' + (profile.banner) + ')' }}>
66. <If condition={edit}>
67. <div>
68. <label htmlFor="banner" className="btn select-banner">
69. <i className="fa fa-camera fa-2x" aria-hidden="true"></i>
70. <p>Cambia tu foto de encabezado</p>
71. </label>
72. <input href="#" className="btn"
73. accept=".gif,.jpg,.jpeg,.png"
74. type="file" id="banner"
75. onChange={handleImageChange} />
76. </div>
77. </If>
78. </div>
79. <div className="user-summary">
80. <div className="container-fluid">
81. <div className="row">
82. <div className="hidden-xs col-sm-4 col-md-push-1
83. col-md-3 col-lg-push-1 col-lg-3" >
84. </div>
85. <div className="col-xs-12 col-sm-8 col-md-push-1
86. col-md-7 col-lg-push-1 col-lg-7">
87. <ul className="user-summary-menu">
88. <li>
89. <NavLink to={`/${profile.userName}`} exact
90. activeClassName="selected">
91. <p className="summary-label">TWEETS</p>

381 | Página
92. <p className="summary-value">{profile.tweetCount}</p>
93. </NavLink>
94. </li>
95. <li>
96. <NavLink to={`/${profile.userName}/following`}
97. activeClassName="selected">
98. <p className="summary-label">SIGUIENDO</p>
99. <p className="summary-value">{profile.following}</p>
100. </NavLink>
101. </li>
102. <li>
103. <NavLink to={`/${profile.userName}/followers`}
104. activeClassName="selected">
105. <p className="summary-label">SEGUIDORES</p>
106. <p className="summary-value">{profile.followers}</p>
107. </NavLink>
108. </li>
109. </ul>
110.
111. <If condition={profile.userName === userContext.userName}>
112. <button className="btn btn-primary edit-button"
113. onClick={() =>
114. dispath(edit ? save() : toggleEditMode())} >
115. {edit ? "Guardar" : "Editar perfil"}</button>
116. </If>
117.
118.
119. <If condition={profile.follow != null &&
120. profile.userName !== userContext.userName} >
121. <button className="btn edit-button"
122. onClick={() => dispath(follow())} >
123. {profile.follow
124. ? (<span><i className="fa fa-user-times"
125. aria-hidden="true"></i> Siguiendo</span>)
126. : (<span><i className="fa fa-user-plus"
127. aria-hidden="true"></i> Seguir</span>)
128. }
129. </button>
130. </If>
131.
132. <If condition={edit}>
133. <button className="btn edit-button" onClick=
134. {() => dispath(toggleEditMode())} >Cancelar</button>
135. </If>
136. </div>
137. </div>
138. </div>
139. </div>
140. </header>
141. <div className="container-fluid">
142. <div className="row">
143. <div className="hidden-xs col-sm-4 col-md-push-1 col-md-3
144. col-lg-push-1 col-lg-3" >
145. <aside id="user-info">
146. <div className="user-avatar">
147. <Choose>
148. <When condition={edit} >
149. <div className="avatar-box">
150. <img src={profile.avatar} />
151. <label htmlFor="avatar"
152. className="btn select-avatar">
153. <i className="fa fa-camera fa-2x"
154. aria-hidden="true"></i>
155. <p>Foto</p>
156. </label>
157. <input href="#" id="avatar"

Página | 382
158. className="btn" type="file"
159. accept=".gif,.jpg,.jpeg,.png"
160. onChange={handleImageChange}
161. />
162. </div>
163. </When>
164. <Otherwise>
165. <div className="avatar-box">
166. <img src={profile.avatar} />
167. </div>
168. </Otherwise>
169. </Choose>
170. </div>
171. <Choose>
172. <When condition={edit} >
173. <div className="user-info-edit">
174. <input maxLength="20" type="text" value={profile.name}
175. onChange={handleInputChange} id="name" />
176. <p className="user-info-username">
177. @{profile.userName}</p>
178. <textarea maxLength="180" id="description"
179. value={profile.description}
180. onChange={handleInputChange} />
181. </div>
182. </When>
183. <Otherwise>
184. <div>
185. <p className="user-info-name">{profile.name}</p>
186. <p className="user-info-username">
187. @{profile.userName}</p>
188. <p className="user-info-description">
189. {profile.description}</p>
190. </div>
191. </Otherwise>
192. </Choose>
193. </aside>
194. </div>
195. <div className="col-xs-12 col-sm-8 col-md-7
196. col-md-push-1 col-lg-7">
197. <Switch>
198. <Route exact path="/:user" component={
199. () => <MyTweets profile={profile} />} />
200. <Route exact path="/:user/followers" component={
201. () => <Followers profile={profile} />} />
202. <Route path="/:user/following" component={
203. () => <Followings profile={profile} />} />
204. <Route exact path="/:user/tweet/:tweet" component={
205. (params) => <Modal> <TweetDetail {...params} /> </Modal>} />
206. </Switch>
207. </div>
208. </div>
209. </div>
210. </div>
211. )
212. }
213.
214. return render()
215. }
216. export default UserPage;

El primer paso, era conectar el componente con el store, por eso, en la línea 22
y 23 utilizamos el hook useSelector para recupera el estado del store, de esta
forma, eliminamos la dependencia a un estado interno, y en su lugar, dejamos

383 | Página
que el hook useSelector nos actualize cada vez que el store sea modificado por
una acción.

El siguiente paso será pasar toda la lógica de negocio a un archivo externo de


acciones, en este caso, el archivo userPageActions.js que importamos en en la
línea 13. Observa que de esa clase importamos varios métodos, lo cuales
utilizaremos para despachar acciones y disparar la actualización del store.

Con las acciones separadas en un archivo independiente, podemos despacharlas


mediante el dispatcher, que obtener al usar el hook useDispatch (línea 21).
Observa que en el efecto (useEffect) en lugar de llamar al API, depachamos
acciones (líneas 26,27,28) las dos primeras las utilizamos para limpiar el store
previo a que la página se cargue, pero el tercer dispatch (línea 28) hacemos una
llamada al action loadProfile, el cual se encargará cargar el perfil del usuario.

El otro paso, es modificar todas las llamadas a donde actualizemos el estado, por
un dispatch, con la intención de que sean los reducer quienes procesen los
actions y actualicen el estado por nosotros. Podemos ver como hacemos esto en
las líneas 46 y 55, que es donde gestionamos los cambios de las imágenes del
perfil o el nombre y descripción.

Ahora bien, si vamos al archivo userPageActions.js en el path


/app/redux/actions. Veremos lo siguiente:

1. import APIInvoker from '../../utils/APIInvoker'


2. import {
3. LOAD_USERPROFILE,
4. FOLLOW_USER,
5. TOGGLE_EDIT_MODE,
6. EDIT_PROFILE,
7. SAVE_PROFILE,
8. USERPAGE_RESET,
9. LOAD_FOLLOWERS,
10. LOAD_FOLLOWINGS
11. } from '../consts'
12.
13.
14.
15. export const loadProfile = (username) => (dispatch, getState) => {
16. APIInvoker.invokeGET(`/profile/${username}`, response => {
17. dispatch({
18. type: LOAD_USERPROFILE,
19. value: response.body
20. })
21. }, error => {
22. console.log("Error al cargar los Tweets");
23. window.location = '/'
24. })
25. }
26.
27. export const rest = () => (dispatch, getState) => {
28. dispatch({ type: USERPAGE_RESET })
29. }
30.

Página | 384
31.
32. export const follow = () => (dispatch, getState) => {
33. let request = {
34. followingUser: getState().userPage.profile.userName
35. }
36. APIInvoker.invokePOST('/secure/follow', request, response => {
37. if (response.ok) {
38. dispatch({
39. type: FOLLOW_USER,
40. value: !response.unfollow
41. })
42. }
43. }, error => {
44. console.log("Error al actualizar el perfil");
45. })
46. }
47.
48. export const toggleEditMode = () => (dispatch, getState) => {
49. dispatch({
50. type: TOGGLE_EDIT_MODE
51. })
52. }
53.
54. export const handleInput = (field, value) => (dispatch, getState) => {
55. dispatch({
56. type: EDIT_PROFILE,
57. value: {
58. id: field,
59. value
60. }
61. })
62. }
63.
64. export const save = () => (dispatch, getState) => {
65. const profile = getState().userPage.profile
66.
67. let request = {
68. username: profile.userName,
69. name: profile.name,
70. description: profile.description,
71. avatar: profile.avatar,
72. banner: profile.banner
73. }
74.
75. APIInvoker.invokePUT('/secure/profile', request, response => {
76. dispatch({
77. type: SAVE_PROFILE
78. })
79. }, error => {
80. console.log("Error al actualizar el perfil");
81. })
82. }
83.
84.
85. export const loadFollowers = () => (dispatch, getState) => {
86. let username = getState().userPage.profile.userName
87. APIInvoker.invokeGET(`/followers/${username}`, response => {
88. dispatch({
89. type: LOAD_FOLLOWERS,
90. value: response.body
91. })
92. }, error => {
93. console.log("Error en la autenticación");
94. })
95. }
96.

385 | Página
97. export const loadFollowings = () => (dispatch, getState) => {
98. let username = getState().userPage.profile.userName
99. APIInvoker.invokeGET(`/followings/${username}`, response => {
100. dispatch({
101. type: LOAD_FOLLOWINGS,
102. value: response.body
103. })
104. }, error => {
105. console.log("Error en la autenticación");
106. })
107. }

Si observas, verás que son funciones comunes y corrientes, que pueden o no


hacer una llamada al API, la única diferencia es que estos reciben dos
parámetros, dispatch y getState, los cuales representa una referencia al
dispatcher para enviar actions al store y getState, el cual es un método que
regresa el estado actual del store. Desde estos método podemos hacer llamadas
al API y luego con la respuesta despachar acciones.

Si recordamos, un action debe de tener al menos el type, el cual es utilizado por


los reducers para determinar que acciones deberán hacer sobre el estado.

Las funciones que vemos en este archivo son:

• loadProfile: Carga desde el API el perfil del usuario.


• rest: Inicializa el estado a su valor inicial.
• follow: Acción que permite indicarle al API que comenzamos o dejamos
de seguir a otro usuario.
• toggleEditMode: cambia la bandera de edición del perfil, para pasar del
modo solo lectura a edición.
• handleInput: Actualiza el estado a medida que editamos el nombre o la
descripción del perfil, así como cuando cambiamos la image o el banner.
• save: guarda los cambios tras editar el pefil.
• loadFollowers: Carga desde el API a nuestros seguidores.
• loadFollowings: Carga desde el API a las personas que seguimos.

Así como tenemos el archivo userPageActions, tenemos en la misma carpeta el


archivo tweetsActions.js, donde dejamos las acciones para actualizar los tweets
que veremos en el componente TweetsContainer. Lo hemos separado debido a
que este componente se ve en la página principal para mostrar los tweets de
todos los usuarios, pero también se ve desde la página del perfil del usuario, para
mostrar solo los tweets de un usuario determinado.

1. import {
2. LOAD_TWEETS,
3. RESET_TWEETS,
4. ADD_NEW_TWEET
5. } from '../consts'
6. import APIInvoker from '../../utils/APIInvoker'

Página | 386
7.
8. export const resetTweets = () => (dispatch, getState) => {
9. dispatch({
10. type: RESET_TWEETS
11. })
12. }
13.
14. export const getTweet = (username, onlyUserTweet, page) =>
15. (dispatch, getState) => {
16. let currentPage = page || 0
17. const url = `/tweets${onlyUserTweet ? "/" + username : ""}?page=${currentPage
}`
18. APIInvoker.invokeGET(url, response => {
19. dispatch({
20. type: LOAD_TWEETS,
21. tweets: response.body,
22. reset: currentPage == 0
23. })
24.
25. }, error => {
26. console.log("Error al cargar los Tweets")
27. })
28. }
29.
30. export const addTweet = (newTweet) => (dispatch, getState) => {
31. APIInvoker.invokePOST('/secure/tweet', newTweet, response => {
32. dispatch({
33. type: ADD_NEW_TWEET,
34. value: {
35. ...newTweet,
36. _id: response.tweet._id
37. }
38. })
39. }, error => {
40. console.log("Error al cargar los Tweets");
41. })
42. }

En este caso tenemos las funciones:

• resetTweets: Resete al store a su estado inicial, para limpiar los tweets


que habíamos cargado con anterioridad.
• addTweets: Cuando creamos un nuevo tweet, ese método se ejecuta
para guardar el tweet en el API al mismo tiempo que lo agrega al store
para que se vea en pantalla.
• getTweets: Carga los últimos tweets generales o del usuario que se
pasa como parámetro.

Migrando el componente Followers

El componente Followers es mucho más simple que el UserPage, pues es un


componente más pequeño y con menos funcionalidad, por lo que no debería de
ser un reto migrarlo.

387 | Página
1. import React, { useEffect } from 'react'
2. import UserCard from './UserCard'
3. import PropTypes from 'prop-types'
4. import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'
5. import { useDispatch, useSelector } from 'react-redux'
6. import { loadFollowers } from './redux/actions/userPageActions'
7.
8. const Followers = (props) => {
9.
10. const dispath = useDispatch()
11. const state = useSelector(state => state.userPage.followers)
12.
13. useEffect(() => {
14. if (state === null) {
15. dispath(loadFollowers())
16. }
17. }, [props.profile.userName])
18.
19. return (
20. <section>
21. <div className="container-fluid no-padding">
22. <div className="row no-padding">
23. <CSSTransitionGroup
24. transitionName="card"
25. transitionEnter={true}
26. transitionEnterTimeout={500}
27. transitionAppear={false}
28. transitionAppearTimeout={0}
29. transitionLeave={false}
30. transitionLeaveTimeout={0}>
31. <For each="user" of={state || []}>
32. <div className="col-xs-12 col-sm-6 col-lg-4"
33. key={user._id}>
34. <UserCard user={user} />
35. </div>
36. </For>
37. </CSSTransitionGroup>
38. </div>
39. </div>
40. </section>
41. )
42. }
43.
44. Followers.propTypes = {
45. profile: PropTypes.object
46. }
47.
48. export default Followers;

Nuevamente, debemos de conectar el componente con el store, por lo que


utilizaremos el hook useSelector, con el cual podemos tener acceso al estado y
seleccionar solo la parte del estado que nos interesa. Por otro lado, recuperamos
la referencia al dispatch mediante el hook useDispatch, el cual nos permitirá
despachar acciones para modificar el estado.

Otro de los cambios es que en el efecto cargamos los seguidores despachando la


acción loadFollowers (línea 15), la cual es una acción que creamos en el archivo
userPageActions.js. Por lo demás el componente permanece exactamente igual.

Página | 388
Migrando el componente Followings

A pesar que el componente Followings es casi igual al componente Followers,


hemos decidido aplicar un enfoque diferente para aprender como conectar un
componente de función o de clase mediante el método connect.

El método connect es la única forma que tenemos conectar una componente de


clase y puede ser utilizado también en componentes de función. Para conectar
un componente con este método, tenemos que exportar el componente envuelto
en el método connect. Veamos la línea 51:

1. import React, { useEffect } from 'react'


2. import UserCard from './UserCard'
3. import PropTypes from 'prop-types'
4. import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'
5. import { connect } from 'react-redux'
6. import { loadFollowings } from './redux/actions/userPageActions'
7.
8. const Followings = (props) => {
9.
10. useEffect(() => {
11. if (props.state === null) {
12. props.loadFollowings()
13. }
14. }, [props.profile.userName])
15.
16. return (
17. <section>
18. <div className="container-fluid no-padding">
19. <div className="row no-padding">
20. <CSSTransitionGroup
21. transitionName="card"
22. transitionEnter={true}
23. transitionEnterTimeout={500}
24. transitionAppear={false}
25. transitionAppearTimeout={0}
26. transitionLeave={false}
27. transitionLeaveTimeout={0}>
28. <For each="user" of={props.state || []}>
29. <div className="col-xs-12 col-sm-6 col-lg-4"
30. key={user._id}>
31. <UserCard user={user} />
32. </div>
33. </For>
34. </CSSTransitionGroup>
35. </div>
36. </div>
37. </section>
38. )
39. }
40.
41. Followings.propTypes = {
42. profile: PropTypes.object
43. }
44.
45. function mapStateToProps(state) {
46. return {
47. state: state.userPage.followings
48. }
49. }

389 | Página
50.
51. export default connect(mapStateToProps, {loadFollowings})(Followings)

Observa que el método connect (llínea 51) recibe dos parámetros, y después nos
retorna una nueva función, esta nueva función recibe como parámetro un
componente, es por ello que vemos dos pares de paréntesis.

El parámetro mapStateToProps es equivalente al useSelector, sin embargo, este


es una función que recibe como parámetro el estado y debenos de retornar la
parte del estado que queremos que este disponible en el componente. El segundo
parámetro, es equivalente el useDispatch, ya que nos permite despachar
acciones, por lo que debemos enviarle las acciones que podrá despachar el
componente.

Ahora bien, tanto mapStateToProps como el segundo parámetro de connect, hará


que el estado y las acciones esten disponibles en el componente por medio de
las props, es por ello que el estado lo accedemos mediante this.props.state
(línea 28). Y las acciones también se accederán mediante las props (línea 12).

Sea una clase o un componente de función, funciona de la misma forma,


mandamos el componente como parámetro al connect y el estado y las acciones
estarán disponibles como propiedades.

Migrando el componente TweetsContainer

En este punto ya hemos migrado la sección del perfil del usuario junto con las
secciones de los seguidores y las personas que seguimos, sin embargo, falta por
migrar la sección de los tweets, sin embargo, este componente se reutiliza en la
sección de Mis Tweets y en el Home de cada usuario, por lo que al final, las dos
secciones utilizan el mismo componente TweetsContainer para mostrar los datos,
aunque la información que se muestra varía según la sección en la que lo
mostrarmos.

1. import React, { useEffect } from 'react'


2. import Tweet from './Tweet'
3. import Reply from './Reply'
4. import PropTypes from 'prop-types'
5. import InfiniteScroll from 'react-infinite-scroller';
6. import { useDispatch, useSelector } from 'react-redux'
7. import { getTweet, addTweet } from './redux/actions/tweetsActions'
8.
9. const TweetsContainer = (props) => {
10.
11. const dispatch = useDispatch()
12.
13. const tweets = useSelector(state => state.tweets.tweets)
14. const hasMore = useSelector(state => state.tweets.hasMore)
15.
16. useEffect(() => {
17. const username = props.profile.userName
18. const onlyUserTweet = props.onlyUserTweet

Página | 390
19. dispatch(getTweet(username, onlyUserTweet, 0))
20. }, [props.profile.userName, props.onlyUserTweet])
21.
22. const addNewTweet = (newTweet) => {
23. dispatch(addTweet(newTweet))
24. }
25.
26. const loadMore = (page) => {
27. const username = props.profile.userName
28. const onlyUserTweet = props.onlyUserTweet
29. dispatch(getTweet(username, onlyUserTweet, page - 1))
30. }
31.
32. return (
33. <main className="twitter-panel">
34. <Choose>
35. <When condition={props.onlyUserTweet} >
36. <div className="tweet-container-header">
37. TweetsDD
38. </div>
39. </When>
40. <Otherwise>
41. <Reply profile={props.profile} operations={{ addNewTweet }} />
42. </Otherwise>
43. </Choose>
44. <InfiniteScroll
45. pageStart={1}
46. loadMore={loadMore}
47. hasMore={hasMore}
48. loader={<div className="loader" key={0}>Loading ...</div>} >
49.
50. <For each="tweet" of={tweets}>
51. <Tweet key={tweet._id} tweet={tweet} />
52. </For>
53. </InfiniteScroll>
54. <If condition={!hasMore} >
55. <p className="no-tweets">No hay tweets que mostrar</p>
56. </If>
57. </main>
58. )
59.
60. }
61.
62. TweetsContainer.propTypes = {
63. onlyUserTweet: PropTypes.bool,
64. profile: PropTypes.object
65. }
66.
67. TweetsContainer.defaultProps = {
68. onlyUserTweet: false,
69. profile: {
70. userName: ""
71. }
72. }
73.
74. export default TweetsContainer;

Vemos como el componete fue migrado, pero antes de eso, es importante


recordar como es que este componente trabaja, por ello, recordaremos que este
componente carga los últimos tweets basados en dos propiedades de entrada,
onlyUserTweets y userName, el primero indica si debemos cargar solo los tweets

391 | Página
de un determinado usuario, si es true, entonces tomamos el parámetro userName
para consultar solo los tweets de ese usuaro.

Cuando entramos al home de la aplicación, este componente se carga, pero


desde se visualizan los tweets de todos los usuario, por esta razón las dos
propiedades dentrada no son enviadas, por tal motivo, los default props se
activan (línea 67) y se cargan los tweets de todos los usurios.

Dicho lo anterior, veremos como fue migrado el componente. Lo primero es


convertir el componente de clase a componente de función (línea 9), luego,
obtendremos la referencia al dispatcher (línea 11) para poder lanzar acciones, y
luego obtendremos la referencia al estado mediante el hook useSelector (línea
13 y 14). Hemos creados dos estados, hasMore para determinar si hay más tweets
por cargar y tweets para recuperar los tweets recuperados del API.

Por otra parte, hemos creado un efecto (línea 16) que se encargará despachar
las acciones necesarias para cargar los tweets desde el API, y hemos
condicionado el efecto para que solo se ejecute cuando las dos propiedades que
explicamos hace un momento cambien.

Finalmente, hemos actualizado la función addNewTweets (línea 22) para que en


lugar de que actualice el estado directamente, lanze una acción al store para que
el nuevo tweet sea guardado en el API y finalmente actualizado en el store.

Migrando el componente TwitterDashboard

En realidad en este componente solo necesitamos hacer un pequeño ajuste para


limpiar el store justo después de desmontar el componente, con la intención de
limpiar los tweets de la página anterior antes de comenzar a cargar los tweets
de la siguiente página, y de esta forma, no ver tweets repetidos entre diferentes
páginas.

1. import React, {useEffect} from 'react'


2. import Profile from './Profile'
3. import TweetsContainer from './TweetsContainer'
4. import SuggestedUser from './SuggestedUser'
5. import UserContext from './context/UserContext'
6. import { useContext } from 'react'
7. import {resetTweets} from './redux/actions/tweetsActions'
8. import { useDispatch } from 'react-redux'
9.
10. const TwitterDashboard = (props) => {
11.
12. const userContext = useContext(UserContext)
13. const dispatch = useDispatch()
14.
15. useEffect(() => {

Página | 392
16. return () => dispatch(resetTweets())
17. })
18.
19. return (
20. <div id="dashboard" className="animated fadeIn">
21. <div className="container-fluid">
22. <div className="row">
23. <div className="hidden-xs col-sm-4 col-md-push-1
24. col-md-3 col-lg-push-1 col-lg-3" >
25. <Profile profile={userContext} />
26. </div>
27. <div className="col-xs-12 col-sm-8 col-md-push-1
28. col-md-7 col-lg-push-1 col-lg-4">
29. <TweetsContainer profile={userContext} onlyUserTweet={false}/>
30. </div>
31. <div className="hidden-xs hidden-sm hidden-md
32. col-lg-push-1 col-lg-3">
33. <SuggestedUser />
34. </div>
35. </div>
36. </div>
37. </div>
38. )
39. }
40.
41. export default TwitterDashboard;

En este componente el único cambio relevante es la llamada de la acción


resetTweets en el efecto.

393 | Página
Resumen

Redux es sin duda una de las herramientas más potentes a la hora de desarrollar
aplicaciones con React, pues permite tener un control mucho más estricto del
estado y nos evitamos la necesidad de pasar una gran cantidad de propiedades
a los componentes hijos, haciendo que el desarrollo y el mantenimiento sea
mucho menos complejo. De la misma forma, logramos desacoplar a los
componentes con su dependencia de los padres.

Redux puede llegar a ser un reto la primera vez que lo utilizamos, pero a medida
que nos acostumbramos a utilizarlo, resulta cada vez más difícil desarrollar sin
él.

A pesar que Redux es la herramienta por excelencia en el desarrollo web de hoy,


puede ser que nuevas herramientas nazcan y ofrezcan mejores soluciones, a si
como en su momento fue Flux, hoy es Redux, y mañana puede ser otra cosa, lo
importante es estar actualizados y está siempre abierto a nuevas opciones.

Quiero felicitarte si has llegado hasta aquí, quiere decir que ya has aprendido
prácticamente todo lo necesario para crear aplicaciones con React. Si bien,
todavía no vemos la parte del backend con NodeJS, quiero recordarte que React
es una librería totalmente diseñada para el FrontEnd, por lo que en este punto,
ya podrías llamarte un FrontEnd developer.

Página | 394
Introducción a NodeJS
Capítulo 15

Hasta este momento, hemos construido una aplicación completa utilizando React
y conectándola a un API REST, sin embargo, poco o nada hemos visto acerca de
NodeJS y como este juega un papel crucial en el desarrollo de aplicaciones web.

Porque es importante aprender NodeJS

NodeJS se ha venido convirtiendo rápidamente en una de las tecnologías más


populares para las grandes empresas, incluso, muchas de las Startups que están
naciendo, están utilizando NodeJS como parte de su Stack tecnológico, pues les
permite desarrolla soluciones rápidamente y con un costo de despliegue muy
económico. Incluso, muchos inventos que utilizan hardware como Arduino o
Raspberry PI están utilizando NodeJS para ejecutar el código que los hace
funcionar, pues una de las principales ventajas que tiene NodeJS es que es
extremadamente ligero y súper eficiente en el uso de los recursos.

Ahora bien, puede que sea una persona que no piensa emprender en este
momento y lo que busques es aprender una nueva tecnología para buscar un
mejor trabajo. En ese caso, déjame decirte que NodeJS es ya hoy en día una de
las tecnologías más buscada por las grandes empresas como Google, Amazon,
Microsoft, etc. Esto quiere decir que aprender NodeJS es sin duda una de las
mejores inversiones que puedes hacer.

395 | Página
Fig. 94 - Posiciones abiertas

La imagen es una gráfica publicada por indeed.com, una famosa página de


trabajos, en la cual se aprecia las posiciones abiertas para las principales
tecnologías web. Lo interesante de esta gráfica, es la forma tan explosiva que ha
crecido la demanda de NodeJS. Esta gráfica nos da una idea bastante clara de lo
que está pasando en el mercado.

El Rol de NodeJS en una aplicación

Como ya lo mencionamos al inicio de este libro, NodeJS es un entorno de


ejecución de JavaScript del lado del servidor, lo que implica que cualquier cosa
que se pueda programar en JavaScript se podrá ejecutar en NodeJS. Eso lo hace
una herramienta muy potente, pero seguramente cuando escuchas eso, se te
venga a la mente que NodeJS crea interfaces gráficas, pues JavaScript es
utilizado para eso habitualmente.

Sin embargo, por muy extraño que parezca, NodeJS no es utilizado para eso,
pues no es un navegador, por lo que no puede renderizar elementos en pantalla,
lo que sí, es que mediante NodeJS podemos servir páginas web al navegador.
Pero no solo queda allí la cosa, puede ser utilizado para aplicaciones no web y
ser montado en sistemas embebidos, o en nuestro caso, nos puede servidor como
base para crear todo un API REST.

En nuestro caso, usaremos NodeJS con dos propósitos, el primero y más claro
hasta el momento, es crear nuestra API REST y el segundo, lo utilizamos para
servir nuestra aplicación Mini Twitter al cliente como ya lo hemos estado haciendo
hasta el momento.

Página | 396
NodeJS es un mundo

NodeJS junto con su todo su mundo de librerías que ofrece NPM puede llegar a
ser abrumador, pues NPM es el repositorio de librerías Open Source más grande
del mundo. Debido a esto, es imposible hablar de todo lo que nos tiene por
ofrecer NodeJS o al menos lo más importante. Por esta razón, nos centraremos
exclusivamente en el desarrollo de API’s con NodeJS y explicaremos alguna que
otra curiosidad que sea necesaria a medida que sea requerido.

Hoy en día hay muchísimos libros que hablan exclusivamente de NodeJS o de


alguna de sus librerías, y a pesar de que son libros exclusivos de NodeJS, dejan
muchas cosas por fuera, ya que NodeJS es un mundo.

Introducción a Express

Como ya lo platicamos, nos centraremos en el desarrollo de APIs con NodeJS, y


Express es sin duda una de las librerías por excelencia para el desarrollo web y
la construcción de API en NodeJS. Express es definido en página oficial
(http://expressjs.com/es/) como:

Infraestructura web rápida, minimalista y flexible para Node.js

Algo que debemos de tomar en cuenta, es que, NodeJS solo el entorno de


ejecución de JavaScript, mientras que Express una librería que complementa a
NodeJS para el desarrollo de aplicaciones web y desarrollo de API’s. Por sí solo,
NodeJS solo es capaz de ejecutar JavaScript, pero no cuenta con las librerías
necesarias para desarrollar aplicaciones web.

En la actualidad existe más librerías para el desarrollo web y API’s con NodeJS,
lo que quiere decir que Express no es la única opción que tenemos. En realidad,
existen tantas opciones que es muy fácil perderse. Solo para nombrar algunas
alternativas están:

• Koa (http://koajs.com/)
• Hapi (https://hapijs.com/)
• Restify (http://mcavage.me/node-restify/)
• Sailsjs (https://sailsjs.com/)
• Strapi (https://strapi.io/)

Estos son tan solo algunos ejemplos rápidos, pero existe una infinidad de librerías
más que nos puede servir para este propósito, por lo que alguien recién llegado
a NodeJS simplemente no sabría cual elegir

397 | Página
Ahora bien, ¿Por qué deberíamos utilizar Express en lugar de cualquier otra?, la
respuesta es simple, Express es la librería más ampliamente utilizada y con la
mayor comunidad de desarrolladores, esto hace que este en constante evolución
y madure a una velocidad más rápida que las demás. Ahora bien, nada está
escrito en esta vida, por lo que siempre hay que estar atento a las cosas que
pasan, pues cualquier día de estos, otra librería pueda superar a Express, pero
por ahora, Express es la más conveniente.

Instalando Express

Una vez que te he convencido de usar Express (o al menos eso creo) pasaremos
a la instalación. Dado que Express es una librería más de NodeJS, esta puede ser
instalada mediante NPM como lo hemos estado haciendo para el resto te librerías
que hemos estado utilizando hasta el momento. Para ello, solo basta con instalar
usando el siguiente comando:

npm install --save-dev express@4.17.1

Esta instalación ya la habíamos realizado cuando hablamos de Router, por lo que


es probable que al ejecutarlo, no veas ningún cambio en el archivo package.json.

El archivo package.json

Este archivo ya lo hemos explicado en el pasado, sin embargo, como estamos en


la sección de NodeJS, sería bueno dar una repasada, pues este archivo es el más
importante cuando trabajamos con NodeJS.

Muchas personas creen que el archivo package.json, es solo para colocar las
dependencias y configurar algunos scripts para ejecutar el programa, sin
embargo, esto va más allá. Este archivo está pensado para ser un identificador
de nuestro proyecto, pues este, al ser compilado, pasa a ser una librería, y como
toda librería, es posible subirla a los repositorios de NPM. Espera un momento
¿Me estás diciendo que yo puedo crear y subir mi proyecto como una librería a
NPM?, es correcto, nosotros podríamos ahora mismo crear una nueva librería, ya
sea un proyecto completo o una simple utilidad y publicarla para que todo el
mundo la pueda descargar, y por qué no, contribuir en su desarrollo.

Ahora bien, ya que sabemos que el archivo package.json es un descriptor de


nuestro proyecto, debemos de saber que existen ciertas reglas que hay que
seguir. Las más importantes son tener un nombre y una versión, pues estos dos
campos son el identificador de nuestro proyecto.

Página | 398
name

Las reglas para el nombre (name) son las siguientes:

• El nombre debe ser menor o igual a 214 caracteres.


• El nombre no puede comenzar con un punto o un guion bajo.
• Los nuevos paquetes no deben tener letras mayúsculas en el nombre (no
CamelCase).
• El nombre termina siendo parte de una URL, un argumento en la línea de
comando y un nombre de carpeta. Por lo tanto, el nombre no puede
contener ningún carácter que no sea seguro para una URL.

Al momento de establecer un nombre para nuestro proyecto, podremos elegir el


nombre que sea, siempre y cuando cumpla con las reglas anteriores, sin
embargo, si nuestro propósito es subir esta librería a NPM, deberemos validar
antes, que el nombre (name) no esté siendo utilizado ya por otra librería. Si este
fuera el caso, tendremos que considerar otro nombre, pues NPM no nos permitirá
subirlo.

version

La versión es el segundo campo más importante, pues junto con el nombre


crearán un identificador único de la librería. La importante de la versión, es que
nos ayuda a identificar la versión del componente, a la vez que nos permite tener
múltiples versiones publicadas.

En teoría, los cambios en el paquete deben venir junto con los cambios en la
versión, esto significa que cada vez que realicemos un cambio en el módulo, por
más simple que parezca, tendremos que aumentar la versión.

La versión es un valor numérico, que puede estar separado por secciones, estas
secciones se marcan con un punto “.”, de tal forma que podemos tener versiones
como las siguientes:

• 1
• 1.0
• 1.0.1
• 1.1.0.1

Cualquier combinación es válida, pero es importante entender cómo administrar


las versiones. Por ese motivo, aquí te enseñaremos la nomenclatura más
utilizada.

La más utilizada se divide en 3 bloques “Cambios mayores”.”Cambios


menores”.”bugs”:

399 | Página
• Cambios mayores: Son cambios en la librería que tiene un gran
impacto y que por lo general rompen con la compatibilidad con versiones
anteriores. Estos tipos de cambios incluyen cambios en el
funcionamiento de algunos de sus componentes, cambios en las
interfaces expuestas o incluso, un cambio en la tecnología utilizada.
• Cambios menores: Son cambios o adición de nuevos features que se
agregan a los ya existentes, sin romper la compatibilidad. Dentro de
estos cambios podemos encontrar, la adición de nuevos métodos o
funciones, nuevas capacidades de la librería, optimizaciones o
reimplementación de funcionalidades encapsuladas que no afectan al
usuario final.
• Bugs: Esta sección la incrementamos cada vez que corregimos uno o
una serie de bugs, pero sin agregar o remplazar funcionalidad,
simplemente son correcciones. La clave en esta sección es que no
debemos incluir nuevos features, si no correcciones a los existentes.

description

Este es solo un campo que nos permite poner una breve descripción de lo hace
nuestro paquete. Es utilizado como una guía para que las personas puedan saber
qué hace tu proyecto y es utilizado por NPM para realizar búsquedas.

Keywords

Es un campo en donde pones palabras claves, las cuales identifican mejor a


nuestra librería. También es utilizada por NPM para realizar búsquedas.

bugs

Aquí es posible definir una URL que nos lleve a la página de seguimiento de bugs
y también es posible definir un email para contactar al equipo de soporte.

1. {
2. "url" : "https://github.com/owner/project/issues",
3. "email" : "project@hostname.com"
4. }

autor

Campo que nos permite poner el nombre del autor de la librería, puede ser el
nombre del desarrollador o la empresa que lo está desarrollando.

Página | 400
license

Es posible determinar la licencia que tiene nuestra librería, actualmente existe


un catálogo de posibles licencias (https://spdx.org/licenses/).

scripts

Este es un campo muy importante, pues nos permite crear scripts para compilar,
construir, deployar y ejecutar la aplicación, sin embargo, este campo es complejo
y requiere de un entendimiento más avanzado para comprender todas sus
posibilidades. Para ver la documentación completa acerca de cómo construir
script puedes ir a la siguiente URL (https://docs.npmjs.com/misc/scripts).

devDependencies

Aquí se enlistan todos los módulos que son requeridos para ejecutar la aplicación
en modo desarrollo, estos módulos estarán disponibles solo cuando la aplicación
no se ejecute en modo productivo.

Campo dependencies

Aquí definimos las librerías indispensables para correr nuestra aplicación, ya sea
en modo desarrollo o productivo. Esto quiere decir que estas librerías son
indispensables en todo momento.

Documentación de package.json

Dado que este archivo es bastante extenso, te dejo la liga a la documentación


oficial del archivo package.json, por si quieres profundizar en todas las cosas que
tiene por ofrecernos.

Node Mudules

A estas alturas del libro, seguramente ya entiendes a la perfección que son los
módulos de Node, pero solo para dejarlo claro. Los módulos son todos aquellos
paquetes que descargamos con ayuda del comando install de NPM. Los paquetes
que descargamos se guardan en una carpeta llamada node_modules, la cual se
crea automáticamente al momento de instalar cualquier librería:

401 | Página
Fig. 95 - Carpeta node_module en Atom

Lo interesante es que en esta carpeta podremos ver todos los módulos que hemos
instalando en nuestro proyecto y que por ende, estarán disponibles en tiempo de
ejecución. Solo por nombrar algunos ejemplos, podrás encontrar las carpetas
React, React-redux, React-router, jsx-control-statements, etc.

Algo importante a notar, es que el nombre de la carpeta corresponde con el


atributo name del archivo package.json de cada módulo.

Creando un servidor de Express

Ya con un entendimiento más claro de lo que es NodeJS y como los paquetes son
administrados, pasaremos a implementar nuestro primero servidor con NodeJS.
Para aprender desde cero, crearemos un nuevo archivo llamado express-
server.js con la intención de hacer algunas pruebas. La intención es usar este
nuevo archivo solo durante este capítulo, después de esto, podremos borrarlo.

Iniciemos creando el archivo express-server.js en la raíz del proyecto, el cual


deberá tener el siguiente contenido:

1. var express = require('express');


2. var app = express();
3.
4. app.get('/*', function (req, res) {
5. res.send("Hello world");
6. });

Página | 402
7.
8. app.listen(8181, function () {
9. console.log('Example app listening on port 8181!');
10. });

Para hacer funcionar este servidor solo basta con ejecutar el siguiente comando
desde la carpeta raíz del proyecto:

node ./express-server.js

Tan solo con estas pocas líneas hemos creado un servidor Express que responde
en el puerto 8181. Veamos que está pasando, en la línea 1 estamos importando
el módulo de Express, el cual instalamos mediante el comando npm install --
sav-deve express. En la línea 2, estamos creando una nueva instancia de Express
que ponemos en la variable app (línea 2).

En la línea 4 estamos utilizamos algo llamado Methods (métodos) con el cual es


posible registrar routeadores que procesarán las peticiones entrantes. En este
caso, le estamos indicando a Express que todas las peticiones que lleguen por el
método GET serán atendidas por este Router, y como consecuencia, regresará la
palabra “Hello world”. Un poco más adelante entraremos en detalles acerca de
cómo funcionan los métodos. Por ahora, es suficiente saber que el “*” indica que
esta ruta acepta cualquier URL. Los parámetros req y res, corresponde al request
y response respectivamente.

Finalmente, en la línea 8, creamos un oyente, con la intención de atender todas


las peticiones en el puerto 8181. Este último paso es el que inicia el servidor y lo
hace disponible para ser accedido desde el navegador.

Fig. 96 - Hello world con Express.

En la imagen anterior, ya podemos observar nuestro servidor respondiendo a


nuestras peticiones.

403 | Página
Express Verbs

Algo sumamente importante a la hora de trabajar con Express y el desarrollo de


API’s, es conocer los distintos métodos a los cuales puede responder Express.
HTTP Verbs es como se les conoce a los diferentes métodos que soporta un
servidor HTTP para comunicarse. El termino Verbs suele ser sustituido por
métodos, por lo que utilizaremos el nombre métodos para referirnos a ellos.

Cuando una aplicación se comunica con un servidor HTTP, este le tiene que
indicar que método utilizará para la comunicación, pues cada uno de ellos tiene
una estructura diferente. La principal diferencia que radica entre cada uno de
ellos, es la interpretación que le da el servidor.

El protocolo HTTP, así como Express, soportan una gran cantidad de método, sin
embargo, los más utilizados son 4, y el resto es utilizado para cosas más
específicas que no tendría caso comentar ahora. Si quieres ver la lista completa,
puedes verla en la documentación oficial de Express.

Método GET

Este es el método más utilizado por la WEB, pues es el que usa el navegador
para consultar una página a un servidor. Cuando entramos a cualquier página,
ya sea Facebook, Google o Amazon, el navegador lanza una petición GET al
servidor y este le regresa el HTML correspondiente a la página.

Las peticiones no llevan un payload asociado a la petición, si no que la URL es lo


único necesario para que el servidor HTTP sepa qué hacer con ella. En resumidas
cuentas, el método GET es utilizado como un método de consulta.

Método POST

El método POST es el segundo método más utilizado por la WEB, pues permite
enviarle información al servidor, como puede ser el formulario de una página,
una imagen, un JSON, XML, etc. Mediante la barra del navegador es imposible
enviar peticiones POST, pero si es posible mediante programación, formularios o
programas especializados para probar recursos web como es el caso de SoapUI
O Postman.

Los servidores entiendan la petición POST como un método de creación, es


decir, que cuando se manda un POST, este interpreta que la información deberá
ser utilizada para crear un registro del lado del servidor.

Página | 404
Método PUT

El método PUT no es muy utilizado por los navegadores, al menos no de forma


natural. Este método es utilizado para reemplazar por completo un registro
con los nuevos valores que son enviados en el Payload, lo que indica que este
método si puede tener un payload asociado a la petición.

Método DELETE

Delete tampoco es utilizado por el navegador de forma natural, y es utilizado


para indicar que un registro debe de ser eliminado, este método soporta el
envío de un payload asociado a la petición.

Consideraciones adicionales.

HTTP hace una serie de recomendación de cómo los métodos se deben utilizar,
sin embargo, esto no es garantía que se cumpla, pues perfectamente podrías
mandar una petición DELETE para crear un nuevo registro, o un POST para
actualizar o un GET para borrar. Cuando entremos de lleno a la creación de
nuestros servicios REST retomaremos este tema y veremos las mejores prácticas
para la creación de servicios. Por ahora, basta con que comprendas teóricamente
como deberían de funcionar.

Implementemos algunos métodos.

Para comprender un poco cómo funcionan los métodos, crearemos un router que
procese las solicitudes de los métodos que analizamos hace un momento, para
ello, agregaremos las siguientes líneas a nuestro archivo express-server.js:

1. var express = require('express');


2. var app = express();
3.
4. app.get('*', function (req, res) {
5. res.send("Hello world GET");
6. });
7.
8. app.post('*', function (req, res) {
9. res.send("Hello world POST");
10. });
11.

405 | Página
12. app.put('*', function (req, res) {
13. res.send("Hello world PUT");
14. });
15.
16. app.delete('*', function (req, res) {
17. res.send("Hello world DELETE");
18. });
19.
20. app.listen(8181, function () {
21. console.log('Example app listening on port 8181!');
22. });

Lo que hemos hecho es muy simple, hemos agregado 3 nuevos routers que
procesan las solicitudes para POST (línea 8), PUT (línea 12), DELETE (línea 16),
lo que significa que cuando entre cualquier petición al servidor por cualquiera de
estos métodos, será procesado por el router apropiado.

Observa que tan solo es necesario utilizar app.<method> para crear un router para
cada método, y el * indica que puede procesar cualquier URL entrante, y no solo
la raíz.

Cada método recibe dos parámetros, el path y un Callback, el path, corresponde


a la URL a la que puede responder, por lo que solo se ejecutará si el path hace
match con la URL invocada. El segundo parámetro es una función que será
ejecutada si el path se cumple, esta función recibe dos parámetros, el primero
es el request y el segundo es el response.

Ahora bien. Para probar esto, podemos utilizar SoapUI y ejecutar la URL
http://localhost:8181 en los cuatro métodos disponibles:

Página | 406
Fig. 97 - Probando el método GET

Fig. 98 - Probando el método POST

407 | Página
Fig. 99 - Probando el método PUT.

Fig. 100 - Probando el método DELETE.

Página | 408
Como hemos visto en las imágenes anteriores, ante la misma URL pero con
método diferente, obtenemos un resultado diferente, pues el router que procesa
la solicitud es diferente.

Con esto, nos debe quedar claro que podemos atender la misma URL pero con
distintos comportamientos, porque una misma URL podría hacer cosas diferentes
con tan solo cambiar el método, y es allí donde radica la magia del API REST.

Trabajando con parámetros

Cuando la WEB nació a principio de los 80’s, jamás se imaginó el alcance que
tendría y como esta evolucionaría para crea aplicaciones tan complejas como lo
son hoy en día. En sus inicios, todas las URL a los recursos de internet, eran
meramente un enlace a un documento alojado en otro servidor o directorio del
mismo servidor, y la URL como tal, era irrelevante, por lo que nos encontrábamos
links como los siguientes:

• http://server.com/?page=index
• http://server.com/121202/134%2023.html

Estas URL, si bien, funcionan, la realidad es que no son nada descriptivas, pues
no te da ninguna idea de lo que va hacer o donde te van a enviar.

Si lo que buscamos es desarrollar un API fácil de utilizar, tenemos que tener


mucho cuidado al memento de definir las URL, ya que la URL por sí solo, debería
darnos una idea bastante clara de lo que hace. Por ejemplo:

http://api.com/users/oscar/profile

Si analizamos esta URL, nos da mucha información, pues cada sección de la URL
nos da una pista de lo que hace. En este caso, queda claro que estamos
consultando el perfil del usuario oscar.

Query params

La forma más fácil de enviar parámetros al servidor, es mediante Query params,


los cuales consisten en una serie de key=value (propiedad= valor) que se
agregan al final de una url:

http://api.com/?param1=val1&param2=val2&param3=val3

409 | Página
Cada parámetro debe estar separado con un ampersand (&) y debe de anteponer
un signo de interrogación (?) antes de iniciar con los parámetros.

Para recuperar un query param en Express solo tenemos que ejecutar la siguiente
instrucción req.query.<param-name>. Veamos el siguiente ejemplo:

1. app.get('*', function (req, res) {


2. const name = req.query.name
3. const lastname = req.query.lastname
4. res.send("Hello world GET => " + name + ' ' + lastname);
5. });

Hemos actualizado el archivo express-server.js para agregar las siguientes


líneas en el método GET, con la finalidad de recuperar los parámetros name y
lastname, seguido de eso, los concatenamos en la respuesta. Para probar estos
cambios, ejecutaremos la siguiente URL en el navegador:

http://localhost:8181/?name=Oscar&lastname=Blancarte

Fig. 101 - Express query params.

URL params

Los URL params, son parámetros que se pueden pasar por medio de la misma
URL y no estamos hablando de los Query params, en su lugar, las mismas
secciones de una URL se pueden convertir en parámetros, por ejemplo, en el
proyecto Mini Twitter, usamos un servicio para consultar el perfil de un usuario,
para esto, ejecutar una URL como la siguiente:
http://api.com/users/test/profile, en esta URL, test, es un URL Param, y
puede ser recuperado para ser utilizado como un parámetro.

Es posible definir estos parámetros anteponiendo dos puntos ( : ), antes de cada


sección, por ejemplo, para consultar el perfil, podemos crear un path con el
siguiente formato (/:username/profile), adicional, estos parámetros pueden ser
recuperados mediante el request de la siguiente manera: req.params.<param-
name>.

Página | 410
Actualizaremos nuevamente el archivo express-server.js y agregaremos las
siguientes líneas:

1. app.get('/:name/:lastname', function (req, res) {


2. const name = req.params.name
3. const lastname = req.params.lastname
4. res.send("Hello world GET => " + name + ' ' + lastname);
5. });

Es muy importante que este nuevo router este por arriba del router GET que ya
teníamos, pues como el otro acepta todas las peticiones, no dejará que esta
nueva se procese.

Ahora bien, ya hemos cambiado el path para aceptar dos url params, los cuales
son name y lastname, luego estos son recuperados mediante req.params.name y
req.params.lastname.

Ejecutaremos nuevamente los cambios, pero esta vez, utilizaremos la URL:

http://localhost:8181/Oscar/Blancarte

Y el resultado será el siguiente:

Fig. 102 - Express URL params.

Body params

La última forma de enviarle parámetros a Express es mediante el payload y solo


está disponible para los métodos que lo soportan, como es POST, PUTH, DELETE.

En la práctica, el payload no es tan simple de obtener, pues en realidad no es


que llegue junto al mensaje, en su lugar, el payload es enviado como un Input

411 | Página
Stream, lo que quiere decir que se empieza a recibir por partes hasta completar
todo el mensaje.

Para superar este problema, tendríamos que definir un Middleware, el cual recibe
el mensaje primero que los Router y procese el payload, para el final dejarlo en
el objeto request. Esto quedaría de la siguiente manera:

1. app.use(function(req, res, next){


2. var data = "";
3. req.on('data', function(chunk){ data += chunk})
4. req.on('end', function(){
5. req.body = data;
6. next();
7. })
8. })

Más adelante veremos qué es esto de los Middleware, pero por ahora, eso que
estamos viendo en pantalla no lo vamos a requerir, porque existe una librería
que nos facilita la vida y que adicional, nos convierte el mensaje a JSON o al tipo
que necesitemos.

Body-parse module

La librería body-parse una utilidad que nos ayuda a parsear el payload, mediante
el cual es posible convertir el payload a el formato que necesitemos y dejarlo
disponible en el objeto request.

Para instalar esta librería bastará con ejecutar el siguiente comando:

npm install --save-dev body-parser@1.19.0

Una vez instalado, actualizaremos el archivo express-server.js para agregar las


siguientes líneas:

1. var express = require('express');


2. var app = express();
3. var bodyParser = require("body-parser")
4.
5. app.use(bodyParser.urlencoded({extended: false}));
6. app.use(bodyParser.json({limit:'10mb'}));
7.
8. app.get('/:name/:lastname', function (req, res) {
9. const name = req.params.name
10. const lastname = req.params.lastname
11. res.send("Hello world GET => " + name + ' ' + lastname);
12. });
13.
14. app.get('*', function (req, res) {
15. const name = req.query.name
16. const lastname = req.query.lastname
17. res.send("Hello world GET => " + name + ' ' + lastname);

Página | 412
18. });
19.
20.
21. app.post('/login', function (req, res) {
22. const body = req.body
23. res.send(body);
24. });
25.
26. app.post('*', function (req, res) {
27. res.send("Hello world POST");
28. });
29.
30. app.put('*', function (req, res) {
31. res.send("Hello world PUT");
32. });
33.
34. app.delete('*', function (req, res) {
35. res.send("Hello world DELETE");
36. });
37.
38. app.listen(8181, function () {
39. console.log('Example app listening on port 8181!');
40. });

En la línea 3 estamos haciendo el import al módulo. En la línea 5 estamos


registrando el body-parse como un middleware y le estamos indicando que no
queremos un formato extendido en la codificación. En la línea 6 le indicamos que
una vez que el payload sea recuperado por el paso anterior, este lo convierta en
JSON, le indicamos que los mensajes no deberán exceder los 10 megas
(mensajes mayores producirán error).

Finalmente, en la línea 21 definimos un nuevo router que atenderá las peticiones


en el path /login, el cual responderá con el payload enviado.

Ejecutemos este ejemplo para ver los resultados:

413 | Página
Fig. 103 - Express parse-body

Observa que en la respuesta nos hemos colocado en la pestaña JSON, pues el


mensaje de respuesta tiene este formato.

Middleware

Los Middleware son funciones comunes y corrientes que tiene la particularidad


de ejecutarse antes que los routings de los métodos tradicionales. Los Middlware
los podemos ver como interceptores que toman la ejecución antes que los demás,
hacen cualquier cosa y luego pasan la ejecución al siguiente middlware, al
termina la cadena de Middleware, la ejecución pasa al routing apropiado para
atender la petición.

Página | 414
Fig. 104 - Ejecución en cadena de Middleware.

Cuando un Middlware toma la petición, tiene acceso total al objeto request y


response, por lo que puede manipular la información antes que este llegue al
routing final, el cual no se dará cuenta de cómo venía el mensaje original.

Las funciones middleware reciben al menos tres parámetros, el request (req),


response (res) y next, esta última es una referencia al siguiente Middleware de
la lista. El middleware tiene la responsabilidad de ejecutar next() al finalizar la
ejecución, pues de lo contrario dejara colgada la petición del cliente.

Otra de las características que tiene el Middleware, es que puede responder


directamente al cliente, sin la necesidad de pasar por el router. Un ejemplo de
esto es la seguridad, si no nos autenticamos, un middleware puede rechazar la
petición sin ni siquiera llevar al router adecuado. Solo en los casos que el
middleware responda al cliente, puede no ejecutar next().

Existe 5 tipos de middleware soportados por Express:

415 | Página
• Middleware de nivel de aplicación
• Middleware de nivel direccionador
• Middleware de terceros
• Middleware incorporado
• Middleware de manejo de errores

De los 5 listados analizaremos los primeros 4, y en la siguiente sección


analizaremos los de manejo de errores.

Middleware de nivel de aplicación

Los middlewares de aplicación son aquellos que definimos mediante app.use(),


y que adicional definimos nosotros mismo la implementación de la función
Callback

1. var app = express();


2.
3. app.use(function (req, res, next) {
4. console.log('Time:', Date.now());
5. next();
6. });
7.
8. app.use('/user/:id', function (req, res, next) {
9. console.log('Request Type:', req.method);
10. next();
11. });

El primer middleware (línea 3) no tiene un path, por lo que se ejecutará ante


cualquier path sin importar el método.
El segundo middleware es más específico, pues solo se ejecutará cuando la URL
cumpla con el path definido.

Middleware de nivel direccionador

El middleware de nivel de direccionador funciona de la misma manera que el


middleware de nivel de aplicación, excepto que está enlazado a una instancia
de express.Router(). Este tipo de Middleware es utilizado para agrupar una serie
de reglas de ruteo en un solo objeto, con la finalidad de facilitar su administración
a medida que crece el número de routeos.

Más adelante veremos cómo este tipo de middleware nos ayudará a separar los
routeos que van a la aplicación Mini Twitter de las que van al API.

1. var app = express();

Página | 416
2. var router = express.Router();
3.
4. router.get('/api/profile', function (req, res, next) {
5. //Any action
6. });
7.
8. router.get('/api/user', function (req, res, next) {
9. //Any action
10. });
11.
12. app.use('/api', router);

En el ejemplo anterior vemos cómo estamos creando una serie de routeos pero
por medio del objeto router, luego, este objeto es utilizando para crear un nuevo
middleware (línea 12) que atiende en la URL /api.

Middleware de terceros

Los middlewares de terceros son aquellos que están implementados en una


librería externa y que solo hace falta registrarla. Un ejemplo de estos, es body-
parser, recordemos como lo implementamos:

1. var express = require('express');


2. var app = express();
3. var bodyParser = require("body-parser")
4.
5. app.use(bodyParser.urlencoded({extended: false}));
6. app.use(bodyParser.json({limit:'10mb'}));

Observemos como en la línea 5 y 6 estamos haciendo uso de bodyParser para


crear dos middlewares, uno que recupera el payload y el segundo que lo convierte
en json. Este tipo de middleware son también vistos como plugins, pues solo
hace falta registrarlos para que empiecen a funcionar.

Para profundizar más acerca de los middleware, puedes entrar a revisar la


documentación oficial en http://expressjs.com/es/guide/using-middleware.html.

Middleware incorporado

Como su nombre lo indica, estos corresponden a los Middleware que ya viene


incorporados a Express si necesidad de instalar un módulo aparte. Sin embargo,
a partir de la versión 4.x de Express, todos los middlewares incorporados fueron
separados en módulos independientes. Como es el caso de body-parser que
anteriormente venía incorporado, pero hoy en día es un módulo independiente.

417 | Página
Hoy en día, el único middleware incorporado es express.static, el cual sirve
para exponer recursos estáticos. Los recursos estáticos son archivos como el
index.html, styles.css, bundle.js, todos aquellos que queramos que sea
accesibles públicamente sin necesidad de definir un routing específico para cada
archivo y que además, su contenido no cambia en el tiempo.
Un ejemplo típico de su utilidad, es para exponer todo el contenido de la carpeta
public.

1. app.use(express.static(path.join(__dirname, 'public')));

En este ejemplo, registramos el middleware static, el cual recibe como


parámetro un path, el cual es la ruta a la carpeta public del mismo proyecto.
path.join es una utilidad que se utiliza para unir dos direcciones, __dirname es
la ruta del proyecto y ‘public’ es el nombre de la carpeta a publicar.

Este ejemplo da como resultado la exposición pública a todos los archivos


contenidos en la carpeta public, por lo que hay que tener cuidado de que
información dejamos aquí.

Error Handler

Un error handler son un tipo especial de Middleware, pues permiten gestionar los
errores producidos en tiempo de ejecución. Un error handlers se definen
exactamente igual que los middlewares a nivel de aplicación, pero con la
diferencia de que estos reciben 4 parámetros, donde el primero es el error (err),
el segundo el request (req), el tercero el response (res) y el cuarto es el next.

1. app.use("/api", function(err, req, res, next){


2. //Any action
3. });
4.
5. app.use(function(err, req, res, next){
6. //Any action
7. });

Es posible definir handlers globales (línea 5) o específicos para una path


determinado (línea 1), la única diferencia, es que el global no tiene ningún path
asociado y el especifico sí.

Ahora bien, es posible tener más de un handler global y más de uno específico
para el mismo path, lo que pasará es que se ejecutarán en el orden en que fueron
definidos, pero siempre y cuando, el handler anterior ejecute next().

Página | 418
Puedes ver la documentación completa de Express para el tratamiento de errores
en la documentación oficial ( http://expressjs.com/en/guide/error-
handling.html).

419 | Página
Resumen

En este capítulo hemos analizado hemos analizado las principales características


que nos ofrece NodeJS + Express para la construcción de aplicaciones WEB y
API´s.

También hemos aprendido acerca de los verbs o métodos disponibles y como


estos deben de ser utilizados.

Hemos aprendido las distintas formas que tiene express para recibir parámetros,
mediante url params, query params y body param o payload.

Hemos aprendido a crear nuestros propios middlewares y a utilizar los


middlewares provistos por terceros.

Sin duda, este capítulo nos deja listos para empezar a construir el API, pero antes
de iniciar con eso, nos introduciremos a MongoDB para conocer cómo funciona y
aprender a conectarnos desde NodeJS.

Página | 420
Introducción a MongoDB
Capítulo 16

En el primer capítulo de este libro nos introdujimos al mundo de MongoDB para


hablar a grandes rasgos de cómo es que esta funciona y las principales
diferencias que tiene contra el modelo SQL tradicional, por lo que en este capítulo
profundizaremos más en el tema, y aprenderemos a utilizar las operaciones más
importantes de MongoDB y veremos cómo utilizarlo en conjunto con NodeJS.

Porque es importante aprender MongoDB

Como siempre, me gusta explicar por qué MongoDB es una tecnología que vale
la pena aprender y cómo es que está ganando popularidad rápidamente. A pesar
de que MongoDB ha venido subiendo rápidamente en popularidad, la realidad es
que mucha gente todavía se siente desconfiada de usar esta base de datos, pues
rompe completamente con los paradigmas que durante años se nos ha enseñado.

Temas como, relaciones, procedimientos almacenados, tablas y columnas, etc.,


es algo que en MongoBD no existe, y no es porque no esté madura o por que
falte evolucionar, la cuestión es que MongoDB y el paradigma NoSQL en general,
no se planteó para resolver los problemas que teníamos con las bases de datos
SQL tradicionales. En su lugar, MongoDB propone un esquema de
almacenamiento por documentos, donde toda la información relacionada al
documento, esta toda junta, por lo que no hace falta hacer joins para unir la
información, lo que se refleja en una mejora considerable en el performance.

Ahora bien, estudiando un poco el mercado y comparando las tecnologías que la


gente está interesada en aprender, me encontré con esta gráfica, la cual da una
clara señal del futuro de las bases de datos NoSQL en general.

421 | Página
Fig. 105 - Grafica de intereses 2017

La gráfica anterior, hace una comparación con los diversos tópicos o temas más
relevantes que la gente ha manifestado con mayor interés de aprender, por lo
que no solo se habla de bases de datos, sino que se compara con cosas como
Realidad Virtual, Internet de las cosas (IoT), Machine Learning, DevOps, Cloud
computing, etc. Lo que a mí me llama la atención, es que las bases de datos
NoSQL (entre las que se incluye MongoDB) representa el segundo lugar de
popularidad, solo sobrepasada por la Arquitectura de Software.

Esta gráfica es muy reveladora, pues no dice que la gran mayoría de personas,
están muy interesadas en aprender Bases de datos NoSQL, lo que sin duda
también disparará la demanda de esta base de datos.

Ahora bien, con estos datos, quiero que veas la siguiente gráfica:

Página | 422
Fig. 106 - Posiciones de trabajo abiertas.

La siguiente gráfica ilustra las posiciones abiertas en enero de 2016, en las cuales
podemos apreciar las principales bases de datos del mercado. Primero que nada,
quiero que observes, como MongoDB al final de la gráfica, se logra posicionar
como la tercera base de datos más solicitada, solo superada por Oracle y SQL
Server.

Sé que la diferencia entre Oracle y SQL Server es abismal. Sin embargo, hay que
recordar que estas dos bases de datos son el estatus quo del momento, es decir,
es donde todas las empresas están actualmente, y muchas de ellas ya están
empezando a migrar partes de sus aplicaciones a MongoDB, por lo que se espera
que, en los próximos años, las bases de datos NoSQL tomen mucha más fuerza
y empiece a desplazar a las SQL.

Ahora bien. MongoDB no está diseñado para todo tipo de aplicaciones, por lo que
sin duda SQL seguirá teniendo su lugar.

Como conclusión a todo este análisis, cerraría diciendo que MongoDB es sin duda
unas de las tecnologías con mayor potencial en los años que siguen y que la
oferta de trabajo para gente con este perfil va a subir drásticamente, por lo que
es buen momento para empezar a aprender.

El rol de MongoDB en una aplicación

A diferencia de las bases de datos tradiciones o SQL, MongoDB es diseñado para


solucionar problemáticas diferentes, en donde no es requerido realizar grandes
transacciones sobre el mismo registro.

Otra de las características de MongoDB, es su capacidad de adaptarse a los


cambios, pues no requiere de una estructura fija, en su lugar, puede recibir

423 | Página
cualquier elemento JSON. Con la llegada de IoT, Mongo ha demostrado una gran
potencia, pues nos permite guardar las configuraciones de los dispositivos e ir
guardado toda la información que va generando.

Solo por poner un ejemplo, me toco conocer de un proyecto para inducir a los
niños a la tecnología, mediante el lanzamiento y globos orbitales, estos globos
son construidos con dispositivos ultra económicos, como tarjetas Arduino y todo
tipo de sensores compatibles. La idea del proyecto es armar globos que suban
hasta la atmosfera y en su camino vallan registrando temperatura, altura,
posición, humedad y tiene una cámara para tomar fotos cada minuto. Lo
interesante es que este globo, tiene un programa en NodeJS el cual es el
encargado de gestionar la comunicación con los sensores, para finalmente
guardar los registros en una base de datos MongoDB.

Mientras el globo vuela, los alumnos pueden seguir la trayectoria por el GPS para
recuperar la capsula (donde está el hardware). El globo se revienta al entrar a la
atmosfera y empieza su caída en picada. A cierta altura se activa un paracaídas
que hace que la capsula descienda suavemente y va fotografiando su caída. Al
final los alumnos pueden saber dónde cayo exactamente por el GPS y las últimas
fotos que tomo antes de tocar tierra. A y se me olvida, los alumnos se encargan
de programar todo lo necesario para el funcionamiento

Como me hubiera gustado vivir esa experiencia cuando estudiaba (snif , snif).
Pero, ¿por qué les cuento esta historia de un proyecto universitario?, No sé
ustedes, pero yo veo demasiado potencial en eso, con el hecho de que un
dispositivo alcance la atmosfera y que valla monitoreando cada aspecto, me hace
pensar que las posibilidades son infinitas.

Ahora bien, esto es sin hablar de la robótica, wearables (ropa, relojes, etc.),
dispositivos electrónicos que requiere almacenar información, etc. Lo triste, es
que cuando hablamos de aplicaciones y bases de datos, siempre se nos viene a
la mente los sistemas de información, sin embargo, existen muchísimas más
cosas que requieren de una base de datos.

Con este contexto, parece que queda claro que el contexto de MongoDB en una
aplicación, puede ser en cualquier lugar que se requiera almacenamiento y que
este no requiera de gran cantidad de actualizaciones sobre un mismo objeto.

Instalando MongoDB

En el caso de MongoDB no necesitaremos hacer una instalación como tal, pues


aprovecharemos la infraestructura de MongoDB para crear nuestra propia base
de datos en la nube y totalmente gratis.

Lo primero que deberemos hacer será crea una cuenta en MongoDB Atlas:

Página | 424
Fig. 107 - Crear cuenta en MongoDB

Presionaremos el botón que dice “Try free” para iniciar el registro. Nos llevará a
un pequeño formulario en donde tendremos que capturar algunos datos para
crear la cuenta.

Una vez creada la cuenta, nos redirigirá a la consola de administración. Si es la


primera vez que entramos, veremos una pantalla como la siguiente:

En esta nueva pantalla daremos click en el botón verde que dice “Build a
Cluster”, lo que nos llevará a seleccionar el paquete que queremos adquirir, por

425 | Página
supuesto, seleccionaremos la opción “Free”, lo que nos llevará finalmente a la
página de la configuración del Cluster.

Fig. 108 - Crear una base de datos de MongoDB.

Importante

La interfaz de MongoDB Atlas cambia con frecuencia,


por lo que es posible que lo que veas ahora no
corresponda con la imagen, por lo que procura hacer
la configuración lo más parecido posible.

En esta nueva pantalla vamos a dejar todos los valores que viene por default,
con la única excepción del Cluster Name, al cual le pondremos Mini-Twitter,
finalmente presionamos el botón verde que dice “Create Cluster”.

El proceso de creación del cluster puede tardar algunos minutos, por lo que
deberemos de ser pacientes. Una vez terminado el proceso veremos algo como
lo siguiente:

Página | 426
El siguiente paso será crear un usuario para conectarnos a la base de datos, el
cual es diferente al usuario que creamos para crear el cluster. Para crear el
usuario de la base de datos tendremos que dar click en la opción “Database
Access” que se encuentra en las opciones del lado izquierdo y luego en el botón
“Add new database user”:

En la siguiente pantalla tendremos que crear al nuevo usuario, por lo que nos
aseguramos de seleccionar “password” como método de autenticación, y darle
acceso de escritura y lectura a todas las bases de datos. Finalmente, ponemos
como nombre usuario “twitter” y ponemos un password.

427 | Página
Fig. 109 - Captura de usuario y password para crear la base de datos.

Quiero aclarar que he estado utilizando erróneamente el término “Base de datos”,


pues en realidad lo que hemos creado, es un cluster, es decir, un conjunto de 3
bases de datos funcionando en paralelo. Sin embargo, eso ahora no nos tiene
que preocupar.

Habilitando el acceso por IP

Una vez que el usuario está creado, es hora de configurar desde que IP’s nos
podemos conectar, por lo que nos dirigiremos a la sección “Network Access” que
se encuentra en el menú del lado izquierdo:

Página | 428
En la nueva pantalla presionaremos el botón “ALLOW ACCESS FROM ANYWHERE”, es
decir, aceptar el acceso desde cualquier IP. Finalmente presionamos el botón
“Confirm”

En este punto ya hemos terminado de configurar nuestro cluster de MongoDB en


la Nube. Realmente fácil, ¿no es así?

Te recomiendo que te des un tour por la aplicación y que conozcas cada detalle
que nos ofrece. Entre las características más interesantes son las métricas, pues
no dice el número de transacciones, conexiones activas, número de operaciones,
ancho de banda utilizado, etc. También cuenta con alertas, Respaldos, etc.
Desafortunadamente algunas características son de pago, pero con las
características que nos da gratis es más que suficiente para empezar.

Otra de las opciones es instalar MongoDB local en tu equipo, sin embargo,


recomiendo ampliamente utilizarlo desde la nube, pues nos ahorramos la

429 | Página
instalación, iniciar y detener la base de datos para que no consuma recursos.
Como sea, si tu deseas instalarlo local, te dejo la siguiente liga de la
documentación de MongoDB.

https://docs.mongodb.com/manual/administration/install-community/

Instalando Compass, el cliente de MongoDB

Una vez que la base de datos esta activa y funcionando, nos queda solo un paso,
instalar un cliente para conectarnos a la base de datos de MongoDB.

En el mercado existen varios productos que nos permiten conectarnos a una base
de datos Mongo, como lo son:

• Compass
• Mongobooster
• Estudio 3T
• Robomongo
• MongoVue
• MongoHub
• RockMongo

Y seguramente hay más. hay algunos muy simples, otros muy completos, hay
gratis otros de paga, es cuestión de que te des una vuelta a la página de cada
uno para que veas qué características tiene.

En la práctica, a mí me gusta mucho Estudio 3T, pues es uno de los más robustos
y completos, pero tiene el inconveniente que es de paga, por esa razón, nos
iremos por la segunda mejor opción que es Compass y que además es Open
Source.

Para descargarlo, regresaremos al Dashboard de MongoDB y presionamos la


última opción que dice “Connect with MongoDB Compass” como se ve en la
siguiente imagen:

Página | 430
Fig. 110 - Download MongoDB Compass

Seguido de esto, una nueva ventana aparecerá, desde la cual podremos


descargar la versión adecuada para nuestro sistema operativo. Existe una versión
para Windows, Mac y Linux, por lo que no hay pretexto para no instalarla.

431 | Página
Fig. 111 – Descargando MongoDB Compass.

Una vez descargado e instalado MongoDB presionamos el botón que dice


“COPY”, el cual copia el String de conexión para conectarnos a la base de datos,
solo recuerda reemplazar el campo “<password>” por el password que le pusiste
al usuario Twitter.

Una vez terminado de instalar, Compass nos reconocerá el String de conexión


directamente desde el portapapeles y solo tendremos que poner el password. Si
no reconoce el String de conexión, podemos ir al menú principal y presionar
Connect ➔ Connecto To. Una vez conectado, tendremos una pantalla como la
siguiente:

Página | 432
Fig. 112 - Conexión exitosa a MongoDB.

Del lado derecho izquierdo podemos ver las opciones “admin”, “local” y “test”,
de esas tres, la que nos interesa es “test”, sin embargo, como todavía no vamos
a hacer nada con ella. Más adelante a medida que avancemos el proyecto Mini-
Twitter, vamos a ir viendo cómo se van creando las colecciones (lo que
conocemos como tablas en SQL).

En este punto ya nos deberíamos sentir muy orgullosos, pues ya hemos creado
un clúster en la nube y nos hemos logro conectar.

NOTA: Si no vez la base de datos “test” no te preocupes, más adelante cuando


nos conectemos por NodeJS aparecerá.

Finalmente, tomaremos nuevamente el String de conexión, y lo pondremos en el


campo connectionString del archivo serverConfig.js. Si tenemos correctamente
configurado el String de conexión y ejecutamos la aplicación podremos ver que
la aplicación se conecta correctamente. Para ello ejecutamos el comando

node ./AnimalMongoDBTest.js:

Fig. 113 - Conexión exitosa a MongoDB desde Mongoose.

433 | Página
Que son los Schemas de Mongoose

Los Schemas son objetos JavaScript que, en primera instancia, definen la


estructura de un Documento y lo relacionan con una colección de MongoDB.
Mediante los Schemas es posible definir los campos y el tipo de datos esperado
para cada uno de ellos. Además, permite definir valor por default e indicar si un
campo será un Index dentro de la colección.

Una schema es muy sencillo de definir, tan solo es necesario crearlo mediante el
objeto schema proporcionado por mongoose. Veamos cómo quedaría un schema
para la colección animals un ejemplo:

1. var animal = mongoose.Schema({


2. name: {type: String, index: true},
3. color: {type: String, default: "Blanco"},
4. edad: {type: Number, default: 0},
5. apodos: [{type: String, default: []}],
6. propiedades: {
7. alto: Number,
8. largo: Number,
9. peso: Number
10. }
11. },{ strict: false })
12. var Animal = mongoose.model('animals', animal);

De la línea 1 a 11 estamos creando la definición del schema, para ello, es


necesario pasar a mongoose.Schema un objeto que defina la estructura esperada
para un documento. Dentro de este objeto, cada elemento que pongamos, será
un campo en el documento final, por lo que deberemos definir apropiadamente
el nombre y el tipo de cada campo.

Una vez que hemos creado la estructura del schema, solo falta registrarlo en
Mongoose. Esto lo hacemos mediante el método mongoose.model, al cual se le
pasen dos parámetros, el primero corresponde al nombre del modelo, que por
default también corresponde con el nombre de la colección, el segundo
parámetro es la definición del schema. Con solo eso, hemos definido un schema.

Ahora bien, la forma de definir un campo es mediante el formato {name: type },


donde el name, es el nombre que tendrá en el documento, el type es el tipo de
dato. Si un campo no requiere más que definir el tipo de datos, podemos hacerlo
de la siguiente manera:

1. var animal = mongoose.Schema({


2. name: String,
3. edad: Number
4. })

En esta nueva forma, simplemente podemos poner el tipo de datos directamente,


sin crear un nuevo objeto para definirlo. Sin embargo, es muy común que
requiramos poder alguna otra propiedad al campo, como un valor por default o

Página | 434
indicar si se trata de un index. En estos casos, debemos crear un nuevo objeto y
definir todas las propiedades requeridas separadas por coma (,) como en el
primero ejemplo.

Esquemas estrictos y no estrictos

En más de una ocasión, te he mencionado que una de las ventajas de MongoDB


es que permite guardar cualquier documento, sin importar la estructura que este
tenga. Entonces la pregunta que te puedes estar haciendo en este momento es
¿Si MongoDB soporta cualquier estructura, porque debo de crear un Schema?
Esta pregunta es muy importante y la respuesta es simple.

Mongoose utiliza los Schemas para ayudar al programador a tener un mejor


control sobre la creación de los documentos, aunque no es obligados fijar una
estructura. Dado que la mayoría de las aplicaciones manejan una estructura fija,
los Schemas pueden ser una gran ventaja, pues te ayudarán a asegurar que los
documentos tengan la estructura adecuada.

En Mongoose se pueden crear esquemas de dos formas, estrictos y no estrictos,


los estrictos ignorarán cualquier campo que no esté en el schema, y los NO
estrictos permitirá agregar nuevos campos si son requeridos.

Dentro de los NO estrictos, podemos crear un schema sin ninguna estructura


para agregar dinámicamente cualquier campo requerido, pero también es posible
definir solo los campos que siempre deben de venir y dejar abierto el esquema
para agregar nuevos campos dinámicamente.

1. var animal = new mongoose.Schema({}, { strict: false });


2. var animal = mongoose.model('animals', animal);

En este nuevo ejemplo, podemos ver que creamos una estructura en blanco y
pasar como segundo argumento la propiedad strict en false. Con tan solo hacer
esto, estamos diciendo que el schema puede soportar campos no definidos en el
schema.

Schemas avanzados

Los eschemas no solo sirven para definir la estructura de documento, sino que,
además, permiten crear funciones para validaciones, consultas, propiedades
virtuales, etc, etc. Hablar de toda la capacidad que tiene Mongoose se podría
extender demasiado, por lo que te contare rápidamente algunas de las cosas más
interesantes para que te des una idea de su capacidad.

435 | Página
Statics methods

Las funciones estáticas permiten definir funciones a nivel del schema, que pueden
ser invocadas sin necesidad de tener una instancia del Schema

1. animalSchema.statics.findByName = function(name, cb) {


2. return this.find({ name: new RegExp(name, 'i') }, cb);
3. };
4.
5. var Animal = mongoose.model('Animal', animalSchema);
6. Animal.findByName('fido', function(err, animals) {
7. console.log(animals);
8. });

Para definir una función estática, solo tendremos que crearla sobre el Schema,
el formato es el siguiente: <schema>.statics.<method-name>.

Para ejecutar el método estático, solo tendremos que recuperar el Schema (línea
5) y después ejecutar directamente la función.

Este tipo de métodos se utilizan para realizar búsquedas o realizar algunas


validaciones que no requieren de un documento en concreto.

Instance methods

Los métodos de instancia funcionan exactamente igual que los métodos estáticos,
pero estos se definen por medio de la propiedad methods.

1. var animalSchema = new Schema({ name: String, type: String });


2. animalSchema.methods.findSimilarTypes = function(cb) {
3. return this.model('Animal').find({ type: this.type }, cb);
4. };
5.
6. var Animal = mongoose.model('Animal', animalSchema);
7. var dog = new Animal({ type: 'dog' });
8. dog.findSimilarTypes(function(err, dogs) {
9. console.log(dogs); // woof
10. });

El formato para definir un método es el siguiente: <Instacia>.methods.<method-


name>. Este tipo de métodos se utiliza para realizar operaciones que requieren de
los valores concretos de un documento, como, por ejemplo, validar si no existe
un documento del mismo tipo, o si el nombre de usuario ya está registrado, etc.

Query Helpers

Página | 436
Los query helpers son parecidos a los métodos estáticos, con la diferencia que
están diseñador para implementar funcionalidad custom, como buscar por
nombre, obtener todos los documentos de un tipo, etc.

1. animalSchema.query.byName = function(name) {
2. return this.find({ name: new RegExp(name, 'i') });
3. };
4.
5. var Animal = mongoose.model('Animal', animalSchema);
6. Animal.find().byName('fido').exec(function(err, animals) {
7. console.log(animals);
8. });

Estos se definen mediante de la siguiente manera: <schema>.query.<method-


name>.

Virtuals

Los virtuals permiten agregar propiedades virtuales, las cuales son calculadas
por medio de otras propiedades del mismo modelo, como concatenar el nombre
y el apellido para obtener el nombre completo.

1. personSchema.virtual('fullName').get(function () {
2. return this.name.first + ' ' + this.name.last;
3. });
4.
5. console.log(person.fullName);

Las propiedades virtuales no son persistentes, si no que más bien se calculan al


momento de ser solicitadas.

En la siguiente liga podrás encontrar toda la documentación acerca de la creación


de Schemas: http://mongoosejs.com/docs/guide.html

Plugins

Una de las capacidades más interesantes de los schemas, es que es posible


instalarles plugins, los cuales agregan funcionalidad que originalmente no tienen,
como, por ejemplo, cambiar el ID por una secuencia, agregar validadores de
campos únicos, etc.

Un plugin puede ser aplicado de forma global a todos lo schemas o a un solo


schema, la diferencia radica sobre que objeto lo aplicas:

1. //Global plugin
2. mongoose.plugin(<plugin>, <params>)
3.
4. //Schema plugin

437 | Página
5. Animal.plugin(<plugin>, <params>)

Instalar un plugin se realiza mediante el método plugin el cual recibe uno o dos
parámetros, el primero corresponde al plugin como tal y el segundo es un objeto
con configuraciones para el plugin.

Dado que cada plugin tiene una forma distinta de trabajar, no es posible hablar
acerca de su funcionamiento sin tener que hacer referencia un plugin en concreto.
Por lo voy a dejar una liga a todos los plugin que tenemos disponibles y más
adelante veremos cómo implementar un plugin.

Listado completo de los plugins disponibles: http://plugins.mongoosejs.com/

NodeJS y MongoDB

Tras una larga platica de cómo funciona MongoDB por sí solo, ha llegado el
momento de aprender cómo debemos usarlo en conjunto con NodeJS.

En esta sección estudiaremos la librería Mongoose, la cual es una de las más


populares para trabajar con MongoDB. De la misma forma, aprenderemos a
establecer conexión con la base de datos y aprenderemos que son los modelos.
Adicional a eso, analizaremos algunos ejemplos básicos de inserción,
actualización, eliminación y búsquedas por medio de NodeJS.

Estableciendo conexión desde NodeJS

Antes de poder hacer cualquier cosa, es necesario instalar la librería Mongoose y


establecer conexión con la base de datos, por tal motivo iniciaremos con la
instalación.

npm install --save-dev mongoose@5.9.11

El siguiente paso será crear el archivo AnimalMongoDBTest.js en la raíz del


proyecto (/), en el cual realizaremos nuestras primeras pruebas para aprender a
utilizar Mongoose. El archivo tendrá el siguiente contenido:

1. var mongoose = require('mongoose')


2. var configuration = require('./serverConfig')
3. const connectString = configuration.mongodb.connectionString
4.
5. var opts = {
6. useNewUrlParser: true,
7. appname: "AnimalMongoDBTest",
8. poolSize: 10,
9. autoIndex: false,
10. bufferMaxEntries: 0,

Página | 438
11. reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
12. reconnectInterval: 500,
13. autoReconnect: true,
14. loggerLevel: "error", //error / warn / info / debug
15. keepAlive: 120,
16. validateOptions: true
17. }
18.
19. mongoose.connect(connectString, opts, function(err){
20. if (err) throw err;
21. console.log("==> Conexión establecida con MongoDB");
22. })

Para establecer la comunicación con el servidor hay que hacer básicamente 3


cosas, importar las librerías necesarias, que es lo que hacemos en la línea 1 y
obtenemos el String de conexión (en seguido lo analizaremos).

El segundo paso crear los parámetros de conexión con MongoDB, los cuales
vemos en la línea 5, donde creamos un objeto de configuración para establecer
la conexión a la base de datos, este objeto puede tener muchísimas opciones de
configuración, por lo que nombraremos las más importantes:

Propiedad Descripción
useMongoClient Es una propiedad propia de Mongoose, la cual habilita una
nueva estrategia de conexión a partir de la versión 4.11 de
Mongoose. Sin dar muchas vueltas, solo hay que ponerla
en true.

appname Propiedad especial para poner el nombre de la aplicación


que se conecta a MongoDB. Es especialmente útil para
buscar un error en los logs de MongoDB.

poolSize Valor número que determina el número de conexiones que


deberá tener abiertas a MongoDB.

autoIndex Mongoose soporta un buffer en memoria para guardar los


documentos si la conexión a MongoDB falla. De tal forma
que al restablecer la conexión podrá guardar los cambios.

bufferMaxEntries Esta propiedad está ligada a autoIndex y solo tiene sentido


definirla si esta otra es true. Esta propiedad recibe un valor
numérico, el cual corresponde al número de operaciones
que amortiguara antes de renunciar y esperar una
conexión.

autoReconnect Propiedad booleana, que le indica a Mongoose si debe de


reconectarse de forma automática en caso de una
desconexión.

reconnectTries Ligada a autoReconnect y corresponde al número de


reintentos que realizará antes de darse por vencido y
marcar error de desconexión.

439 | Página
reconnectInterval Ligada a autoReconnect, determina el tiempo que esperará
entre cada intento de reconexión(reconnectTries)

loggerLevel Determina el nivel de log que debe lanzar Mongoose, los


valores permitidos son (error|warn |info|debug). Se
recomienda tenerlo siempre en error, a menos que se esté
depurando un error, pues la cantidad de log que puede
generar es abrumadora.

keepAlive Habilita una estrategia para garantizar que las conexiones


estén activas en todo momento.

validateOptions Esta es una propiedad especial de Mongoose, la cual ayuda


a validar que todas las propiedades estén correctamente
establecidas, se recomienda tenerla siempre en false,
excepto la primera vez que modifican las propiedades.

En la siguiente liga podrás encontrar el listado completo de propiedades


disponible

Finalmente, en la línea 19, se establece la conexión con el método connect del


objeto mongoose. Como parámetro recibe el String de conexión, las opciones de
configuración y opcionalmente una función que se ejecutará al momento de la
conexión, esta función recibe como parámetro el error (si hubo). Desde esta
función se puede realizar una acción si no se pudo conectar o si se conectó
correctamente.

Ahora bien, respecto al String de conexión, vamos a crear un nuevo archivo


llamado serverConfig.js en la raíz del proyecto, el cual se verá de la siguiente
forma:

1. module.exports = {
2. mongodb: {
3. connectionString: "<CONNECTION_STRING"
4. }
5. }

Ahora bien, para conectarnos a MongoDB, será necesario crear nuestra cluster
de MongoDB, pero esto lo analizaremos en a continuación:

Como funciona MongoDB

Página | 440
Para comprender como funciona MongoDB, es necesario conocer que son las
Colecciones y los documentos, pues son el equivalente que existe entre Tablas y
Columnas.

Que son las Colecciones

Las colecciones, es la forma que tiene Mongo para agrupar los documentos,
por ejemplo, podemos tener una colección para los usuarios y otra para los
tweets. Estas colecciones no restringen la estructura que un registro puede tener,
si no que ayuda solo a agruparlos. Por ejemplo, veamos como guardamos la
información del proyecto Mini Twitter:

Fig. 114 - Colecciones en MongoDB.

Por muy impresionante que parezca, en toda la aplicación solo utilizamos dos
colecciones, una para los usuarios (profiles) y otra para los tweets (tweets), lo
cuales podemos ver del lazo izquierdo. Estas dos colecciones las utilizamos para
agrupar a los usuarios y los tweets por separado, pero en ningún momento,
definimos la estructura que puede tener una colección.

Por ejemplo, yo podría guardar un usuario (profile) en la colección de los tweets


y Mongo me lo permitiría. Esto es posible debido a que Mongo no define una
estructura de columnas como lo hacen las bases de datos SQL, en su lugar,
Mongo permite guardar Documentos.

441 | Página
Que son los Documentos

Un documento es un archivo en formato JSON, el cual guarda la información


en una estructura “clave:valor”, por ejemplo:

1. {
2. “_id”: “59f9f12317247f48f13367b3”,
3. “_creator”: “59f90ca2de72f70dd9a8d819”,
4. “message”: “Mi primer Tweet”,
5. “image”: null,
6. “replys”: 0,
7. “likeCounter”: 0,
8. “date”: “2017-11-01 10:06:59.036”
9. }

El JSON que vemos a continuación corresponde a un usuario. Cuando creamos


un registro en MongoDB, este agrega el campo _id, el cual corresponde al ID
mediante el cual, puede identificar como único a el registro.

MongoDB también permite la creación de índices, con lo cual, podemos aumentar


el performance en las búsquedas. Los índices son apuntadores, que permite a la
MongoDB encontrar de forma más eficiente un documento. Aunque esto es
posible hacerlo desde la terminal o desde un cliente, dejaremos esta
responsabilidad a la librería que utilizaremos para conectarnos a Mongo desde
NodeJS.

Operaciones básicas con Compass

En esta sección aprenderemos a realizar las operaciones más básicas que


podemos hacer con Compass, entre las que se encuentras, crear colecciones,
insertar, actualizar, borrar y consultar documentos.

Si bien, Compass no es el cliente más potente en este sentido, si nos permite


realizar todas estas acciones, aunque de una forma muy básica. Al final, esto
será suficiente para empezar.

Creando nuestra primera colección

Lo primero que aprenderemos será a crear colección, para ello, nos dirigiremos
a Compass y nos colocaremos en nuestra base de datos, en nuestro caso sería
“test”, la cual podemos ubicar del lado izquierdo. Una vez allí, nos mostrará el
listado de todas las colecciones existentes hasta el momento, por lo que, si ya
hemos trabajado con el proyecto Mini Twitter, ya deberías de tener creadas las
colecciones profiles y tweets.

Página | 442
Una vez allí, presionamos el botón “CREATE COLLECTION” y nos arrojará una nueva
pantalla:

Fig. 115 - Creando una colección en Compass.

Lo único que nos pedirá, es que le pongamos un nombre a la colección y adicional,


nos pedirá que indiquemos si la colección es limitada (Capped). Las Capped
Collectios sirve para establecer un límite máximo de espacio para la colección,
de tal forma, que, cuando la colección llega a límite, empieza a borrar los
primeros registros para insertar los nuevos.

En nuestro caso no queremos que sea Capped, por lo que solo le podremos el
nombre “animals” para realizar pruebas. Presionamos nuevamente “CREATE
COLLECTION” y listo, habremos creado nuestra primera colección.

Una vez echo este paso, la nueva colección deberá aparecer del lado derecho y
damos click en ella para ver su contenido. Lógicamente, estará vacía, pero más
adelante insertaremos algunos registros.

Insertar un documento

Una vez que ya estamos dentro de la colección, podremos observar el botón que
dice “INSER DOCUMENT” el cual presionaremos. Una nueva pantalla nos
aparecerá y no solicitará los valores para nuestro documento:

443 | Página
Fig. 116 - Insert document.

Como verá, por default nos va a crear un campo llamado “_id”, el cual no
podremos eliminar, pues es el único valor obligatorio. Lo siguiente será empezar
a capturar los valores de nuestro documento.

Cada valor se componente de un nombre y valor, y adicional, es posible definir


un tipo, el cual veremos del lado derecho. Para empezar, crearemos 5 registros
de animales, los cuales tendrán los campos, nombre, color y edad:

Fig. 117 - Perro document.

Y repetiremos lo mismo para otros 4 animales, puedes poner lo que sea para que
practiques. Una vez que termines de capturar los datos, solo presionar el botón
“INSERT” para guardar el documento.

Yo he creado los siguiente 5 animales:

Página | 444
Fig. 118 - 5 animales insertados.

El botón “FIND” actualiza la pantalla para ver todos los registros guardados.
Como vez, el número y nombre de los campos no está limitado, incluso
podríamos crear un nuevo animal que tenga un valor que el resto no, por
ejemplo, voy a crear otro animal que se llame “zapo” y le voy a poner un campo
nuevo llamado “patas:4”:

Fig. 119 - Animal Zapo con el campo patas.

Así de fácil es crear un documento, claro que estos documentos son simples, pero
cualquier valor podrá contener otro objeto dentro.

445 | Página
Actualizar un documento

Actualizar un documento es todavía más fácil que crearlo, pues solo hace falta
ponernos sobre el registro que queremos actualizar y presionar el pequeño lápiz
que sale del lado derecho.

Fig. 120 - Actualizando un documento.

Al presionarlo, el registro se pondrá en modo edición y podremos hacer los


cambios necesarios, como agregar nuevos campos, eliminar o actualizar
existentes. Finalmente, solo hace falta guardar los cambios en el botón
“UPDATE”.

Eliminar un documento

Eliminar un documento es exactamente igual que editarlo, con la diferencia de


que, en lugar de presionar el botón del lápiz, deberemos presionar el botón del
bote de basura:

Fig. 121 - Eliminando un documento.

Finalmente hay que confirmar la eliminación presionando el botón “DELETE”.

Página | 446
Consultar un documento

La consulta es todavía más compleja que el resto de operaciones, pues una


consultar tiene verías secciones que analizar. Las consultas las hacemos
posicionándonos en una colección, una vez allí, podremos ver un panel de
búsqueda, el cual podemos expandir presionando el botón “OPTIONS” para ver
más opciones de búsqueda:

Fig. 122 - Opciones de búsqueda en MongoDB.

Una búsqueda en MongoDB se divide en las siguientes secciones:

447 | Página
• Filter: corresponde a la sección WHERE de SQL, en ella ponemos en
formato {clave: valor} los elementos a filtrar.
• Projection: En esta sección ponemos los campos que esperamos que
nos regrese la búsqueda, es similar a la sección (SELECT columna1,
columna2). La proyección se escribe en formato {clave: valor}.
• Sort: Esta sección se utiliza para ordenar los elementos de la respuesta
y correspondería a la instrucción ORDER BY de SQL. Esta sección se
escribe en formato {clave: valor}, donde la clave es el nombre del
campo a ordenar y el valor solo puede ser 1 o -1 para búsquedas
ascendentes y descendentes.
• Skip: Permite indicar a partir de que registro se regresen los resultados,
es utilizado con frecuencia para la paginación.
• Limit: Se utiliza para establecer el número máximo de registros que
debe de regresar la consulta. Se utiliza en conjunto con Skip para lograr
la paginación. Es similar a la instrucción TOP o LIMIT de SQL.

Aprender a realizar consultas


Sin duda, realizar consultas con MongoDB puede resultas un dolor de cabeza al
inicio, pues de estar a acostumbrados a realizar consultas muy estructuradas con
SQL, deberemos pasar a una sintaxis de JSON, pero una vez que lo
comprendamos, verás que es muy simple.

Filter

Lo primero que aprenderemos será utilizar la sección filter (filtro), en la cual


debemos definir las condiciones con las que un documento será selecciona o no,
muy parecido a la sentencia WHERE de SQL.

La forma más simple de filtrar un elemento, es cuando buscamos un campo que


sea igual a un valor, por ejemplo, queremos buscar todos los animales que tenga
color blanco o lo que es igual {color: “Blanco”}:

Página | 448
Fig. 123 - Busca de animales de color blanco.

Como puedes apreciar, solo hace falta indicar el campo y valor en formato {clave:
valor}.

Operadores lógicos

Los operadores lógicos son todas aquellas expresiones entre dos o más
operadores donde su evaluación da como resultado un booleano, y que por lo
tanto siguen la teoría del algebra de Boole (Tabla de la verdad). Los operadores
disponibles son los siguientes:

Operados Descripción
AND Retorna true si todas las condiciones son verdaderas

1. {$and: [{expresión 1}, {expresión 2}, {expresión N} ] }

OR Retorna true sí al menos una de las condiciones es verdadera

1. {$or: [{expresión 1}, {expresión 2}, {expresión N} ] }

NOT Niega cualquier expresión, por lo que un true lo convierte en false


y viceversa.

1. {$nor: [{expresión 1}, {expresión 2}, {expresión N} ] }

NOR Retorna true solo cuando todas las condiciones fallan.

1. { field: { $not: { <operator-expression> } } }

449 | Página
Operador AND

si queremos que adicional al color, la edad sea igual a 5. Solo deberemos agregar
ese nuevo campo en el filtro:

Fig. 124 - Búsqueda de animales blancos y con 5 años de edad.

Solo hace falta separar los valores con una coma, entre cada campo a filtrar. En
este caso, la condición se está evaluando como un AND, por lo que los dos
criterios se deberán cumplir para que el resultado sea retornado.

Esta misma búsqueda se podría lograr mediante la siguiente instrucción:

{ $and: [{nombre: “Perro”} , {edad: 5} ]}

En este formato, el clave deberá ser el operador $and y como valor, se pasa un
array, en donde cada posición corresponde a una condición. El array puede tener
2 o más condiciones.

La sintaxis de este operador es:

{$and: [{expresión 1}, {expresión 2}, {expresión N} ] }

Operador OR

Si lo que buscamos es realizar una búsqueda utilizando el operador OR,


deberemos cambiar un poco la sintaxis, por ejemplo, imagina que queremos
consultar todos los animales que tenga como nombre el valor “Perro” o “Gato”:

Página | 450
Fig. 125 - Búsqueda mediante el operador OR.

Como puedes observar, es necesario iniciar con el operador $or, y como valor,
deberemos enviarle un array, en donde cada posición del array, será una
condición a evaluar mediante el operador OR, en este arreglo puede haber de 2
a N condiciones.

La sintaxis de este operador es:

{$or: [{expresión 1}, {expresión 2}, {expresión N} ] }

Operador NOR (NO OR)

Este operador funciona prácticamente igual que OR, no la diferencia de que este
niega la expresión. Utilizamos el operador NOR para indicar expresiones como
“Selecciona todos los animales donde el nombre NO sea Perro o la edad NO sea
1”:

451 | Página
Fig. 126 - Operador NOR

De los 6 registros que tenemos, solo nos arroja 3, pues dos de ellos tiene edad
= 1 y uno tiene como nombre = Perro. En este operador con una sola condición
que se cumpla será suficiente para que el registro no se muestre.

Observemos que el tercer documento (Zapo) se está mostrando a pesar de tener


la edad=1, sin embargo, notemos que el valor es un String y no un numérico, lo
que lo hace diferente para la comparación.

La sintaxis de este operador es:

{$nor: [{expresión 1}, {expresión 2}, {expresión N} ] }

Operador NOT

El operador NOT se utiliza para negar una expresión, como parámetro recibe un
boolean o una expresión que lo retorne, para finalmente negar el valor.

Página | 452
Fig. 127: operador NOT

Para usar el operador NOT, es necesario indicarle un operador de comparación,


en este caso, usamos el comparador $eq (equals) para decirle no queremos
animales de color igual a blanco.

MongoDB ofrece varios operadores de comparación, pero los analizaremos más


adelante.

La sintaxis de este operador es:

{ field: { $not: { <operator-expression> } } }

Operadores de comparación

Los siguientes operadores ayudan a realizar comparaciones y pueden ser


utilizados en conjunto con los operadores lógicos

Operador Descripción
$eq Valida si un campo es igual a un valor determinado, su
nombre proviene de “equals”.

1. { <field>: { $eq: <value> } }

$ne Valida que un campo no sea igual a un valor determinado.


Su nombre proviene de “not equals”

453 | Página
1. { <field>: { $ne: <value> } }

$gt Valida si un campo es más grande que un valor determinado.


Su nombre proviene de “greater than”.

1. { <field>: {$gt: value} }

$gte Valida si un campo es más grande o igual a un determinado


valor. Su nombre proviene de “greater than or equals”.

1. { <field> : {$gte: value} }

$lt Valida que un campo sea menor que un valor determinado.


Su nombre proviene de “less than”.

1. {field: {$lt: value} }

$lte Valida que un campo sea menor o igual a un valor


determinado. Su nombre proviene de “less than or equals

1. {field: {$lte: value} }

$in Valida que el valor de un campo se encuentra en una lista de


valores, retorna true si existe al menos una coincidencia. Su
nombre proviene de “dentro de”

1. { field: { $in: [<value1>, <value2>, ... <valueN> ] } }

$nin Valida que el valor de un campo no se encuentre en ningún


valor de una lista. Regresa verdadero solo si el campo
evaluado no se encuentra en la lista de valores. Su nombre
proviene de “no en (una lista)”

1. { field: { $nin: [ <value1>, <value2> ... <valueN> ]} }

Hablar de cada operador de comparación puede resultar algo abrumador, pues


nos extenderíamos demasiado explicándolos, por lo que analizaremos solo un par
de estos operadores, ya que al final, todos se utilizan de la misma forma:

Regresando a la colección de animales, vamos a resolver algunas problemáticas


usando los nuevos operadores de comparación que hemos aprendido, el primero
a analizar es:

Caso 1:

Busquemos todos los animales que tenga más de 1 años y que sean de color sea
diferente de café:

Página | 454
Fig. 128 - Operadores de comparación $gt y $ne.

Vemos que, en esta consulta, hemos utilizado los operadores de comparación $gt
(mayor que un año) y $ne (Diferente de Café), adicional, nos hemos apoyado de
operador lógico $and para unir las dos condiciones.

Caso 2:

Encontremos todos los animales que sean “Perro, Conejo y Ratón” o animales
que tenga 4 patas:

Fig. 129 - Operadores de comparación $in y $eq

Veamos que utilizamos el operador $in (animales llamados Perro, Conejo o


Ratón) y el operador $eq (Animales con 4 patas), adicionalmente, nos hemos
apoyado del operador lógico $or para unir las dos condiciones.

455 | Página
Algo interesante en este query, es que solo el Zapo tiene la propiedad patas, lo
que comprueba la versatilidad que tiene MongoDB para crear estructuras
dinámicas.

En estos dos simples, pero prácticos ejemplos, hemos aprendido a utilizar los
operadores de comparación. Yo te invito que adicional a los ejemplos que hemos
planteado, te pongas un rato a jugar con el resto de operadores para que
compruebes por ti mismo como funcionan y aprender incluso a combinarlos con
los operadores lógicos.

Operadores sobre elementos

Los siguientes operadores son utilizados sobre los elementos de un documento,


los cuales tiene que ver con la existencia o no de ciertos elementos o incluso el
tipo de datos de los elementos.

Operador Descripción
$exists Valida si un documento tiene o no un campo determinado, si el
valor del operador es true, entonces buscará todos los documentos
que si cuenten con el campo, por otra parte, si el valor se establece
en false, entonces buscará todos los documentos que no cuente
con el campo.

1. { <field>: { $exists: <boolean> } }

$type Valida el tipo de datos de un determinado campo. Regresa true si


el tipo del campo coincide con el valor enviado.

1. { <field>: { $type: <BSON type number> | <String alias> } }

$all Este operador valida si un array del documento contiene todos los
valores solicitados.

1. { <field>: { $all: [ <value1> , <value2> ... ] } }

Caso 1:

Veamos el siguiente ejemplo. Seleccione todos los animales que tengan el


atributo “patas”:

Página | 456
Fig. 130 - Utilizando el operador de elementos $exists

Zapo es el único animal retornado, pues es el único que cuenta con la propiedad
“patas”. Observa que al operador le hemos puesto el valor true. Esto indica que
buscamos los que SI tengan el atributo, pero también le pudimos haber puesto
false, lo que cambiaría el resultado, pues buscaría solo los documentos que no
tuvieran el atributo. El operador $exists sol valida que el campo exista, sin
importar su valor

Caso 2:

Para probar el operador $type va a ser necesario crear un nuevo registro, el cual
será el siguiente:

Fig. 131 - Nuevo documento para el animal Vaca.

Quiero que prestes atención en el campo edad, pues a diferencia del resto, ha
este le he puesto que la edad es de tipo String, mientras que al resto les puse
Int32:

Fig. 132 - Operador $type.

457 | Página
Caso 3:

Otro ejemplo sería buscar sobre los elementos de un array con ayuda del
operador $all, para realizar una prueba con este operador deberemos crear dos
nuevos registros, los cuales tenga una lista de “apodos”. Yo he creado los
siguientes dos registros:

Fig. 133 - Dos nuevos registros con el array apodos.

Observa que los dos nuevos registros tienen el array “apodos”, sin embargo, no
tiene los mismos valores, el tercer valor es diferente entre los dos documentos.
Ahora bien, si yo quisiera recuperar los animales que tangan como apodos los
valores “Fido, Cachorro y Huesos” tendría que hacer lo siguiente:

Fig. 134 - Operador $all.

Como resultado, solo nos trae un registro de los dos, pues solo uno tiene los tres
valores indicamos en el operador $all.

Vamos a dejar hasta aquí las operaciones que nos da MongoDB, pues son las
más importantes que necesitaremos para el desarrollo de nuestra API. Si quieres
conocer el listado completo de operadores que soporta MongDB, te invito a que
te des una vuelta por la documentación oficial:

Página | 458
Project

La sección Project se utiliza para determinar los campos que debe de regresar
nuestra consulta, algo muy parecido cuando hacemos un “SELECT campo1,
campo2” a la base de datos. Esta sección es especialmente útil debido a que ayuda
a reducir en gran medida la información que retorna la base de datos, ahorrando
una gran cantidad de transferencia de datos y por lo tanto un aumento en el
performance.

Seleccionando o filtrando campos

Debido a que el viaje por la red es uno de los principales factores de degradación
de performance, es especialmente importante cuidar este aspecto. En MongoDB,
es muy fácil determinar los campos que queremos y no queremos en la
respuesta, pues tan solo falta listas los campos en formato {clave: val, calve:
val, …. } donde la clave es el nombre del campo en el documento y el val solo
puede tener 1 o 0, donde 1 indica que si lo queremos y 0 que no lo queremos.

Veamos un ejemplo para hacer esto más claro, imaginemos que queremos
recuperar el nombre y la edad de todos los animales:

Fig. 135 - Filtrando campos con MongoDB.

En la imagen podemos claramente que de todos los campos que tiene el


documento, solo nos regresó el nombre y la edad, aunque también vemos el _id,
esto se debe a que, por default, Mongo siempre lo regresa.

Ahora bien, si queremos que no nos regrese el _id, hay que decirle explícitamente
de la siguiente manera:

459 | Página
Fig. 136 - Eliminando el _id del resultado.

Observemos que hemos puesto el campo _id con valor a cero (0). El cero
MongoDB lo interpreta como que no lo queremos.

Ahora bien, así como le hemos dicho que campos queremos, también le podemos
indicar simplemente cuales no queremos y el resto si los retornará. Esta sintaxis
es muy cómoda cuando queremos todos los campos con excepción de unos
cuantos, lo que nos ahorra tener que escribir todos los campos que tiene. Veamos
un ejemplo de esto, imaginemos que queremos todos los campos, excepto, el
nombre:

Fig. 137 - Excluyendo solo el nombre del documento.

Trabajando con objetos

Página | 460
El último tipo de selección que veremos será sobre un objeto, para esto
tendremos que hacer algunos cambios. He editado el último registro de la
colección para agregarle una nueva propiedad llamada propiedades la cual es de
tipo Object, y dentro de ella he puesto 3 nuevos campos, alto, largo y peso,
todos estos de tipo Int32.

Fig. 138 - Agregando la propiedad propiedades.

Dado que “propiedades” es un objeto, podríamos determinar que no requerimos


(o requerimos) un valor de este objeto anidado. Imaginemos que requerimos
todos los campos del documento con excepción del campo alto del objeto
propiedades.

Fig. 139 - Obteniendo todo el documento excepto la propiedad alto.

Veamos que en este caso la sintaxis ha cambiado ligeramente, para empezar,


tenemos que poner todo el path hasta llegar a la propiedad mediante puntos “.”,
y este debe de estar entre comillas. En este caso pusimos 0 por que no lo
queríamos, pero igualmente podríamos poner un 1 para que solo nos traiga ese
campo.

Si quieres seguir profundizando en la sección Project, puedes revisar la


documentación oficial de MongoDB

461 | Página
Sort

La sección sort es utilizada para ordenar los resultados, es sin duda de las
secciones más simples, pues solo hace falta indicar los campos a ordenar y el
sentido de la ordenación (ascendentes = 1 o descendente = -1).

Esta sección se define en el formato {key: {1|-1} }, donde key es el nombre del
campo a ordenar.

Veamos unos ejemplos, imaginemos que queremos ordenar los animales por
nombre de forma ascendente:

Fig. 140 - Ordenamiento Ascendente.

Ahora bien, imaginemos que queremos ordenar por nombre ascendente y color
descendente:

Página | 462
Fig. 141 - Ordenamiento ascendente y descendente.

Podemos ver qué cambio el cuarto registro, pues el orden decreciente del color
provoco un reordenamiento.

Paginación con Skip y Limit

Tanto el campo Skip y Limit reciben un valor numérico, es decir que no requieren
un objeto JSON {key:value}. Limit permite determinar cuántos registros
máximos debe de regresar la consulta y Skip indica a partir de que elemento de
empieza a regresar los valores.

Veamos la primera prueba, voy a solicitar que me regrese los 2 primeros


elementos de la lista:

463 | Página
Fig. 142 - Limitando el número de registros con limit.

Ahora bien, si a esto le sumamos la propiedad skipe para que salte el primero
resultado, esto recorrerá la búsqueda en un registro, de tal forma que el segundo
registro se convertirá en el primero y el segundo que veamos, corresponderá al
3 resultado de la búsqueda:

Fig. 143 - Probando la instrucción Skip.

En este momento tenemos 9 registros, por lo que podríamos paginar por bloques
de 3, realizando la siguiente combinación:

• Skip = 0 y Limit = 3
• Skip = 3 y Limit = 3
• Skip = 6 y Limit = 3
• Skip = 9 y Limit = 3 (ya no tendría más resultados)
La técnica es muy simple, primero que nada, ponemos en limit el tamaño de los
bloques que queremos consultar y luego en Skipe debemos ejecutar en múltiplos
del valor colocado en limit, pero siempre empezando en 0.

Página | 464
Fig. 144 - Ejemplo de paginación en bloques de 3.

Schemas del proyecto Mini Twitter

Una vez que hemos aprendido como a crear schemas con Mongoose, vamos a
practicar creando los modelos que utilizaremos en el proyecto Mini Twitter, sin
embargo, esta parte ya no será parte de la aplicación, si no del API.

Tweet Scheme

El schema Tweet lo utilizaremos para guardar los Tweets de todos los usuarios.
En la aplicación Mini Twitter, tratamos las respuestas de los Tweet, como un
nuevo Tweet, con la única diferencia que tiene una referencia al Tweet padre,
esta referencia se lleva a cabo mediante el campo tweetParent. Este schema
deberá ser creado en el archivo Tweet.js en el path /api/models.

1. var mongoose = require('mongoose')


2. var Schema = mongoose.Schema
3.
4. var tweet = Schema({
5. _creator: {type: Schema.Types.ObjectId, ref: 'Profile'},
6. tweetParent: {type: Schema.Types.ObjectId, ref: 'Tweet'},
7. date: {type: Date, default: Date.now},
8. message: String,
9. likeRef: [{type: Schema.Types.ObjectId, ref: 'Profile', default: []}],
10. image: {type: String},
11. replys: {type: Number, default: 0}
12. })
13. //Virtuals fields
14. tweet.virtual('likeCounter').get(function(){
15. return this.likeRef.length
16. })

465 | Página
17. var Tweet = mongoose.model('Tweet', tweet);
18. module.exports= Tweet

Los campos que podemos apreciar son los siguientes:

• _creator: representa el ID del usuario que creo el Tweet, esta propiedad se


guardar como un ObjectId, y el atributo ref le indica a Mongoose, que este
ID debe de corresponder con el schema Profile.
• tweetParent: Cuando el Tweet es una respuesta a otro Tweet, este campo
deberá contener el ObjectId al Tweet padre, el atributo ref le indica a
Mongoose que este campo deberá corresponder con el schema Tweet.
• date: fecha de creación del Tweet. El atributo default le indica a Mongoose,
que en caso de estar null al momento de insertar el documento, este deberá
poner la fecha y hora actual.
• message: corresponde al texto capturado por el usuario.
• likeRef: Es un arreglo con el ID de los usuarios que dieron like al Tweet.
• Image: Imagen asociada al Tweet, siempre y cuando tenga una imagen.
• Replys: Contador de respuestas que tiene el Tweet. Es decir, número de
Tweet hijos.

Para poder tener un contador de los likes que tiene el tweet, creamos un campo
virtual (línea 14), el cual retorna el número de posiciones que tiene el campo
likeRef.

Este schema es ligado a la colección Tweet, como podemos ver en la línea 17.
Finalmente exportamos el schema para poder ser utilizado más adelante en el
API.

Profile Scheme

El schema Profile, representa a un usuario dentro del proyecto Mini Twitter, en


él se guardan los datos básicos de su perfil, y una referencia a las personas que
sigue y lo siguen.

1. var mongoose = require('mongoose');


2. var uniqueValidator = require('mongoose-unique-validator');
3. var Schema = mongoose.Schema;
4. var Tweet = require('./Tweet')
5.
6. var profile = new Schema({
7. name: {type: String},
8. userName: {type: String, unique: true, index: true,
9. uniqueCaseInsensitive: true},
10. password: String,
11. description: {type: String, default: 'Nuevo en Twitter'},
12. avatar: {type: String, default: null},
13. banner: {type: String, default: null},
14. tweetCount: {type: Number, default: 0},
15. followingRef: [{type: Schema.Types.ObjectId, ref: 'Profile', default: [] }],
16. followersRef: [{type: Schema.Types.ObjectId, ref: 'Profile', default: [] }],
17. date: {type: Date, default: Date.now},

Página | 466
18. });
19. //Unique plugin validate
20. profile.plugin(uniqueValidator, { message: 'El {PATH} to be unique.' });
21.
22. //Helpers
23. profile.query.byUsername = function(userName){
24. return this.find({userName: userName})
25. }
26. //Virtuals fields
27. profile.virtual('following').get(function(){
28. return this.followingRef.length
29. })
30. profile.virtual('followers').get(function(){
31. return this.followersRef.length
32. })
33.
34. var Profile = mongoose.model('Profile', profile);
35. module.exports= Profile

Los campos que podemos apreciar son:

• name: Nombre completo del usuario.


• userName: Representa el nombre de usuario, este campo lo marcamos
como índice mediante el atributo index, además, este campo debe de ser
único. Para valida que el campo sea único, estamos utilizando el plugin
mongoose-unique-validator, el cual nos permite agregar los atributos
unique y uniqueCaseInsensitive, lo que hace que el campo sea validado
sin importar mayúsculas y minúsculas.
• Password: Password del usuario. Por seguridad, encriptaremos el
password antes de guardarlo, previniendo que cualquier persona con
acceso a la base de datos pueda conocer el password real del usuario.
• Description: Breve descripción acerca de la persona. Definimos el valor
“Nuevo en Twitter” por default.
• Avatar: Imagen del avatar.
• Banner: Imagen del banner
• tweetCount: Número de Tweets que ha publicado el usuario.
• followingRef: Arreglo con el ID de todos los usuarios que sigue.
• followerRef: Arreglo con el ID de todos los usuarios que lo siguen.
• Date: fecha de registro del usuario. Por default es la fecha y hora actual
del servidor.

En la línea 20 registramos el plugin mongoose-unique-validator, el cual nos


ayudará a validar que no guardemos un userName repetido y nos arroje un error
personalizado.

Debido a que una de las búsquedas más frecuentes, es la búsqueda por nombre
de usuario (userName), hemos creado un método helper (línea 23), el cual nos
retornará un usuario que corresponda con el nombre de usuario solicitado.

467 | Página
Adicional, hemos creados las propiedades virtuales followers (línea 30) y
Followings (línea 27), lo cual retornan el número de personas que sigue y los
que lo siguen.

Finalmente creamos el Schema ligado a la colección profiles (línea 34)

Para que este schema funcione, debemos de instalar el plugin de validación:

npm install --save-dev mongoose-unique-validator@2.0.3

Ejecutar operaciones básicas

Los schemas no solo sirven para definir la estructura de los documentos, sino
que, además, proporcionan una serie de operaciones para insertar, actualizar,
borrar y consultar los documentos.

Save

Mongoose proporciona dos formas de crear un nuevo documento, una es por


medio del método de instancia save y el método statico create. La diferencia
fundamental entre estas dos técnicas, es que en la primera (save) creamos el
documento a partir de un objeto del Schema previamente creado, mientras que
la segunda forma (create) se crea el documento a partir de un objeto simple.
Veamos un ejemplo:

Save

1. var Tweet = mongoose.model('Tweet', yourSchema);


2.
3. var myTweet = new Tweet({
4. _creator: ObjectId('5a012b3986b5c864a4fe6225')
5. message: 'Mi primer tweet'
6. });
7. myTweet.save(function (err) {
8. if (err) return handleError(err);
9. //Tweet saved
10. })

En el fragmento de código podemos apreciar la creación de un tweet por medio


del método save. Apreciemos que en la línea 3 hemos creado el Tweet por medio
del operador new, con lo cual, creamos un objeto de tipo Tweet. Una vez creado
el objeto Tweet, podemos simplemente llamar la función save y si todo sale bien,
habremos creado un nuevo documento en la base de datos.

Página | 468
Observemos que el método save se ejecuta sobre el objeto myTweet, el cual es
una instancia del schema,

Create

1. var Tweet = mongoose.model('Tweet', yourSchema);


2.
3. Tweet.create({
4. _creator: ObjectId('5a012b3986b5c864a4fe6225')
5. message: 'Mi primer tweet'
6. }, function (err, myTweet) {
7. if (err) return handleError(err);
8. // Tweet created
9. })

En este nuevo ejemplo, estamos creando un nuevo documento por medio de


método create. Este método se ejecuta directamente sobre el Schema y recibe
como parámetro un objeto con la estructura del documento. Este objeto puede
tener la estructura que sea.

Cabe mencionar, que los dos ejemplos que mostramos a continuación, dan el
mismo resultado.

Find

En MongoBD se le conoce como find a la operación de búsqueda, lo que sería


equivalente al SELECT de SQL. Podemos buscar documentos por medio de dos
métodos, find y findOne. La primera (find) permite buscar cero o más
documentos, mientras que el segundo método (findOne) permite buscar un solo
documento.

Método find

El método find siempre regresa un array, por lo que, si no encuentra ningún


documento, este regresa un array vacío. El formato de este método es
find(query, projection).

El método find es estático, por lo que lo deberemos de ejecutar sobre el Schema,


veamos un ejemplo:

1. Tweet.find({_id: tweet.id},{message:1,image:1}) //Return array => []

En este ejemplo, estamos buscando un tweet por medio de su ID, le indicamos


que solo queremos el campo message e image mediante la proyección.

Método findOne

469 | Página
El método findOne funciona exactamente igual que find, con la única diferencia
de que este solo regresa un objeto, por lo que, si más de un documento
concuerdan con el query, entonces será retornado el primer documento
encontrado. Por otra parte, si no se encuentra ningún documento, entonces se
regresa null.

1. Tweet.findOne({_id: tweet.id},{message:1,image:1}) //Return one object => {}

NOTA: findOne no soporta las operaciones skip, limit y sort.

Métodos Skip, Limit y Sort

Los operadores skip, limit y sort, solo pueden ser utilizados en conjunto de la
operación find. La forma de utilizarlos es la siguiente:

1. find({_id: tweet.id},{message:1,image:1})
2. .sort( { date: 1 } )
3. .limit( 10 )
4. .skip( 5 )
5. .limit(5)
6. .exec(callback(err, returns){
7. //Any action
8. })

Como podemos ver, la función find se ejecuta normalmente, con la diferencia


que agregamos las instrucciones sort, limit, skip, limit y al final siempre
deberemos ejecutar la función exec para ejecutar la consulta. Exec recibe dos
parámetros, el error (si hay) y la respuesta del query.

NOTA: Para los query podemos utilizar todos los operadores lógicos, de
elementos y comparación que vimos al inicio de este capítulo.

Update

Update es el procedimiento por el cual es posible actualizar un documento


previamente existente en la base de datos. Su nombre es el único que
corresponde con los comandos tradicionales de SQL. Existen 4 formas de
actualizar un documento.

Método update

El método estático update es una instrucción que nos permite actualizar solo el
primer documento que concuerden con un filtro sin retornarlo. Como retorno
obtendremos el número de registros seleccionados y el número de documentos
actualizados.

La función tiene el siguiente formato update(query,update), donde query es el


criterio de búsqueda y update son los campos a actualizar

Página | 470
1. Profile.update(
2. {userName: 'oscar'},
3. {name: 'Oscar Blancarte, description: 'Nuevo en Twitter'}
4. function( err, response){
5. //Any action
6. })

El ejemplo anterior actualiza el campo name y description para el usuario con


userName = oscar. Al terminar la consulta, ejecuta una callback con dos
parámetros, un error (si hay) y la respuesta.

La respuesta es un objeto que contiene información de los registros actualizados,


el cual se ve de la siguiente manera:

1. {
2. n: 1,
3. nModified: 1,
4. opTime:
5. { ts: Timestamp { _bsontype: 'Timestamp', low_: 2, high_: 1510367840 }, t: 1 },

6. electionId: 7fffffff0000000000000001,
7. ok: 1
8. }

De la respuesta solo nos deberá interesar 3 campos:

• n: número de documentos seleccionados por el query.


• nModified: Número de documentos realmente actualizados
• ok: indica si la operación termino correctamente.
Por lo general, siempre nos fiamos en el campo nModified para asegurar que la
operación actualizo al menos un documento.

Método updateMany

El método updateMany también es un método estático, y funciona exactamente


igual que update, con la única diferencia de que este método actualizado todos
los documentos que coincidan con el query, a diferencia de update que solo
actualiza el primero documento.

1. Profile.updateMany(
2. {userName: 'oscar'},
3. {name: 'Oscar Blancarte, description: 'Nuevo en Twitter'}
4. function( err, response){
5. //Any action
6. })

La respuesta es también igual que update.

471 | Página
Método save

El método de instancia save, ya lo habíamos analizado, pues sirve para crear un


documento. Sin embargo, también es posible actualizar un documento existente
mediante este método, sin embargo, para que esto funcione como un update,
deberemos ejecutarlo sobre un objeto que allá sido retornado como parte de un
find. Veamos un ejemplo:

1. Profile.findOne({userName: 'oscar'}, function(err, user){


2. user.description = 'Mi nueva descripción'
3. user.save(function(err, saveUser){
4. //Update completed
5. })
6. })

Cómo podemos ver en este ejemplo, buscamos a un usuario (línea 1), luego
actualizamos la descripción (línea 2) directamente sobre el objeto retornado.
Finalmente, guardamos los cambios mediante el método de instancia save.

Esta forma de actualizar solo se recomienda cuando ya tiene el documento


cargado, pues consultar y luego actualizar es doble trabajo para para la base de
datos.

Remove

Remove es la operación que permite borrar uno más documentos de la base de


datos. Esta operación elimina todos los documentos que coinciden con una
operación. El formato para invocar es el siguiente:

1. Profile.remove({userName: 'oscar'}, function(err){


2. //Any action
3. })

En el ejemplo pasado estamos eliminando todos los usuarios que tenga como
nombre de usuario, oscar.

Population

Population es una de las operaciones más poderosas que tiene Mongoose, pues
permite simular la instrucción JOIN de SQL.

Population es una operación por medio de la cual es remplazado un ObjectId por


el documento real. Para lograr esto, Mongoose requiere de un metadato, el cual
le indique que schema corresponde el ObjectId, para finalmente consultar el
documento y remplazarlo por el ObjectId.

Para entender cómo funciona population, vamos a repasar el schema de Tweet.

Página | 472
1. var tweet = Schema({
2. _creator: {type: Schema.Types.ObjectId, ref: 'Profile'},
3. tweetParent: {type: Schema.Types.ObjectId, ref: 'Tweet'},
4. date: {type: Date, default: Date.now},
5. message: String,
6. likeRef: [{type: Schema.Types.ObjectId, ref: 'Profile', default: []}],
7. image: {type: String},
8. replys: {type: Number, default: 0}
9. })

Observemos los campos _creator y tweetParent, ambos son de tipo ObjectId y


los tiene un atributo llamado ref, este último, le indica a Mongoose, de que
schema pertenece el ObjectId, de esta forma, cuando se ejecuta la operación
populate, Mongoose buscará el atributo ref para luego buscar el documento en
la colección correspondiente.

Veamos un ejemplo para ver cómo funciona:

1. Tweet.find({})
2. .populate("_creator")
3. .exec(function(err, tweets){
4. //Any action
5. })

En este ejemplo, consultamos todos los tweets y luego realizamos un populate


al creador del tweet. Veamos un ejemplo:

El siguiente documento corresponde a un tweet, tal cual se guarda en la colección


de la base de datos. Ahora bien, observa el campo _creator, el cual tiene como
valor el ObjectId del usuario que creo el Tweet.

1. {
2. "_id": "5a0657ad3ccd98529d83a9b9",
3. "_creator": ObjectId('5a05286db5371dffe40bafae'),
4. "date": "2017-11-11T01:51:41.421Z",
5. "message": "test",
6. "likeCounter": 0,
7. "replys": 0,
8. "image": null
9. }

Tras realizar la operación populate, el ObjectId es remplazado por el objeto que


corresponde al usuario (profile).

1. {
2. "_id": "5a0657ad3ccd98529d83a9b9",
3. "_creator": {
4. "_id": "5a05286db5371dffe40bafae",
5. "name": "Jaime",
6. "userName": "jaime",
7. "avatar": "<img base64>"
8. },

473 | Página
9. "date": "2017-11-11T01:51:41.421Z",
10. "message": "test",
11. "likeCounter": 0,
12. "replys": 0,
13. "image": null
14. }

La operación populate funciona para objetos simples como el que acabamos de


ver, pero también funciona bien para los Array, por lo que remplaza todos
ObjectId por el objeto correspondiente.

Página | 474
Resumen

Como hemos analizado en este capítulo, MongoDB es mucho más potente de lo


que hubiéramos imaginado en un inicio, pues permite realizar básicamente todas
las operaciones que realizamos en una base de datos tradicional y hemos
analizado el rol que juega MongoDB en las aplicaciones modernas y por qué vale
la pena aprender esta tecnología.

También nos hemos adentrado al funcionamiento de MongoDB, aprendiendo los


operadores más importantes como lo son los operadores lógicos, operadores de
elementos y operadores de comparación. También hemos aprendido las
seccionas básica de una consulta en Mongo, como lo son el filter, project, sort,
limit, skip.

Tras conocer cómo funciona MongoDB, aprendimos como conectarnos con


NodeJS y cómo es que a través de los schemas es posible definir la estructura de
nuestros documentos y como es que los schemas son la base para realizar las
operaciones más básicas como lo son, find, findOne, update, updateMany,
findOneAndUpdate, remove, save.

475 | Página
Desarrollo de API REST con
NodeJS
Capítulo 17

Tras un largo camino, ha llegado el momento de entrarle de lleno al desarrollo


del API para nuestro proyecto Mini Twitter. Para muchos, este será uno de los
capítulos más interesantes, pues aprenderemos a crear desde cero un API
utilizando NodeJS, Express y MongoDB.

¿Qué es REST y RESTful?

Una de las grandes preguntas que todo desarrollador se plantea al momento de


iniciar el desarrollo de servicios web, es ¿Cuál es la diferencia entre REST y
RESTful? Pues por lo general, no suelen ser explicados de una forma clara,
sumado a que los conceptos se pueden confundir, incluso, muchos creen que
REST es la abreviación de RESTful.

Para empezar, REST (Representational state transfer) es un estilo de


Arquitectura de software que se ejecuta sobre el protocolo de comunicación
HTTP. En este sentido, podríamos decir que toda la WEB está bajo esta
arquitectura, pues absolutamente toda la comunicación que hace el navegador
es mediante HTTP. Dicho esto, nos podemos dar cuenta que REST no es para
nada nuevo.

La arquitectura REST se rige por una serie de principios que son clave para el
entendimiento entre el cliente y el servidor. Estos principios son los siguientes:

• Todos lo que se mueve a través de HTTP es considerado un Recurso y cada


recurso cuenta con una URL única. Lo que significa que no podrán existir dos
recursos diferentes en una misma URL.
• Todos los recursos tienen un formato particular que describe el tipo de
contenido, el cual es utilizado por el cliente para saber cómo interpretar el
recurso recuperado. El tipo de contenido se establece en las cabeceras de
HTTP mediante el valor Content-Type, el cual varía según el tipo de recurso.
Por ejemplo, image/png, image/gif, text/html, application/json,
applicacipon/xml, etc.

Página | 476
• Toda la comunicación por medio de HTTP deberá utilizar los verbos o métodos
definidos por el protocolo, por ejemplo, GET, POST, PUT, DELETE, etc. Ya
hablamos acerca de los verbos en el pasado.
• Un mismo recurso puede tener múltiples representaciones, lo que quiere
decir que, es posible enviar la misma información en diferente formato, por
ejemplo, es posible enviar un documento en formato JSON o en XML o una
imagen en formato JPEG o PNG.
• Toda la comunicación que se realiza por medio de HTTP es sin estado
(Stateless), lo que significa que cada petición es tratada de forma
independiente y todas las ejecuciones con los mismos parámetros de entrada
deberá arrojar el mismo resultado.

Por otra parte, tenemos RESTful, el cual son los servicios web que se crean
siguiente la arquitectura REST y los principios fundamentales.

A pesar de que REST ya es viejo, los servicios RESTful empezaron a tomar


protagonismo con la llega de la arquitectura SOA, pues se empezó a ver la
ventaja de utilizar los servicio como una forma de integrar a aplicación y
desacoplando los componentes con recursos web accesibles de forma estándar e
interoperables.

En este sentido, podríamos resumir que REST es un estilo arquitectónico y


RESTful son servicio web implementados a partir de la arquitectura REST.

Fig. 145 - REST y RESTful

REST vs SOA

Es muy probable que el termino SOA (Service-Oriented Architecture) te sea


mucho más conocido que REST o RESTful, pues SOA es otro estilo de arquitectura
que ha tenido mucho éxito en los últimos años. Sin embargo, cuando nos
ponemos a pensar detenidamente, es muy probable que ahora te estés
preguntando qué diferencia tiene REST y SOA o más precisamente RESTful y
SOA, pues estas dos últimas promueven la creación de servicios.

477 | Página
Para responder esta pregunta, es necesario entender que es SOA. SOA es un
estilo de arquitectura de software que promueve el desarrollo de servicios como
la unidad más pequeña de un software, y que a partir de los servicios es posible
crear cosas más complejas.

Ahora bien, tanto en SOA como en REST es posible crear servicios, pero existe
una diferencia importante, REST se limita a la comunicación mediante el
protocolo HTTP, mientras que SOA es una arquitectura de más alto nivel, en
donde no se habla de una tecnología específica para el transporte de mensajes,
como lo es HTTP. Esto quiere decir que con SOA es posible tener servicios con
HTTP, pero también es posible utilizar otras tecnologías como Colas de mensajes,
correo electrónico, FTP, TCP, etc.

En este sentido, podríamos decir que RESTful implementa la arquitectura SOA y


REST al mismo tiempo, sin embargo, SOA puede o no implementar REST.

Fig. 146 - La relación entre SOA, REST y RESTful.

Preparar nuestro servidor REST

Finalmente, tras una pequeña introducción a lo que es SOA, REST y RESTful,


podemos iniciar con el desarrollo del API. Para ello, lo primero que necesitaremos
será, crear un servidor capaz de procesar las solicitudes HTTP y atender en los
diferentes métodos.

Antes de comenzar, debemos de entender que hasta el momento, siempre hemos


estado utilizando un servidor NodeJS, sin embargo, no haremos conscientes de
ello. En el momento en que ejecutamos el comando npm start, lo que estamos
haciendo es en realidad levantar un servidor integrados que provee Webpack, el
cual se llama webpack-dev-server, que incluso, lo podemos ver como librería en
el archivo package.json.

El problema con este servidor es que nos permite hacer cosas muy básicas, que
para correr una aplicación de React es más que suficiente, sin embargo, si
necesitamos construir un API REST, entonces las cosas se complican, por lo que

Página | 478
estamos obligados a construir nuestro propio servidor y personalizarlo como
mejor se adapte a nuestras necesidades.

Para crear nuestro propio servidor es necesario instalar varias dependencias, de


las cuales, ya hemos instalado la mayoría en las secciones anteriores, porque
vamos a hacer una recopilación de las librerías necesarias y explicaremos para
que son necesarias.

npm install --save-dev express@4.17.1


npm install --save-dev body-parser@1.19.0
npm install --save-dev webpack-dev-middleware@1.10.2
npm install --save-dev webback-dev-server@3.10.3
npm install -g nodemon

La primera y más obvia de las librerías es express, el cual es un framework ligero


para crear aplicaciones web con NodeJS. Mediante este módulo podremos
exponer los servicios que expondrá nuestro API.

Para poder parsear el payload de los mensajes, utilizaremos body-parser, el cual


ya hemos analizado en el pasado.

Las siguientes librerías son webpack-dev-middleware y webpack-dev-server, que


en realidad, son las librerías que habíamos estado utilizando sin saberlo para
crear nuestro servidor integrado, pero ahora los utilizaremos explícitamente solo
cuando corramos nuestro servidor en modo desarrollo. La ventaja sustancial de
webpack-dev-server es que detecta cuando cambiamos un archivo y realiza la
transpilación de forma automática, para que no sea necesario tener que estar
prendiendo y apagando el servidor cada vez que realizamos en cambio en el
código, lo que nos hace significativamente más productivos.

Por otro lado, nodemon nos permite reiniciar automáticamente el servidor cada
vez que detecta un cambio en el código, un poco contradictorio con lo que hace
Webpack, sin embargo, debemos de tener en cuenta que webpack solo compila
los archivos de la app, es decir, todo los que tenga que ver con React, por lo
tanto, cada vez que realicemos un cambio en los servicios, entonces no se
reflejarán sino hasta reiniciar el servidor, y como esto puede ser una terea
bastante molesta, el módulo nodemon se encarga precisamente de detectar los
cambios y realizar el reinicio automático del servidor.

Finalmente, serán necesaras todas librerías que ya conocemos de Babel para que
webpack pueda realizar la transpilación, sin embargo, no veo necesarios
mencionarlas en este punto.

479 | Página
Configuración inicial del servidor

Dicho todo lo anterior, procederemos con la creación de nuestro servidor, por lo


que crearemos el archivo server.js en la raíz del proyecto:

1. var express = require('express');


2. var app = express();
3. var bodyParser = require("body-parser")
4. var path = require('path')
5. var webpack = require('webpack')
6. var config = require('./webpack.config')
7. var compiler = webpack(config)
8.
9. app.use('/public', express.static(__dirname + '/public'));
10. app.use(bodyParser.urlencoded({extended: false}));
11. app.use(bodyParser.json({limit:'10mb'}));
12.
13. app.use(require('webpack-dev-middleware')(compiler, {
14. noInfo: true,
15. publicPath: config.output.publicPath
16. }));
17.
18.
19. app.get('/*', function (req, res) {
20. res.sendFile(path.join(__dirname, 'index.html'));
21. });
22.
23. app.listen(8080, function () {
24. console.log('Example app listening on port 8080!');
25. });

En la primera sección (líneas 1 a 7) tenemos los imports de todos los módulos


requeridos, como express, body-parser, path y webpack, los cuales ya hemos
abordado en el pasado, con excepción de path, el cual es el módulo de utilidades
para trabajar con rutas de archivos y directorios, nada del otro mundo; a medida
que sea requerido, lo vamos a ir explicando, por ahora, solamente debemos de
saber que es parte de las librerías estándar de NodeJS, por lo que no hay que
hacer una instalación.

En la línea 9 creamos un Middleware incorporado static para exponer la carpeta


public de forma pública. De esta forma, todo el contenido de la carpeta public,
podrá ser accedida directamente desde el navegador mediante le path
<host>/public/.

En las líneas 10 y 12 configuramos el middleware body-parser para procesar los


mensajes entrantes y convertirlos en JSON.

En las líneas 13 a 16 estamos utilizando el módulo webpack-dev-middleware para


compilar todos los archivos JavaScript y crear el archivo bundle.js. Este
middleware solo lo deberemos utilizar en ambiente de desarrollo, pues crea el
archivo bundle.js en memoria, además que está diseñado para ayudar al
desarrollador. Más adelante en este libro, cuando abordemos el pase a
producción, veremos como condicionar estas líneas para que no se ejecuten en
un entorno de producción.

Página | 480
En las líneas 19 a 21 creamos un router para atender todas las peticiones en el
método GET sin importar el path (/*), el cual regresará el archivo index.html.
Con esto nos aseguraremos que sin importar a que URL entremos, la aplicación
se montará, sin embargo, recordemos que al final, React Router determinar que
componentes mostrar dependiendo la URL.

Finalmente, en las líneas 23 a 25 levantamos el servidor y le indicamos que reciba


peticiones por el puerto 8080. El método listen recibe dos parámetros, el puerto
por el que estará escuchando peticiones y un callback que se ejecuta cuando el
servidor se inicia, en el cual se puede hacer cualquier cosa, aunque por lo general,
se escribe en el log para informar al usuario que el servicio ya está activo.

En este punto nuestro servidor ya debería de estar operativo, por lo que solo
restaría ejecutarlo con el comando:

node ./server.js

Y finalmente entramos al navegador a la URL http://localhost:8080 para la


aplicación funcionando como hasta ahora:

Ahora bien, si aplicamos cualquier cambio en el servidor, veremos que este no


se reflejará hasta que reiniciemos la aplicación, lo que puede ser sumamente
molesto y propenso a errores, pues muchas veces realizamos un cambio sin
reiniciar el servidor, lo que da pie a que creamos que los cambios no se están

481 | Página
reflejando, pero en realidad, nunca los aplicamos. Para solucionar este problema,
tenemos nodemon, el cual nos permite ejecutar la aplicación exactamente igual
que con el comando node, con la única diferencia de que nodemon reiniciar el
servidor cada vez que apliquemos un cambio.

Para ejecutar la aplicación con nodemon es necesario primero que nada instarlos
como librería global, que fue lo que hicimos al comienzo de esta unidad, el
siguiente paso es ejecutar comando:

nodemon ./server.js

Establecer conexión con MongoDB

Para conectar nuestro servidor a la base de datos MongoDB realizaremos los


siguientes ajustes al archivo server.js:

1. var express = require('express');


2. var app = express();
3. var bodyParser = require("body-parser")
4. var path = require('path')
5. var webpack = require('webpack')
6. var config = require('./webpack.config')
7. var compiler = webpack(config)
8. var mongoose = require('mongoose')
9. var configuration = require('./serverConfig')
10.
11. var opts = {
12. useNewUrlParser: true,
13. appname: "Mini Twitter",
14. poolSize: 10,
15. autoIndex: false,
16. bufferMaxEntries: 0,
17. loggerLevel: "error", //error / warn / info / debug
18. keepAlive: 120,
19. validateOptions: true,
20. useUnifiedTopology: true
21. }
22.
23. let connectString = configuration.mongodb.connectionString
24. mongoose.connect(connectString, opts, function(err){
25. if (err) throw err;
26. console.log("==> Conexión establecida con MongoDB");
27. })
28.
29. app.use('/public', express.static(__dirname + '/public'))
30. app.use(bodyParser.urlencoded({extended: false}))
31. app.use(bodyParser.json({limit:'10mb'}))
32.
33. app.use(require('webpack-dev-middleware')(compiler, {
34. noInfo: true,
35. publicPath: config.output.publicPath
36. }))
37.
38. app.get('/*', function (req, res) {
39. res.sendFile(path.join(__dirname, 'index.html'))
40. });
41.

Página | 482
42. app.listen(configuration.server.port, function () {
43. console.log(`Example app listening on port ${ configuration.server.port}!`)
44. });

Lo primero que realizaremos será, agregar los imports a Mongoose (línea 8) y


nuestro archivo de configuración serverConfig.js (línea 9).

El siguiente paso es definir las propiedades para configurar Mongoose (líneas 11


a 23), las cuales explicamos en el capítulo anterior.

Seguido, obtenemos el String de conexión (línea 25) del archivo de configuración


y establecemos conexión con MongoDB mediante el método connect de
Mongoose (línea 26 a 28). La conexión también la explicamos en la sección
pasada.

Finalmente, en la línea 44 vamos a remplazar el puerto hardcode del servidor, a


uno configurable en el mismo archivo serverConfig.js, por lo que también
tendremos agregar el puerto a ese mismo archivo:

1. module.exports = {
2. server: {
3. port: 8080
4. },
5. mongodb: {
6. connectionString: "<CONNECTION_STRING>"
7. }
8. }

En este punto solo faltaría guardar los cambios para que la aplicación se actualice,
de tal forma que, deberemos ver el siguiente resultado en la consola:

Fig. 184147 - Conexión exitosa a MongoDB

Creando un subdominio para nuestra API

Una de las cuestiones más importantes a la hora de publicar un API por internet,
es definir la URL por medio de la cual el API atenderá las solicitudes. Dicha URL
debe de ser significativa, de tal forma que con tan solo ver la URL podremos
identificar que API es y a qué ambiente pertenece (desarrollo, pruebas,
producción).

483 | Página
Por lo general, las API son publicadas sobre el mismo domino de la aplicación
principal, y existen dos formas de hacer esto, la primera y más simple es que
reservemos un path del dominio para atender solicitudes del API, por ejemplo:
http://test.com/api/*. Esto quiere decir que cualquier cosa que llegue con el
path /api, será atendida por el API. Esta estrategia puede resultar atractiva, sin
embargo, no es lo más recomendable, por varias razones:

• la primer y más importante, es que tanto la aplicación como el API


compartirían la misma IP, pues a nivel DNS, es el mismo dominio, lo que
complica la gestión.
• En segundo lugar, tenemos que reservar paths para el API, lo que nos
quita toda la gama de URL para la aplicación.
• En tercer lugar, el ser una subsección del dominio principal, Google o los
demás buscadores podrían indexar los servicios o las páginas de
documentación, como parte de la aplicación, lo cual no siempre es
deseado.

La segunda opción es publicar nuestra API como un subdominio de nuestro


dominio principal, por ejemplo http://api.test.com/*. De esta forma, podemos
configurar en nuestro DNS una dirección IP de destino diferente a la de nuestra
aplicación, de esta forma, separamos la administración de nuestra aplicación de
nuestra API.

Otra de las ventajas, es que ya no es necesario reservar ninguna URL para el


API, y como es un subdominio, es posible instalarle el mismo certificado de
seguridad del dominio principal.

Exponer el API como un subdominio es una mejor estrategia, y una muestra clara
de esto, son el API de:

• Paypal: https://api.paypal.com/
• Uber: https://api.uber.com
• Facebook: https://graph.facebook.com
• Twitter: https://api.twitter.com

Puedes ver el resultado final de nuestra API de la aplicación Mini Twitter en


https://minitwitterapi.reactiveprogramming.io, para que veas el resultado final.

Por los motivos que hemos explicado, en nuestro proyecto Mini Twitter, hemos
decidido crear un subdominio para nuestra API, de tal forma que la URL quedaría
de la siguiente manera http://api.<domain>. Ahora bien, Dado que estamos
desarrollando de forma local, <domain> se remplaza por localhost, de tal forma
que el API lo configuraremos para trabar en http://api.localhost:8080.

Página | 484
Implementando un Virtual Host en NodeJS

Un Virtual Host es la capacidad de un servidor web de tener un alojamiento


compartido, es decir que, por medio del mismo servidor, es posible atender dos
o más aplicaciones diferentes.

Mediante un Virtual Host es posible distinguir entre las solicitudes que entran al
dominio principal (localhost) y de los que entra a un subdominio (api.localhost)
y en NodeJS es extremadamente fácil realizar esta configuración, pero antes de
eso, vamos a necesitar instalar el módulo vhost mediante el siguiente comando:

npm install --save-dev vhost@3.0.2

Y vamos a instalar un motor de plantillas de NodeJS llamado Pug, que nos


ayudará a crear páginas web más fácil, del cual hablaremos más adelante,
cuando empezásemos a documentar el API.

npm install --save-dev pug@2.0.4

Una vez que tenemos las dependencias, vamos a necesitar crear el archivo api.js
en el path /api, en el cual vamos procesar todas las solicitudes que lleguen al
API. El archivo se deberá ver de la siguiente manera:

1. var express = require('express')


2. var router = express.Router()
3.
4. const pug = require('pug')
5.
6. router.get('/', function(req, res) {
7. res.send(pug.renderFile(__dirname + '/../public/apidoc/api-index.pug'))
8. })
9.
10. router.get('/*', function(req, res, err){
11. res.status(400).send({message: "Servicio inválido"})
12. })
13.
14. module.exports = router;

En este archivo estamos utilizando un Middleware direccionador (línea 2), luego,


en las líneas 6 y 10, creamos dos router, una para escuchar en la raíz del
subdominio (“/”) y otro para cualquier otra dirección (“/*”), para finalmente,
exportar el Router (línea 14). Es importante exportar el Router, porque más
adelante vamos a necesitar referenciarlo.

El primero router (línea 6) escuchará únicamente en la raíz del subdominio, es


decir http://api.localhost:8080 y tiene la finalidad de mostrar los términos de uso
del API, así como un link a la documentación. Para lograr eso, nos estamos
apoyando del motor de plantillas Pug, el cual permite desarrollar HTML de una
forma mucho más simple. Lo que hacemos es procesar el archivo api-index.pug
para convertirlo en HTML. Crearemos este archivo un poco más adelante.

Por otra parte, tenemos un router que escucha en cualquier URL del subdominio.
Este router es importante, porque se ejecutará cuando ninguna regla de route
se cumpla y de esta forma, es posible enviarle un mensaje de error al usuario.

485 | Página
El siguiente paso será crear el archivo api-index.pug en el path /public/apidoc
el cual se verá de la siguiente manera:

1. html
2. head
3. link(href='https://fonts.googleapis.com/css?family=Roboto', rel='stylesheet')
4. link(
5. rel='stylesheet'
6. href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css'
7. integrity='sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u'
8. crossorigin='anonymous'
9. )
10. link(rel='stylesheet', href='/public/apidoc/api-styles.css')
11. body
12. .api-alert
13. p.title Mini Twitter API REST
14. p.body
15. | Esta API es provista como parte de la aplicación Mini Twitter,
16. | la cual es parte del libro
17. a(href='#')
18. strong "Desarrollo de aplicaciones reactivas con React, NodeJS y MongoDB"
19. | , por lo que su uso es únicamente con fines educativos y por
20. | ningún motivo deberá ser empleada para aplicaciones productivas.
21. .footer
22. button.btn.btn-warning(data-toggle='modal', data-target='#myModal')
23. | Terminos de uso
24. a.btn.btn-primary(href='/catalog') Ver documentación
25.
26. #myModal.modal.fade(
27. tabindex='-1', role='dialog', aria-labelledby='myModalLabel',
28. aria-hidden='true')
29. .modal-dialog
30. .modal-content
31. .modal-header
32. button.close(type='button', data-dismiss='modal',
33. aria-hidden='true') ×
34. h4#myModalLabel.modal-title Terminos de uso
35. .modal-body
36. p El API de Mini Twitter es provista por Oscar Blancarte, autor del libro
37. strong "Desarrollo de aplicaciones reactivas con React, NodeJs y MongoDB"
38. | con fines exclusivamente educativos.
39. p Esta API es provista
40. strong "tal cual"
41. | esta, y el autor se deslizanda de cualquier problema o falla
42. | resultante de su uso. En ningún momento el autor será responsable
43. | por ningún daño directo o indirecto por la pérdida o publicación
44. | de información sensible.
45. strong El usuario es el único responsable por el uso y la información
46. | que este pública.
47. .modal-footer
48. button.btn.btn-primary(type='button', data-dismiss='modal') Cerrar
49.
50.
51. script(src='https://code.jquery.com/jquery.js')
52. script(src='//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js')

No te preocupes por entender su contenido, más adelanta regresaremos para


explicar cómo funciona Pug, por lo que solo limitémonos a copiarlo tal como está.

Página | 486
El siguiente paso será crear el archivo api-styles.css en el path /public/apidoc,
el cual se verá de la siguiente forma:

1. body {
2. background-color: #fafafa;
3. background: #1E5372;
4. background: -webkit-linear-gradient(left, #1C3744 , #1E5372); /
5. background: -o-linear-gradient(right, #1C3744, #1E5372);
6. background: -moz-linear-gradient(right, #1C3744, #1E5372);
7. background: linear-gradient(to right, #1C3744 , #1E5372);
8. }
9.
10. *{
11. font-family: 'Roboto', sans-serif;
12. color: #333;
13. }
14.
15. .hljs {
16. display: block;
17. overflow-x: auto;
18. padding: 0.5em;
19. color: #abb2bf;
20. background: #282c34;
21. }
22.
23. .hljs-comment,
24. .hljs-quote {
25. color: #5c6370;
26. font-style: italic;
27. }
28.
29. .hljs-doctag,
30. .hljs-keyword,
31. .hljs-formula {
32. color: #c678dd;
33. }
34.
35. .hljs-section,
36. .hljs-name,
37. .hljs-selector-tag,
38. .hljs-deletion,
39. .hljs-subst {
40. color: #e06c75;
41. }
42.
43. .hljs-literal {
44. color: #56b6c2;
45. }
46.
47. .hljs-string,
48. .hljs-regexp,
49. .hljs-addition,
50. .hljs-attribute,
51. .hljs-meta-string {
52. color: #98c379;
53. }
54.
55. .hljs-built_in,
56. .hljs-class .hljs-title {
57. color: #e6c07b;
58. }
59.
60. .hljs-attr,
61. .hljs-variable,
62. .hljs-template-variable,

487 | Página
63. .hljs-type,
64. .hljs-selector-class,
65. .hljs-selector-attr,
66. .hljs-selector-pseudo,
67. .hljs-number {
68. color: #d19a66;
69. }
70.
71. .hljs-symbol,
72. .hljs-bullet,
73. .hljs-link,
74. .hljs-meta,
75. .hljs-selector-id,
76. .hljs-title {
77. color: #61aeee;
78. }
79.
80. .hljs-emphasis {
81. font-style: italic;
82. }
83.
84. .hljs-strong {
85. font-weight: bold;
86. }
87.
88. .hljs-link {
89. text-decoration: underline;
90. }
91.
92. .badge{
93. background-color: #E74C3C;
94. }
95.
96. api-alert p{
97. margin: 0px;
98. }
99.
100. .api-alert{
101. margin: auto;
102. width: 40%;
103. margin-top: 50px;
104. background-color: #fafafa;
105. padding: 20px;
106. border-top: 5px solid #FEB506;
107. border-radius: 5px;
108. }
109.
110. .method-templete{
111. margin: auto;
112. width: 80%;
113. margin-top: 50px;
114. margin-bottom: 50px;
115. background-color: #fafafa;
116. padding: 20px;
117. border-top: 5px solid #FEB506;
118. border-radius: 5px;
119. }
120.
121. .secure-icon{
122. font-size: 50px;
123. float: right;
124. }
125.
126. .api-alert .title{
127. font-size: 28px;
128. text-align: center;

Página | 488
129. margin-bottom: 20px;
130. }
131.
132. .api-alert .body{
133. font-size: 20px;
134. text-align: justify;
135. }
136.
137. .api-alert .body strong{
138. font-style: italic;
139. color: #3498DB;
140. }
141.
142. .api-alert .footer{
143. margin-top: 20px;
144. text-align: center;
145. }
146.
147. .api-alert .footer a{
148. margin-left: 20px;
149. }
150.
151. .label{
152. margin-left: 20px;
153. font-size: 12px;
154. }

Este archivo de estilos es solo para el API, y no lo compartiremos con el proyecto


Mini Twitter.

Una vez hecho esto, tendremos que regresar al archivo server.js y agregar solo
las líneas marcadas:

1. var express = require('express');


2. var app = express();
3. var bodyParser = require("body-parser")
4. var path = require('path')
5. var webpack = require('webpack')
6. var config = require('./webpack.config')
7. var compiler = webpack(config)
8. var mongoose = require('mongoose')
9. var configuration = require('./config')
10. var vhost = require('vhost')
11. var api = require('./api/api')
12.
13. var opts = {/**props**/}
14.
15. let connectString = configuration.mongodb.development.connectionString
16. mongoose.connect(connectString, opts, function(err){
17. if (err) throw err;
18. console.log("==> Conexión establecida con MongoDB");
19. })
20.
21. app.use('/public', express.static(__dirname + '/public'))
22. app.use(bodyParser.urlencoded({extended: false}))
23. app.use(bodyParser.json({limit:'10mb'}))
24.
25. app.use(require('webpack-dev-middleware')(compiler, {
26. noInfo: true,
27. publicPath: config.output.publicPath

489 | Página
28. }))
29.
30. app.use(vhost('api.*', api));
31.
32. app.get('/*', function (req, res) {
33. res.sendFile(path.join(__dirname, 'index.html'))
34. });
35.
36. app.listen(8080, function () {
37. console.log('Example app listening on port 8080!')
38. });

Lo primero que haremos será importar el módulo vhost (línea 10) y el archivo
api.js (línea 11) que acabos de crear. El segundo paso será crear el Virtual Host
mediante la línea 30. Lo que estamos haciendo en esta línea es crear un
Middleware con vhost, el cual tomará todas peticiones que lleguen al path api.*
y las envíe al archivo api.js, más precisamente, al Router definido dentro.

Ahora bien, si corremos la aplicación e intentamos entrar a http://api.localhost,


el navegador nos arrojará un error de página no encontrada:

Para solucionar este problema, será necesario hacer un pequeño ajuste a nivel
de configuración de sistema operativo, con la intención de que identifique el
subdominio api como parte del localhost, para esto, tendremos que hacer un
procedimiento diferente según nuestro sistema operativo:

En Windows:

No dirigimos a la carpeta C:\Windows\System32\drivers\etc y abrimos el archivo


hosts como administrador, y agregamos la siguiente línea:

1. 127.0.0.1 api.localhost

En Linux

Página | 490
En el caso de Linux, el procedimiento es exactamente el mismo, solamente que
el archivo que tendremos que editar es /etc/hosts/. De tal forma que
agregaremos solamente la siguiente línea:

1. 127.0.0.1 api.localhost

En Mac

En Mac, tendremos que hacer exactamente lo mismo que en los anteriores, sin
embargo, el archivo se encuentra en /private/etc/hosts, allí agregaremos la
línea:

1. 127.0.0.1 api.localhost

NOTA: Esto hará que cualquier llamada al subdominio api.* se redirija al


localhost.

Ya con la configuración del sistema operativo, hemos terminado de crear nuestro


Virtual Host, y no solo eso, sino que, además, se ve muy bien. Para probarlo,
entra a la URL http://api.localhost:8080 en tu navegador para que veas los
resultados:

Fig. 148 - Virtual Host con NodeJS.

Configurar subdominio

Recuerda configurar tu sistema operativo para que


redireccione las peticiones del subdominio
api.localhost a la ip 127.0.0.1, de lo contrario no
podrá ser accedido.

491 | Página
Cross-origin resource sharing (CORS)

En este punto, podrías pensar que nuestra API ya está funcionando


correctamente y que ya la podemos integrar con nuestra aplicación, sin embargo,
esto es una verdad a medias, pues al momento de ser consumida desde nuestro
proyecto Mini Twitter, el navegador nos lanzará error. Esto debido a que por
seguridad, el navegador bloquea cualquier petición AJAX a un recurso fuera del
dominio en el que se encuentra la página.

Ante el navegador, un subdominio, es un dominio diferente, por lo que lanzará


un error como el siguiente:

Fig. 149 - CORS error.

Para solucionar este problema, es necesario agregar un header a nuestra


respuesta del servidor, permitiendo que el navegador aprueba la ejecución de
recursos externos, este header es el Access-Control-Allow-Origin. Por suerte,
hay un módulo que nos puede ayudar con eso, de tal forma que agregue por
nosotros el header en todas las respuestas. El módulo es cors y lo podemos
instalar mediante el comando:

npm install --save-dev cors@2.8.5.

1. let connectString = configuration.mongodb.development.connectionString


2. mongoose.connect(connectString, opts, function(err){
3. if (err) throw err;
4. console.log("==> Conexión establecida con MongoDB");
5. })
6.
7. app.use('*', require('cors')());
8.
9. app.use('/public', express.static(__dirname + '/public'))

En la línea 7 agregamos el Middleware Cors que se ejecuta antes de cualquier


path (*), el cual agregará el header “Access-Control-Allow-Origin: *”.

Con este simple paso, el API ya podrá ser invocado desde la aplicación.

Página | 492
Desarrollo del API REST

Estamos a punto de empezar a desarrollar los servicios de nuestra API REST,


pero antes de eso, quisiera abordar dos temas importantes de los cuales no
hemos hablado, el primero son las mejores prácticas para la creación de URL y
los códigos de respuesta REST.

Mejores prácticas para crear URI’s

Diseñar un API REST no se trata solo de crear servicios que cumplan su función,
sino que además, las URL de los servicios deben de darla al usuario una idea
bastante clara de lo que hace un servicio. Un servicio con una URL bien diseñada,
puede decirle al usuario que hace sin necesidad de leer la documentación.

Uso correcto de los métodos (Verbs)

Utilizar correctamente los métodos, es sin duda, una de las principales cosas que
debemos de respetar, pues una mala implementación puede llegar a ser
sumamente confuso. Recordemos que los métodos más utilizados son:

• GET: Solo lectura, y se utiliza para consultar información


• POST: Se utiliza para creación de un nuevo registro.
• PUT: Remplazar actualizar/remplazar un registro
• DELETE: Se utiliza para eliminar un registro.

Una URL por sí sola, no nos dice que operación va a realizar sobre un registro,
por ejemplo, veamos la siguiente URL:

/user/juan

Si vemos la URL, podemos comprender rápidamente, que es un servicio de


usuarios, y que va hacer algo con el usuario “juan”, sin embargo, no está claro
que hace hasta que tenemos el método. Si lo ejecutamos con el método GET,
entonces podemos asumir que el servicio está consultando al usuario juan, pero
si ejecutamos esa misma URL con el método DELETE, entonces podemos
entender que vamos a eliminar al usuario juan.

En este sentido, si empezamos a revolver los métodos para realizar operaciones


para las que no fueron diseñadas, volveremos loco al usuario.

493 | Página
URL simples, compactas y concretas

Al momento de definir las URL, debemos evitar utilizar cosas como


/findUserByName/juan o /deleteUser/juan. En el primer ejemplo, estamos
complicando las cosas, pues una URL como /users/juan en el método GET, dice
exactamente lo mismo. En el segundo ejemplo, poner la frase delete está de
más, pues con el método DELETE es suficiente para dar a entender.

Utilizar lo más posible las URL params

Uno de los grandes errores al definir las URL de nuestros servicios, es no definir
URL params, y en su lugar, esperar que los parámetros requeridos vengan como
parte del payload. En la práctica, recibirlo en el payload o como URL param, dará
el mismo resultado, sin embargo, para el usuario siempre será más claro definir
los parámetros como parte de la URL.

Camel Case

Existen ocasiones donde una sola palabra no es suficiente para representar el


objetivo de un servicio, en estos casos es posible unir palabras utilizando la
notación Camel Case, iniciando en minúscula y agregando Mayúsculas al inicio
de la siguiente palabra.

Por ejemplo, imagina que tienes un servicio de búsqueda de órdenes de ventas


y compras, un servicio con el path /ordes/:id sería un poco confuso, pues no
sabrías que tipo de orden estas buscando, en este caso podrías agregar
/salesOrders y /purchaseOrders. Otra alternativa es usar guiones para separar
las palabras, por ejemplo, /sales-orders o /purchase-orders

Códigos de respuesta

Todos los servicios REST por el simple hecho de trabajar sobre HTTP, retornan
un código de respuesta, este código de respuesta es un indicativo para el cliente
sobre lo que paso durante la ejecución. Los códigos de respuesta más utilizados
son:

• 200: Respuesta de OK, es utilizada para responder una respuesta exitosa


• 400: Bad request, indica que los parámetros enviados son inválidos.
• 401: Unauthorized, es retornado cuando no tenemos privilegios para
consumir un recurso.
• 404: Not found, indica que el recurso solicitado no existe.
• 500: Internal Server Error, indica que se produjo un error inesperado en el
servidor.

Página | 494
En REST hay una infinidad de código de respuesta, por lo que mencionar todos
aquí, está de más, así que solo nos limitamos a los más importantes y que
corresponden a más del 95% de los casos. Si aun así te queda la curiosidad de
conocer todos los demás códigos, puedes darte una vuelta por el siguiente
enlace: códigos de respuesta.

Migrando a nuestra API REST

Debido a que ya vamos a empezar a trabajar con nuestra propia API REST, será
necesario redirigir las llamadas del proyecto Mini Twitter a nuestra nueva API.
Para lograr eso, tendremos que entrar al archivo config.js que se encuentra en
la raíz del proyecto y cambiar el host para apuntar a nuestra API.

1. module.exports = {
2. debugMode: false,
3. api: {
4. host: "http://api.localhost:8080"
5. },
6. tweets: {
7. maxTweetSize: 140
8. }
9. }

En el momento en que realicemos este cambio, la aplicación Mini Twitter dejará


de funcionar por completo, pues no estará disponible ninguno de los servicios
necesarios para funcionar. Por ese motivo, vamos a ir desarrollando los servicios
más importantes para el correcto funcionamiento y nos iremos adentrando a los
menos necesario al final.

Implementar los servicios REST

En esta sección pasaremos de lleno a implementar los servicios REST que le dan
vida a la aplicación Mini Twitter.

Antes de comenzar

Con la finalidad de aprovechar al máximo las características de JavaScript, hemos


decidido crear nuestros servicios utilizando al máximo las Promesas (Promises),

495 | Página
las cuales nos permite trabajar de una mejor forma con los procesos asíncronos,
por lo que si no sabes que son las promesas, te invito a que veas uno de mis
videos donde lo explico: https://www.youtube.com/watch?v=3-jCVorlCZs

Así como las promesas nos permite trabajar mejor con todos los procesos
asíncronos, también tenemos el problema de manejar las callback, lo que hace
sumamente difícil de dar mantenimiento al código, por lo que vamos a utilizar la
instrucción Async/Await para controlar los procesos asíncronos de forma
síncrona, por lo que si tampoco sabes qué es esto de Async/Await, te invito a
que veas el siguiente video donde lo explico:
https://www.youtube.com/watch?v=USuhP9F56UE

Servicio - usernameValidate

El primero servicio que vamos a desarrollar es, el de validación de nombre de


usuario, el cual nos permite validar si un nombre de usuario ya está siendo
utilizado o está disponible.

Antes de empezar con el desarrollo, analicemos la siguiente ficha, la cual describe


la forma en que deberemos desarrollar el servicio:

Nombre Validación de disponibilidad de nombre de usuario

Página | 496
URL /usernameValidate/:username

URL Username: nombre del usuario a validar


params

Método GET

Request N/A

Response
• OK: Valor boolean que indica si el usuario está
disponible o no
• Message: Leyenda para mostrar al usuario en
caso de estar o no disponible.

1. {
2. "ok": true,
3. "message": "Usuario disponible"
4. }

Una vez que hemos analizado la ficha, ya sabemos los detalles básicos para su
implementación, como la URL, el método y el formato del request/response.

Lo primero que haremos será crear la carpeta controllers, la cual deberá estar
dentro del path /api. En esta nueva carpeta vamos a crear el archivo
UserController.js. Dentro de este archivo vamos a crear todos los servicios que
involucran a los usuarios.

Nuevo concepto: Controller

Los controllers son clases diseñadas para encapsular


todas las operaciones que afectan al modelo de datos,
recibiendo las órdenes del usuario, procesando la
solicitud y regresando un resultado.

La intención de usar clases controladores, es separar la lógica de routeo que


definimos en el archivo api.js de la lógica de negocios. De esta forma, el api
tomará los parámetros de entrar y delegará la responsabilidad al controller.

El archivo UserController.js deberá tener el siguiente contenido:

1. var Profile = require('../models/Profile')


2.
3. // Servicio que valida la disponibilidad de un nombre de usuario
4. const usernameValidate = async (req, res, err) => {
5.
6. try {
7. // find if exist some profile with the same username
8. let profiles = await Profile.find()
9. .byUsername(req.params.username.toLowerCase())
10.
11. //If the profiles list is not empty, the username is taken by other user

497 | Página
12. if (profiles.length > 0) throw new Error("Usuario existente")
13.
14. // Confirm to the username is available
15. res.send({
16. ok: true,
17. message: "Usuario disponible"
18. })
19. } catch (err) {
20. res.send({
21. ok: false,
22. message: err.message || "Error al validar el nombre de usuario"
23. })
24. }
25. }
26.
27. module.exports = {
28. usernameValidate
29. }

Dado que vamos a trabajar con los modelos de Mongoose, tendremos que importar
el modelo Profile en la línea 1. La función usernameValidate (línea 4) será
ejecutada por express, es por ese motivo que debemos recibir los parámetros
req, res y err.

En la línea 8 realizamos una búsqueda por medio de la función helper que


definimos en el modelo Profile (byUsername), el cual recibe como parámetro el
nombre de usuario a buscar. El nombre de usuario lo recuperamos del URL param
(req.params.username) y ejecutamos la búsqueda.

En la línea 12 validamos si la consulta retorno resultado, lo que indicaría que el


nombre de usuario ya fue tomado por otro usuario, por lo que lanzamos un error
para regresarlo a la aplicación. En caso de estar vacía, simplemente retornamos
con éxito la consulta en la línea 15.

Finalmente, exportamos la función (línea 28) para que pueda ser referencia
desde el Route de Express.

El paso final, será agregar la regla de routeo /usernameValidate a nuestra API,


de tal forma que cuando llegue una solicitud, este la delegue a nuestro controller.
Para lograr esto, tendremos que editar nuestro archivo api.js para agregar las
líneas marcadas:

1. var express = require('express')


2. var router = express.Router()
3. var userController = require('../api/controllers/UserController')
4. const pug = require('pug')
5.
6. router.get('/', function(req, res) {
7. res.send(pug.renderFile(__dirname + '/../public/apidoc/api-index.pug'))
8. })
9.
10. //Public access services
11. router.get('/usernameValidate/:username', userController.usernameValidate)
12.
13.
14. router.get('/*', function(req, res, err){

Página | 498
15. res.status(400).send({message: "Servicio inválido"})
16. })
17.
18. module.exports = router;

El primer paso es importar nuestro controller (línea 3) y el segundo será agregar


el route a nuestro objeto Router (línea 11). Veamos que este route, escucha en
el path /usernameValidate/:username y envía la solicitud al método de la función
de nuestro controller.

Para validar el funcionamiento del servicio, es necesario dirigirnos directamente


a http://localhost:8080/signup, y capturar un nombre de usuario:

Fig. 150 - Probando el servicio usernameValidate.

499 | Página
Servicio - Signup

El siguiente servicio que vamos a desarrollar, es el de creación de cuentas


(Signup). El cual nos va permitir crear nuevos usuarios para la aplicación.

Antes de empezar con el desarrollo analicemos su ficha:

Nombre Alta de usuarios

URL /Signup

Método POST

Request
• name: nombre de la persona que crea la
cuenta.
• username: nombre de usuario.
• password: contraseña de acceso.
Ejemplo:

1. {
2. "name": "Juan Perez ",
3. "username": "juan",
4. "password": "1234"
5. }

Response
• Ok: booleana que indica si la operación fue
exitosa o no.
• Profile: contiene los datos del usuario creado,
entre los que destacan
o _id: ID único del usuario
o date: fecha de creación

1. {
2. "ok": true,
3. "body": {
4. "profile": {
5. "__v": 0,
6. "name": "Juan Perez",
7. "userName": "juan",
8. "password": "",
9. "_id": "5a1f40142aab1e2318d65e98",
10. "date": "2017-11-29T23:17:40.508Z",
11. "followersRef": [],
12. "followingRef": [],
13. "tweetCount": 0,
14. "banner": null,
15. "avatar": null,
16. "description": "Nuevo en Twitter"
17. }
18. }
19. }

Página | 500
Una vez analizada la ficha, iniciaremos agregando la función signup dentro del
archivo UserController.js el cual procesará las solicitudes para el alta de
usuarios, la función se verá de la siguiente manera:

1. // Servicio para crear un nuevo usuario


2. const signup = async (req, res, err) => {
3. try {
4. // Create a new profile object
5. let newProfile = new Profile({
6. name: req.body.name,
7. userName: req.body.username.toLowerCase(),
8. password: bcrypt.hashSync(req.body.password, 10)
9. })
10.
11. // Create a new profile in the database
12. newProfile = await newProfile.save()
13.
14. // Return the new created profile
15. res.send({
16. ok: true,
17. body: {
18. profile: newProfile
19. }
20. })
21.
22. } catch (err) {
23. let errorMessage = null
24. if (err.errors != null && err.errors.userName != null) {
25. errorMessage = "Nombre de usuario existente"
26. } else {
27. errorMessage = 'Error al guardar el usuario'
28. }
29. res.send({
30. ok: false,
31. message: "Error al crear el usuario",
32. error: errorMessage
33. })
34. }
35. }

El primer paso será crear un objeto Profile mediante el schema de Mongoose


(línea 5) utilizado los parámetros enviados en el payload de la petición. Para crear
el profile es necesario el nombre (name), nombre de usuario (username) y el
password, sin embargo, no podemos guardar el password tal cual llega a nuestra
API, pues eso es un issue de seguridad muy grave, que podría ser aprovechado
por cualquier usuario con acceso a la base de datos, pues podría conocer los
password de los usuarios. Por tal motivo, es necesario encriptarlos mediante la
librería bcrypt, la cual instalaremos de la siguiente manera:

npm install --save-dev bcryptjs@2.4.3

Y tendríamos que agregar el import correspondiente al inicio del archivo:

1. var bcrypt = require('bcryptjs')

501 | Página
Mediante la función hashSync (línea 8) estamos creando un hash del password,
el cual luego puede ser comparado sin necesidad de conocer el password original
que creo dicho hash.

Una vez creado el objeto Profile, solo resta guardarlo en la base de datos
mediante la función save (línea 12) que proporciona el Schema. Si todo sale bien,
el perfil retornado por Mongoose contiene el ID generado. Por otra parte, si hay
algún error, el flujo se va a catch y el error es retornado a la aplicación.

Cabe mencionar que en la línea 24 validamos si hay un error de userName


duplicado, esto con ayuda del plugin mongoose-unique-validator. Los errores los
encontramos en err.errors.{field}, donde field es el campo que tiene algún
error.

El último paso será exportar la función y agregar el Router al archivo api.js, el


cual agregaremos debajo del router de usernameValidate:

1. //Public access services


2. router.get('/usernameValidate/:username', userController.usernameValidate)
3. router.post('/signup', userController.signup)

Para validar que todo funciona correctamente, podemos intentar crear un nuevo
usuario desde la página de signup y comprobar el registro está en MongoDB.

Autenticación con JSON Web Token (JWT)

Una de las grandes características de un API es que, deben de ser seguras, por
eso, implementar un sistema de autenticación es indiscutible. La importancia de
implementar seguridad del lado del servidor es impedir que los usuarios no
autenticados puedan acceder a recursos restringidos.

En la actualidad existen muchas formas de implementar seguridad en un API,


como es el clásico user/password, OAuth, certificados, autorización por rangos
de IP, HTTP basic authentication, tokens, etc.

De todas las opciones que existen, el uso de token es una de las más populares,
pues permite la autenticación sin necesidad de tener que enviar nuestras
credenciales cada vez que necesitamos consumir el API. Por otra parte, el Token
permite guardar datos adicionales, los cuales pueden ser descifrado y
aprovechados del lado del API, como es el caso de la fecha de vigencia, la cual
permite invalidar un Token que tiene cierto tiempo de haber sido creado.

En el caso del proyecto Mini Twitter, utilizaremos la autenticación por medio de


Tokens, pero nos apoyaremos en el cásico user/password para la generación del
token. De esta forma, el usuario tendrá que autenticarse enviando su usuario y
password para que el API le regrese un Token. Una vez con el token, el cliente
solo tendrá que enviar el token generado por el API en todas las invocaciones
siguientes. De esta forma, el API sabrá cuál es el usuario que intenta acceder.

Página | 502
En el artículo Autenticación con JSON Web Token explico con mucho más detalle
acerca de este tema, por si quiere profundizar en el aprendizaje de esta fantástica
herramienta.

Antes de iniciar con la implementación de JWT tenemos que entender que existen
servicios que no requieren autenticación y otros que sí, por eso motivo, tenemos
que tener una lógica para identificar cuáles serán protegidos y cuáles no. Para
facilitar las cosas, nos vamos a apoyar en la URL para realizar esa diferenciación,
de tal forma que todos los servicios que inicien en /secure/* será sujetos a
autenticación, mientras que el resto no.

A pesar de que solo las URL /secure/* son protegidas, siempre es bueno solicitar
el token (si lo tiene) para saber quién nos está invocando, por ese motivo,
empezaremos desarrollando un Middleware que recupere el header
“authorization” que corresponde al token, lo descifre y lo agregue a nuestro
objeto request, con la intención que esté disponible para el resto de los servicios.

Para ello agregaremos el siguiente Middleware al inicio del archivo api.js:

1. var configuration = require('../serverConfig')


2. var jwt = require('jsonwebtoken')
3.
4. router.use('/',function(req, res, next) {
5. var token = req.headers['authorization']
6. if(!token){
7. next()
8. req.user = null
9. return
10. }
11.
12. token = token.replace('Bearer ', '')
13. jwt.verify(token, configuration.jwt.secret, function(err, user) {
14. if (err) {
15. req.user = null
16. next()
17. } else {
18. req.user = user
19. next()
20. }
21. })
22. })

El middleware escucha en cualquier path, y recupera el token del header (línea


5), si el token no está presenta, simplemente invoca next y finaliza con un return.

Si el token está presente, se procese con la validación, para ello, es necesario


retirar la palabra “Bearer “ (línea 12), la cual viene incluida por default, pero no
es necesaria para la validación. El siguiente paso es validar el token, mediante el
método verify que nos proporciona JWT, el cual requiere de una contraseña que
obtenemos del archivo de configuración (configuration.jwt.secret).

Si el proceso de validación termina correctamente, un objeto JSON es creado con


los datos asociados al token y es agregado al objeto request en la propiedad user

503 | Página
(línea 18). Si el proceso de autenticación del token falla, entonces, simplemente
igualamos la propiedad user a null.

En este punto, será necesario instalar el módulo jsonwebtoken mediante el


siguiente comando:

npm install --save-dev jsonwebtoken@8.5.1

Adicional a eso, tendremos que agregar una contraseña en el archivo


serverConfig.js, dicha contraseña será utilizada para cifrar y descifrar los
tokens, por lo que es importante mantener en secreto esta clave. La contraseña
puede ser cualquier valor:

1. module.exports = {
2. server: {
3. port: 8080
4. },
5. mongodb: {
6. connectionString: "<CONNECTION_STRING>"
7. },
8. jwt: {
9. secret: "#$%EGt3eT##$EG%Y$Y&U&/IRTRH45W$%whth$Y$%YAFG"
10. }
11. }

El siguiente paso será denegar el acceso a los servicios restringidos, por lo cual,
agregaremos el siguiente middleware al archivo api.js, justo debajo del
middleware que acabamos de agregar:

1. router.use('/secure',function(req, res, next) {


2. if(req.user === null){
3. res.status(401).send({
4. ok: false,
5. message: 'Token inválido'
6. })
7. return
8. }
9. next()
10. })

Este nuevo middleware solo escucha las llamadas en el path /secure, lo que nos
permite realizar acciones solo para los servicios protegidos. Este middleware
valida si la propiedad req.user es null, si es null significa que no se presentó un
token o el que se presento es inválido. En tal caso, un código 401 es retornado
junto con la leyenda “Token inválido”. Por otra parte, si el token es correcto,
entonces, simplemente permitimos que continúe la ejecución con la llamada a
next.

En este momento, solo nos falta agregar la lógica para crear los Tokens, pero
esto lo veremos en el servicio de login.

Página | 504
Servicio - Login

El servicio login nos permite autenticarnos ante el API, para lo cual es necesario
mandar las credenciales user/password, y como respuesta, nos retornará el
Token generado.

Veamos la ficha del servicio:

Nombre Autenticación

URL /login

Método POST

Request
• name: nombre de la persona que crea la
cuenta.
• username: nombre de usuario.
Ejemplo:

6. {
7. "username": "juan",
8. "password": "1234"
9. }

Response
• Ok: valor booleano que indica si la operación
fue exitosa o no.
• Profile: contiene los datos del usuario creado.
• Token: Token generado para autenticarse en el
API.

20. {
21. "ok": true,
22. "body": {
23. "profile": {
24. "__v": 0,
25. "name": "Juan Perez",
26. "userName": "juan",
27. "password": "",
28. "_id": "5a1f40142aab1e2318d65e98",
29. "date": "2017-11-29T23:17:40.508Z",
30. "followersRef": [],
31. "followingRef": [],
32. "tweetCount": 0,
33. "banner": null,
34. "avatar": null,
35. "description": "Nuevo en Twitter"
36. }
37. },
38. "token": "<Token>"
39. }

Antes de desarrollar el servicio, vamos a crear una dependencia, la cual es una


función para crear los tokens, para ello, vamos a crear el archivo AuthService.js

505 | Página
que crearemos dentro de una nueva carpeta /api/services/, el archivo se verá
de la siguiente manera:

1. var jwt = require('jsonwebtoken')


2. var configuration = require('../../config')
3.
4. function generateToken(user) {
5. var u = {
6. username: user.username,
7. id: user.id
8. }
9. return token = jwt.sign(u, configuration.jwt.secret, {
10. expiresIn: 60 * 60 * 24 // expires in 24 hours
11. })
12. }
13.
14. module.exports = {
15. generateToken
16. }

La función generateToken se utiliza para generar un token basado en un objeto


JSON, este obtento tendrá que tener el ID y username del usuario, con esto se
crea un objeto que representará el token (línea 5).

En la línea 9 creamos el token mediante el método sign de JWT. Esta función


requiere el objeto JSON y la contraseña con la que se firmará. Adicionalmente,
agregamos una fecha de expiración de 24h (línea 10). Finalmente exportamos la
función (línea 15).

Ya con esta dependencia resuelta, vamos a agregar la función login al archivo


UserController.js:

1. // Servicio que autentica a un usuario a partir del usuario/password y genera un


token
2. const login = async (req, res, err) => {
3. try {
4. let profile = await Profile.findOne({
5. userName: req.body.username.toLowerCase() })
6. if (profile == null) throw new Error("Usuario y contraseña inválida")
7.
8.
9. let valid = await bcrypt.compare(req.body.password, profile.password)
10. if (!valid) throw new Error('Usuario y password inválidos')
11.
12. let user = {
13. username: req.body.username,
14. id: profile._id
15. }
16.
17. let token = authService.generateToken(user)
18.
19. res.send({
20. ok: true,
21. profile: {
22. id: profile.id,
23. name: profile.name,
24. userName: profile.userName,
25. avatar: profile.avatar || '/public/resources/avatars/0.png',

Página | 506
26. banner: profile.banner || '/public/resources/banners/4.png',
27. tweetCount: profile.tweetCount,
28. following: profile.following,
29. followers: profile.followers
30. },
31. token: token
32. })
33. } catch (error) {
34. res.send({
35. ok: false,
36. message: error.message || "Error al validar el usuario"
37. })
38. }
39. }

Lo primero que hacemos es buscar al usuario en la base de datos mediante el


username (línea 4), si el usuario no se encuentra, retornamos el error (línea 6).
Si el usuario se encuentra, el siguiente paso será comparar el password enviado,
con el password almacenado en MongoDB. Dado que el password está
encriptado, es necesario realizar la comparación mediante el módulo bcrypt
(línea 9).

Si el password concuerda, entonces estamos ante el usuario legítimo de la


cuenta, por lo que procederemos a crearle un token mediante la función
generateToken que acabamos de crear (línea 17). Finalmente retornamos el perfil
del usuario junto con el token generado (línea 19).

Cuando un usuario es creado, no tiene una foto de avatar, ni un banner, por lo


que regresamos uno por default. Para lograr esto, hemos guardado dos imágenes
dentro de la carpeta public, con la finalidad de retornarlas por default. Las
imágenes son las siguiente:

Imagen por default para el avatar, la cual deberemos guardar en el path


/public/resources/avatars/ con el nombre 0.png

Imagen por default para el banner, la cual deberá estar en la carpeta


/public/resources/banners/ con el nombre 4.png

507 | Página
Para comprobar que las hemos creado correctamente, deberemos poder ver las
imágenes en las siguientes URL, de lo contrario algo hemos hecho mal:

• http://localhost:8080/public/resources/avatars/0.png
• http://localhost:8080/public/resources/banners/4.png

No olvidemos importar la dependencia a AuthService.js y exportar el nuevo


servicio.

El último paso es agregar el router al archivo api.js, el cual se verá de la


siguiente manera:

1. //Public access services


2. router.get('/usernameValidate/:username', userController.usernameValidate)
3. router.post('/signup', userController.signup)
4. router.post('/login', userController.login)

En este momento ya estará listo nuestro servicio, pero no lo podremos ver


reflejado en la aplicación hasta crear el siguiente servicio (relogin), pues la
aplicación al momento de autenticarnos nos manda directo a la raíz de la
aplicación, donde el servicio relogin es ejecutado y al fallar porque no existe,
nos borra el token y nos manda de nuevo a login.

Página | 508
Servicio - Relogin

El servicio relogin es muy parecido al servicio de login, pues también sirve para
autenticar al usuario, sin embargo, tiene una pequeña diferencia, y es que este
servicio, se utiliza para autenticar a los usuarios que ya tiene un token.

Mediante este servicio, es posible autenticar de forma automática a un cliente


que tiene un token válido, sin tener que capturar nuevamente su usuario y
contraseña. Mientras el token sea vigente, el usuario ya no requerirá introducir
su usuario y contraseña.

Veamos la ficha de este servicio:

Nombre Actualización de credenciales (relogin)

URL /secure/relogin

Método GET

Headers authorization: token del usuario

Request N/A

Response

• Ok: valor booleano que indica si la operación


fue exitosa o no.
• Profile: contiene los datos del usuario creado,
entre los que destacan
• Token: Token generado para autenticarse en el
API.

40. {
41. "ok": true,
42. "body": {
43. "profile": {
44. "__v": 0,
45. "name": "Juan Perez",
46. "userName": "juan",
47. "password": "",
48. "_id": "5a1f40142aab1e2318d65e98",
49. "date": "2017-11-29T23:17:40.508Z",
50. "followersRef": [],
51. "followingRef": [],
52. "tweetCount": 0,
53. "banner": null,
54. "avatar": null,
55. "description": "Nuevo en Twitter"
56. }
57. },
58. "token": "<Token>"
59. }

509 | Página
Una de las cosas que llama la atención de este servicio, es que se ejecuta por el
método GET y que no tiene request, esto se debe a que solo requiere del token
en el header “authorization” para validar al usuario. Como respuesta regresa lo
mismo del servicio login pero nos retorna un token actualizado con nueva fecha
de vencimiento.

Lo primero será crear la función relogin en el archivo UserController.js, el cual


se verá de la siguiente manera:

1. // Servicio que autentica a un usuario a partir del token


2. const relogin = async (req, res, err) => {
3. try {
4. let userToken = {
5. id: req.user.id,
6. username: req.user.username
7. }
8. let newToken = authService.generateToken(userToken)
9.
10. let profile = await Profile.findOne({ _id: req.user.id })
11. if (profile === null) throw new Error("No existe el usuario")
12.
13. res.send({
14. ok: true,
15. profile: {
16. id: profile._id,
17. name: profile.name,
18. userName: profile.userName,
19. avatar: profile.avatar || '/public/resources/avatars/0.png',
20. banner: profile.banner || '/public/resources/banners/4.png',
21. tweetCount: profile.tweetCount,
22. following: profile.following,
23. followers: profile.followers
24. },
25. token: newToken
26. })
27. } catch (error) {
28. res.send({
29. ok: false,
30. message: error.message || "Error al validar el usuario"
31. })
32. }
33. }

Dado que este servicio está expuesto en /secure/relogin, quiere decir que antes
de llegar a este servicio, deberá pasar por los middlewares de autenticación que
definimos en api.js, lo que nos garantiza que, si llega hasta aquí, es porque es
un usuario autenticado.

El siguiente paso es generarle un nuevo token con los datos del token viejo
(líneas 8). Seguido, buscamos al usuario por medio del ID (línea 10) y
retornamos los datos del perfil junto con el nuevo token.

Restaría exportar la función al final del archivo para poder accederlo de forma
externa.

Página | 510
Finalmente, solo nos falta agregar el router en el archivo api.js, el cual quedaría
de la siguiente manera:

1. //Private access services (security)


2. router.get('/secure/relogin',userController.relogin)
3.
4. //Public access services
5. router.get('/usernameValidate/:username', userController.usernameValidate)
6. router.post('/signup', userController.signup)
7. router.post('/login', userController.login)

Ya con este servicio terminado, ya es posible probar la autenticación desde la


pantalla de login, sin embargo, una vez autenticado, no podremos ver más que
nuestros datos del perfil, pues todavía falta implementar el servicio para
consultar los tweets y el los usuarios sugeridos.

Fig. 151 - Probando los servicios login y relogin.

511 | Página
Servicio - Consultar los últimos Tweets

Este servicio es el que nos permite recuperar los últimos tweets publicados por
todos los usuarios. Normalmente una red social utiliza IA para determinar los
tweets que deberás ver en tu home, sin embargo, nosotros no tenemos esas
capacidades, por lo que optamos por los últimos tweets.

Veamos la ficha del servicio:

Nombre Consultar los últimos Tweets

URL /tweets

Método GET

Headers N/A

Request N/A

Response

• Ok: valor booleano que indica si la operación


fue exitosa o no.
• Body: contiene los datos del tweet, como lo
son:
o Id: ID único del tweet
o _creator: Perfil del usuario que creo el
Tweet.
o Date: fecha de creación
o Message: Texto capturado en el tweet.
o likeCounter: número de likes
o replys: número de likes
o image: Imagen asociada al Tweet (si
existe).

1. {
2. "ok": true,
3. "body": [
4. {
5. "_id": "5a0657ad3ccd98529d83a9b9",
6. "_creator": {
7. "_id": "5a05286db5371dffe40bafae",
8. "name": "Juan",
9. "userName": "Juan",
10. "avatar": "<Base64 img>"
11. },
12. "date": "2017-11-11T01:51:41.421Z",
13. "message": "test",
14. "likeCounter": 0,
15. "replys": 0,
16. "image": null

Página | 512
17. },
18. ...
19. ]
20. }

Como podemos observar, este servicio regresa un array dentro del body, donde
cada posición corresponde a un Tweet.

Todos los servicios relacionados con Tweets, los vamos a crear en otro controller,
por lo cual, crearemos el archivo TweetController.js en el path
/api/controllers, el cual se verá de la siguiente manera:

1. var Profile = require('../models/Profile')


2. var Tweet = require('../models/Tweet')
3. var mongoose = require('mongoose')
4.
5. // Servicio para consultar los 10 últimos tweets
6. // para mostrar en el home del usuario
7. const getNewTweets = async (req, res, err) => {
8. let user = req.user || {}
9. let page = req.query.page;
10. let perPage = 10
11.
12. try {
13. //Query the first 10 tweets
14. let tweets = await Tweet.find({ tweetParent: null })
15. .populate("_creator", { banner: 0 })
16. .sort({ '_id': -1 })
17. .limit(10)
18. .skip(perPage * page)
19.
20. // Transform MongoDB response
21. let response = tweets.map(x => {
22. return {
23. _id: x._id,
24. _creator: {
25. _id: x._creator._id,
26. name: x._creator.name,
27. userName: x._creator.userName,
28. avatar: x._creator.avatar || './public/resources/avatars/0.png'
29. },
30. date: x.date,
31. message: x.message,
32. liked: x.likeRef.find(
33. likeUser => likeUser.toString() === user.id || null),
34. likeCounter: x.likeCounter,
35. replys: x.replys,
36. image: x.image
37. }
38. })
39.
40. res.send({
41. ok: true,
42. body: response
43. })
44.
45. } catch (error) {
46. console.log(error)
47. res.send({
48. ok: false,

513 | Página
49. message: "Error al cargar los Tweets",
50. error: error
51. })
52. }
53. }
54.
55. module.exports = {
56. getNewTweets
57. }

La función getNewTweets es la que utilizaremos para recuperar los últimos tweets.


Esta función no requiere que estemos autenticados para recuperar los Tweets,
pero si utiliza al usuario autenticado para determinar si ya le disté like al Tweet,
por eso motivo, en la línea 8 recuperamos el usuario autenticado o definimos un
objeto en vacío en su lugar.

Lo siguiente es buscar todos los Tweet donde el campo tweetParent sea null
(línea 14), para asegurar de no recuperar Tweet que correspondan a respuestas.
También hace uso de la instrucción skyp y limit para paginar los resultados
(líneas 17 y 18), de esta forma, recuperamos Tweets en páginas de 10. Limit
determina el número de tweets que debemos de regresar, mientras que skyp
determina los registros que debemos saltarnos antes de llegar a los 10 que vamos
a seleccionar.

Lo siguiente es hacer un populate (join) con los Perfiles (línea 15), con la
intención de remplazar el ID del usuario por el objeto en sí, con todos sus datos,
además, le mandamos banner:0 para indicarle que NO queremos el banner. Lo
siguiente es ordenar (línea 16) los tweets de forma descendiente.

En la línea 21 iteramos todos los tweets para construir el array que retornaremos.
Solo cabe detenernos en la propiedad avatar y liked. En el campo avatar
retornamos la imagen en base64. Si la imagen no existirá, retornamos la imagen
de avatar por default (línea 28). Y finalmente, para el campo liked buscamos
dentro del array likeRef si algún ID corresponde con el usuario autenticado, si
esto es así, quiere decir que el usuario le dio like al tweet y retornamos true.
Finalmente, los tweets son retornados en la línea 40.

Para concluir este servicio, faltaría realizar dos acciones en el archivo api.js, la
primera es realizar el import a TweetController.js y agregar el routeo siguiente:

1. //Private access services (security)


2. router.get('/secure/relogin',userController.relogin)
3.
4. //Public access services
5. router.get('/tweets',tweetController.getNewTweets)
6. router.get('/usernameValidate/:username', userController.usernameValidate)
7. router.post('/signup', userController.signup)
8. router.post('/login', userController.login)

Con estos últimos cambios, ya podremos ver los tweets en nuestro proyecto:

Página | 514
Fig. 152 - probando el servicio de obtención de los últimos tweets.

515 | Página
Servicio - Consultar se usuarios sugeridos

El siguiente servicio tiene como propósito mostrar al usuario una lista de usuarios
sugeridos para comenzar a seguir. Sin embargo, las redes sociales actuales
utilizan algoritmos con Inteligencia Artificial para determinar que usuarios son
buenos prospectos para ser sugeridos, capacidad que desde luego no tenemos
en este mini proyecto, por lo que nos limitaremos a mostrar los últimos usuario
registros en el proyecto.

Veamos la ficha del servicio:

Nombre Consulta de usuario sugeridos

URL /secure/suggestedUsers

Método GET

Headers authorization: token del usuario

Request N/A

Response
• Ok: valor booleano que indica si la operación
fue exitosa o no.
• Body: contiene un array de perfiles de
usuarios.

60. {
61. "ok": true,
62. "body": [
63. {
64. "_id": "5a204cdca0738b612c9c9f5f",
65. "name": "marco",
66. "description": "Nuevo en Twitter",
67. "userName": "marco",
68. "avatar": "<base64 img>",
69. "banner": "<base64 img>",
70. "tweetCount": 0,
71. "following": 0,
72. "followers": 0
73. },
74. ...
75. ]
76. }
77.

Agregaremos la función getSuggestedUser en el archivo UserController.js, el


cual se verá de la siguiente forma:

1. // Servicio que consulta usuarios sugeridos para seguir


2. const getSuggestedUser = async (req, res, err) => {
3. let user = req.user
4.
5. try {
6. let users = await Profile.find({ userName: { $ne: user.username } })
7. .sort({ "date": -1 })

Página | 516
8. .limit(6)
9.
10. res.send({
11. ok: true,
12. body: users.map(x => {
13. return {
14. _id: x._id,
15. name: x.name,
16. description: x.description,
17. userName: x.userName,
18. avatar: x.avatar || '/public/resources/avatars/0.png',
19. banner: x.banner || '/public/resources/banners/4.png',
20. tweetCount: x.tweetCount,
21. following: x.following,
22. followers: x.followers
23. }
24. })
25. })
26. } catch (error) {
27. res.send({
28. ok: false,
29. message: error.message || "Error al validar el usuario"
30. })
31. }
32. }

Este servicio es bastante simple, pues solo realiza la búsqueda de Perfiles (línea
6), los ordena por fecha de creación en forma descendente (línea 7) y luego
recupera los 6 primeros registros (línea 8).

Los resultados son iterados en la línea 12 para crear un array con los perfiles
sugeridos para el usuario.

Para finalizar, solo tendremos que agregar router correspondiente en el archivo


api.js:

1. //Private access services (security)


2. router.get('/secure/relogin',userController.relogin)
3. router.get('/secure/suggestedUsers',userController.getSuggestedUser)
4.
5. //Public access services
6. router.get('/tweets',tweetController.getNewTweets)
7. router.get('/usernameValidate/:username', userController.usernameValidate)
8. router.post('/signup', userController.signup)
9. router.post('/login', userController.login)

Tras agregar estos cambios, ya podremos ver los perfiles sugeridos en la página
de inicio:

517 | Página
Fig. 153 - Probando los usuarios sugeridos.

Página | 518
Servicio – Consulta de perfiles de usuario

Este servicio es el que se utiliza para consultar el perfil de un usuario. En la


aplicación Mini Twitter es utilizada para mostrar el perfil de otro usuario o incluso
el nuestro.

Veamos la ficha del servicio:

Nombre Consulta de perfiles de usuario

URL /profile/:username

URL username: nombre de usuario del perfil a consultar.


params

Método GET

Headers authorization: token del usuario

Request N/A

Response
• Ok: valor booleano que indica si la operación
fue exitosa o no.
• Body: contiene todos los datos del perfil
1. _id: identificador único del usuario.
2. Name: Nombre completo del usuario
3. Description: descripción acerca del
usuario.
4. userName: nombre de usuario.
5. Avatar: Foto de perfil
6. Banner: Imagen del banner
7. tweetCount: Conteo de tweet
publicados
8. Followings: número de personas que
sigue
9. Followers: número de seguidores.
10. Follow: indica si el consumir esta
siguiente al usuario.

78. {
79. "ok": true,
80. "body": {
81. "_id": "5a012b1486b5c864a4fe6223",
82. "name": "Oscar Blancarte",
83. "description": "Nuevo en Twitter",
84. "userName": "oscar",
85. "avatar": "<base64 img>",
86. "banner": "<base64 img>",
87. "tweetCount": 76,
88. "following": 1,

519 | Página
89. "followers": 2,
90. "follow": false
91. }
92. }
93.

Agregaremos la función getProfileByUsername en el archivo UserController.js


el cual se verá de la siguiente manera:

1. //Servicio que consulta el perfil de un usuario por medio de su nombre de usuario


2. const getProfileByUsername = async (req, res, err) => {
3. let user = req.params.user
4.
5. try {
6. if (user === null) throw new Error("parametro 'user' requerido")
7.
8. // Find profile by username
9. let profile = await Profile.findOne({ userName: user })
10.
11. //if the user dont exist, throw error
12. if (profile === null) throw new Error("Usuario no existe")
13.
14. // Retrieve token from the header request
15. var token = req.headers['authorization'] || ''
16. token = token.replace('Bearer ', '')
17.
18. //Validate token
19. let userToken = await jwt.verify(token, configuration.jwt.secret)
20.
21. // Validate if the user profile is follower
22. let follow = profile.followersRef
23. .find(x => x.toString() === userToken.id.toString()) != null
24.
25. // Return user profile
26. res.send({
27. ok: true,
28. body: {
29. _id: profile._id,
30. name: profile.name,
31. description: profile.description,
32. userName: profile.userName,
33. avatar: profile.avatar || '/public/resources/avatars/0.png',
34. banner: profile.banner || '/public/resources/banners/4.png',
35. tweetCount: profile.tweetCount,
36. following: profile.following,
37. followers: profile.followers,
38. follow: follow
39. }
40. })
41. } catch (error) {
42. res.send({
43. ok: false,
44. message: error.message || "parametro 'user' requerido"
45. })
46. }
47. }

Página | 520
Esta función requiere que se le envíe como URL param el nombre del usuario al
cual se va a consultar, de lo contrario, un error será retornado (línea 6).

El siguiente paso será consultar el perfil mediante el username (línea 9), si el


perfil es encontrado, se procederá con enviar como respuesta el perfil encontrado
(línea 29), pero antes de eso, es necesario determinar si el usuario que consumió
el API está siguiendo al usuario consultado, por lo que recuperamos el token
(línea 19) y luego lo desciframos para recuperar el ID del usuario, si el token
existe y es válido, se procede a determinar si el ID del usuario está en el array
followersRef (línea 22). Finalmente, respondemos con el perfil consultado y en
el campo follow (línea 38) envíanos el booleano que indica si seguimos o no a
este usuario.

Adicional a esto, tendremos que agregar el método getProfileByUsername a los


exports y agregar los siguientes imports al inicio del archivo:

1. var configuration = require('../../config')


2. var jwt = require('jsonwebtoken')

El último paso será agregar el router correspondiente al archivo api.js:

1. //Private access services (security)


2. router.get('/secure/relogin',userController.relogin)
3. router.get('/secure/suggestedUsers',userController.getSuggestedUser)
4.
5. //Public access services
6. router.get('/tweets',tweetController.getNewTweets)
7. router.get('/usernameValidate/:username', userController.usernameValidate)
8. router.get('/profile/:user',userController.getProfileByUsername)
9. router.post('/signup', userController.signup)
10. router.post('/login', userController.login)

Una vez que terminamos los cambios, es posible dirigirse a la sección del perfil
de nuestro usuario o el de cualquier otro:

521 | Página
Fig. 154 - Probando la consulta de perfil de usuario.

Página | 522
Servicio – Consulta de Tweets por usuario

El servicio de consulta de Tweets por usuario permite consultar únicamente los


tweets de un determinado usuario, el cual se utiliza en la sección del perfil de un
usuario.

Si nos dirigimos en este momento a la sección del perfil de usuario, verá que se
están mostrando los tweets de todos los usuarios, esto es posible debido a que
este servicio y el de los últimos tweets (/tweets) son compatibles en la URL. Pero
una vez que implementemos este, Express podrá determinar que este es el
correcto para el path /tweets/:username.

Veamos la ficha del servicio:

Nombre Consulta de Tweets por usuario

URL /tweets/:username

URL username: nombre de usuario del perfil a consultar.


params

Método GET

Headers authorization: token del usuario

Request N/A

Response
• Ok: valor booleano que indica si la operación
fue exitosa o no.
• Body: contiene los datos del tweet, como lo
son:
o Id: ID único del tweet
o _creator: Perfil del usuario que creo el
Tweet.
o Date: fecha de creación
o Message: Texto capturado en el tweet.
o likeCounter: número de likes
o replys: número de likes
o image: Imagen asociada al Tweet (si
existe).

21. {
22. "ok": true,
23. "body": [
24. {
25. "_id": "5a0657ad3ccd98529d83a9b9",
26. "_creator": {
27. "_id": "5a05286db5371dffe40bafae",
28. "name": "Juan",
29. "userName": "Juan",
30. "avatar": "<Base64 img>"

523 | Página
31. },
32. "date": "2017-11-11T01:51:41.421Z",
33. "message": "test",
34. "likeCounter": 0,
35. "replys": 0,
36. "image": null
37. },
38. ...
39. ]

94. }

Crearemos la función getUserTweets dentro del archivo TweetController.js, el


cual se verá de la siguiente manera:

1. //Consulta todos los tweets de un usuario determinado


2. const getUserTweets = async (req, res, err) => {
3. let username = req.params.user
4. let page = req.query.page;
5. let perPage = 10
6.
7. try {
8. // Query the user profile
9. let user = await Profile.findOne({ userName: username })
10. // if the profile dont exist, throw error
11. if (user == null) throw new Error("No existe el usuario")
12.
13. // Query all user tweets
14. let tweets = await Tweet.find({ _creator: user._id, tweetParent: null })
15. .populate("_creator")
16. .sort({ '_id': -1 })
17. .limit(10)
18. .skip(perPage * page)
19.
20. let response = tweets.map(x => {
21. return {
22. _id: x._id,
23. _creator: {
24. _id: x._creator._id,
25. name: x._creator.name,
26. userName: x._creator.userName,
27. avatar: x._creator.avatar || '/public/resources/avatars/0.png'
28. },
29. date: x.date,
30. message: x.message,
31. liked: x.likeRef.find(likeUser =>
32. likeUser.toString() === user.id || null),
33. likeCounter: x.likeCounter,
34. replys: x.replys,
35. image: x.image
36. }
37. })
38.
39. res.send({
40. ok: true,
41. body: response
42. })
43. } catch (error) {
44. res.send({
45. ok: false,
46. message: error.message || "Error al consultar los tweets",
47. error: err

Página | 524
48. })
49. }
50. }

Este servicio es prácticamente igual al de búsqueda de los últimos tweets, pero


tiene dos pequeñas diferencias. Debido a que la búsqueda es por usuario, es
necesario validar que exista el usuario solicitado (línea 9), si el usuario existe,
entonces podemos proceder con la búsqueda de los tweets. Agregamos el filtro
por usuario (línea 14) y nuevamente utilizamos las propiedades limit y skip para
paginar los resultados. El resto del servicio es exactamente igual al anterior.
Terminamos aquí exportando la función al final del archivo.

Por otra parte, es necesario agregar el router correspondiente al archivo api.js:

1. //Private access services (security)


2. router.get('/secure/relogin',userController.relogin)
3. router.get('/secure/suggestedUsers',userController.getSuggestedUser)
4.
5. //Public access services
6. router.get('/tweets/:user', tweetController.getUserTweets)
7. router.get('/tweets',tweetController.getNewTweets)
8. router.get('/usernameValidate/:username', userController.usernameValidate)
9. router.get('/profile/:user',userController.getProfileByUsername)
10. router.post('/signup', userController.signup)
11. router.post('/login', userController.login)

Si actualizamos la pantalla de perfil, podremos ver que solo salen tweet del
usuario en cuestión.

525 | Página
Servicio – Actualización del perfil de usuario

Mediante este servicio es posible actualizar el nombre, la descripción, el avatar y


el banner de nuestro perfil. En el proyecto Mini Twitter es utilizado para en la
pantalla del perfil de usuario, al momento de guardar los cambios.

Veamos la ficha del servicio:

Nombre Consulta de Tweets por usuario

URL /tweets/:username

URL username: nombre de usuario del perfil a consultar.


params

Método PUT

Headers authorization: token del usuario

Request
• username: usuario a actualizar
• name: Nuevo nombre de usuario
• description: nueva descripción
• vatar: nueva foto de perfil
• banner: nueva imagen para el banner.

1. {
2. "username":"oscar",
3. "name":"Oscar Blancarte.",
4. "description":"User description",
5. "avatar":"<Base 64 Image>",
6. "banner":"<Base 64 Image>"
7. }

Response
• Ok: booleana que indica si la operación fue
exitosa o no.
• Body: contiene todos los datos actualizados del
perfil.

1. "ok": true,
2. "body": {
3. "_id": "5a012b1486b5c864a4fe6223",
4. "name": "Oscar Blancarte",
5. "description": "Nuevo en Twitter",
6. "userName": "oscar",
7. "avatar": "<base64 image>",
8. "banner": "<base64 image>",
9. "tweetCount": 76,
10. "following": 1,
11. "followers": 2,
12. "follow": false
13. }
14. }

Página | 526
Agregaremos la función updateProfile al archivo UserController.js, la cual se
verá de la siguiente manera:

1. // Servicio utilizado para actualizar el perfil del usuario


2. const updateProfile = async (req, res, err) => {
3. let username = req.user.username
4.
5. try {
6. const updates = {
7. name: req.body.name,
8. description: req.body.description,
9. avatar: req.body.avatar,
10. banner: req.body.banner
11. }
12.
13. let response = await Profile.updateOne({ userName: username }, updates)
14. res.send({
15. ok: true
16. })
17. } catch (error) {
18. res.send({
19. ok: false,
20. message: error.message || "Error al actualizar el perfil"
21. })
22. }
23. }

Este es un método bastante simple, pues solo se crea un objeto con los cambios
(línea 6) y luego se procese con la actualización del perfil mediante el método
update del schema Profile. Se define el nombre de usuario como filtro para solo
actualizar el registro correcto, solo que el nombre de usuario no lo tomamos del
request, si no del token. De esta forma nos aseguramos que no puedan actualizar
más que su propio perfil.

Finalmente agregamos el método a los exports y agregamos el router


correspondiente al archivo api.js:

1. //Private access services (security)


2. router.get('/secure/relogin',userController.relogin)
3. router.get('/secure/suggestedUsers',userController.getSuggestedUser)
4. router.put('/secure/profile', userController.updateProfile)
5.
6. //Public access services
7. router.get('/tweets/:user', tweetController.getUserTweets)
8. router.get('/tweets',tweetController.getNewTweets)
9. router.get('/usernameValidate/:username', userController.usernameValidate)
10. router.get('/profile/:user',userController.getProfileByUsername)
11. router.post('/signup', userController.signup)
12. router.post('/login', userController.login)

Para comprobar los resultados, solo restaría editar tu perfil de usuario, cambiar
tu nombre, descripción, avatar y banner, guardar los cambios y actualizar la vista
para asegurarnos de que los cambios se guardaron correctamente.

527 | Página
Servicio – Consulta de personas que sigo

Este servicio se utiliza para recuperar el perfil de todas las personas a las que
seguimos. En el proyecto mini Twitter se utiliza en la sección del perfil del usuario.

Veamos la ficha:

Nombre Consulta de personas que sigo

URL /followings/:user

URL user: nombre de usuario del cual se requieren las


params personas que sigue

Método GET

Headers N/A

Request N/A

Response
• Ok: valor booleano que indica si la operación
fue exitosa o no.
• Body: array con un listado de perfiles de
usuario

1. {
2. "ok":true,
3. "body":[
4. {
5. "_id":"5938bdd8a4df2379ccabc1aa",
6. "userName":"emmanuel",
7. "name":"Emmauel Lopez",
8. "description":"Nuevo en Twitter",
9. "avatar":"<Base 64 Image>",
10. "banner":"<Base 64 Image>"
11. },
12. ...
13. ]
14. }

Crearemos la función getFollowing dentro del archivo UserController.js el cual


se verá de la siguiente manera:

1. // Servicio para consultar a los usuarios que seguimos


2. const getFollowing = async (req, res, err) => {
3. let username = req.params.user
4.
5. try {
6. let followings = await Profile.findOne({ userName: username })
7. .populate("followingRef")
8.
9. if (followings === null) throw new Error("No existe el usuario")
10.
11. let response = followings.followingRef.map(x => {
12. return {

Página | 528
13. _id: x._id,
14. userName: x.userName,
15. name: x.name,
16. description: x.description,
17. avatar: x.avatar || '/public/resources/avatars/0.png',
18. banner: x.banner || '/public/resources/banners/4.png'
19. }
20. })
21.
22. res.send({
23. ok: true,
24. body: response
25. })
26.
27. } catch (error) {
28. res.send({
29. ok: false,
30. message: error.message || "Error al consultara los seguidores",
31. })
32. }
33. }

Para obtener las personas que sigue un determinado usuario, es tan simple como,
consultar el perfil deseado y luego realizar un populate (join) mediante el campo
followingRef (línea 7), el cual es un array de ID de las personas que sigue. Ya
con eso, solo falta iterar los resultados para generar la respuesta (línea 11).

Finalmente agregamos el método al export y agregamos el router


correspondiente:

1. //Public access services


2. router.get('/tweets/:user', tweetController.getUserTweets)
3. router.get('/tweets',tweetController.getNewTweets)
4. router.get('/usernameValidate/:username', userController.usernameValidate)
5. router.get('/profile/:user',userController.getProfileByUsername)
6. router.post('/signup', userController.signup)
7. router.post('/login', userController.login)
8. router.get('/followings/:user',userController.getFollowing)

Para comprobar los resultados, solo basta con ir a la sección de “Siguiendo” del
perfil del usuario:

529 | Página
Fig. 155 - Probando la sección de "siguiendo".

Servicio – Consulta de seguidores

Este servicio se utiliza para recuperar el perfil de todas las personas que siguen
a un determinado usuario. En el proyecto Mini Twitter se utiliza en la sección del
perfil del usuario.

Veamos la ficha:

Nombre Consulta de seguidores

URL /followers/:user

URL user: nombre de usuario del cual se requieren las


params personas que sigue

Método GET

Headers N/A

Request N/A

Response
• Ok: valor booleano que indica si la operación
fue exitosa o no.
• Body: array con un listado de perfiles de
usuario

15. {
16. "ok":true,
17. "body":[
18. {
19. "_id":"5938bdd8a4df2379ccabc1aa",
20. "userName":"emmanuel",

Página | 530
21. "name":"Emmauel Lopez",
22. "description":"Nuevo en Twitter",
23. "avatar":"<Base 64 Image>",
24. "banner":"<Base 64 Image>"
25. },
26. ...
27. ]
28. }

Crearemos la función getFollower dentro del archivo UserController.js el cual


se verá de la siguiente manera:

1. // Servicio que consulta todos los seguidores


2. const getFollower = async (req, res, err) => {
3. let username = req.params.user
4.
5. try {
6. let followers = await Profile.findOne({ userName: username })
7. .populate("followersRef")
8. if (followers === null) throw new Error("No existe el usuario")
9.
10. let response = followers.followersRef.map(x => {
11. return {
12. _id: x._id,
13. userName: x.userName,
14. name: x.name,
15. description: x.description,
16. avatar: x.avatar || '/public/resources/avatars/0.png',
17. banner: x.banner || '/public/resources/banners/4.png'
18. }
19. })
20. res.send({
21. ok: true,
22. body: response
23. })
24. } catch (error) {
25. res.send({
26. ok: false,
27. message: error.message || "Error al consultara los seguidores",
28. })
29. }
30. }

Para obtener las personas que siguen al usuario, es tan simple como, consultar
el perfil deseado y luego realizar un populate (join) mediante el campo
followersRef (línea 7), el cual es un array de ID de las personas que lo siguen.
Ya con eso, solo falta iterar los resultados para generar la respuesta (línea 10).

Finalmente agregamos el método al export y agregamos el router


correspondiente:

1. //Public access services


2. router.get('/tweets/:user', tweetController.getUserTweets)
3. router.get('/tweets',tweetController.getNewTweets)
4. router.get('/usernameValidate/:username', userController.usernameValidate)
5. router.get('/profile/:user',userController.getProfileByUsername)
6. router.post('/signup', userController.signup)
7. router.post('/login', userController.login)

531 | Página
8. router.get('/followings/:user',userController.getFollowing)
9. router.get('/followers/:user',userController.getFollower)

Para comprobar los resultados, solo basta con ir a la sección de “Seguidores” del
perfil del usuario:

Fig. 156 - Probando la sección de "Seguidores".

Servicio – Seguir

Este servicio se utiliza para seguir o dejar de seguir a un usuario. Es utilizado


desde la pantalla del perfil del usuario.

Este servicio es un poco más complicado que el resto, pues implica transaccionar
el documento de los dos perfiles involucrados. A un documento hay que agregarle
un seguidor (followersRef) y al otro hay que agregarle que lo seguimos
(followingsRef). Pero si lo dejamos de seguir, hay que hacer exactamente lo
contrario.

Veamos la fecha del servicio:

Nombre Seguir

URL /secure/follow

URL N/A
params

Método GET

Headers authorization: token del usuario

Página | 532
Request
• followingUser: nombre de usuario del perfil
que deseamos seguir/dejar de seguir

1. {
2. "followingUser":"jperez"
3. }

Response
• Ok: booleana que indica si la operación fue
exitosa o no.
• Unfollow: booleano que indica si seguimos o
dejamos de seguir, false indica que lo seguimos
y true que lo dejamos de seguir

1. {
2. "ok": true,
3. "unfollow": false
4. }

Agregaremos la función follow al archivo UserController.js, el cual se verá de


la siguiente manera:

1. // Servicio que permite comenzar a seguir a otro usuario


2. const follow = async (req, res, err) => {
3. let username = req.user.username
4. let followingUser = req.body.followingUser
5. let session
6.
7. try {
8. session = await mongoose.startSession()
9. session.startTransaction()
10.
11. // Find the two profiles
12. let users = await Profile.find({
13. userName: { $in: [username, followingUser] } })
14.
15. if (users.length != 2) throw { message: "El usuario no existe" }
16. let my = users.find(x => x.userName == username)
17. let other = users.find(x => x.userName == followingUser)
18.
19. let following = my.followingRef.find(
20. x => x.toString() === other._id.toString()) != null
21.
22. let myUpdate = null
23. let otherUpdate = null
24. if (following) {
25. myUpdate = { $pull: { followingRef: other._id } }
26. otherUpdate = { $pull: { followersRef: my._id } }
27. } else {
28. myUpdate = { $push: { followingRef: other._id } }
29. otherUpdate = { $push: { followersRef: my._id } }
30. }
31.
32. let myUp = await Profile.updateOne(
33. { userName: my.userName }, myUpdate, { session })
34. let otherUp = await Profile.updateOne(
35. { userName: other.userName }, otherUpdate, { session })
36.
37. res.send({

533 | Página
38. ok: true,
39. unfollow: following,
40. })
41.
42. session.commitTransaction()
43. } catch (err) {
44. console.log("error => ", err.message)
45. session.abortTransaction()
46. res.send({
47. ok: false,
48. message: err.message || "Error al ejecutar la operación",
49. })
50. }
51. }

Para el servicio follow vamos a utilizar una nueva característica de MongoDB que
son las transacciones, ya que seguir a un usuario requiere de dos pasos, por un
lado, tenemos que actualizar nuestro perfil para agregar a la persona que
seguimos, pero por otro lado, tenemos que actualizar el perfil de la otra persona
para agregar nuestro perfil a las personas que lo siguen a él, por tal motivo,
necesitamos crear una transacción y garantizar que los dos cambios se apliquen
al mismo tiempo, y en caso de una falla, las dos operaciones sean descartadas.

El primer paso para crear la transacción es obtener una referencia al objeto de


la transacción, por eso, utilizamos el método startSession que proporciona
Mongoose, una vez con la referencia a ella, utilizamos el método startTransaction
para iniciar la transacción.

En la línea 12 hacemos una búsqueda para recuperar los dos perfiles, el nuestro
y el de la persona que queremos comenzar a seguir, por ello utilizamos el $in.
En la línea 15 validamos si la respuesta retorno los dos perfiles, ya que en caso
de no traerlos, quiere decir que uno de los perfiles no existe, y deberemos lanzar
un error.

Una vez con los dos perfiles, los separamos en variables diferentes para tener
una referencia más rápido a ellos, en la variable my (línea 16) guardamos nuestro
perfil y en la variable other (línea 17), guardamos el perfil de la persona que
queremos comenzar a seguir.

Debido a que este servicio sirve para comenzar a seguir a un usuario o dejarlo
de seguir en caso de que ya lo estuviéramos siguiendo, debemos de determinar
si ya lo seguimos primero (línea 19).

Segundo si lo seguimos o no, será la operación que realizaremos sobre los dos
perfiles. Si ya lo seguimos, quiere decir que la operación hará que dejemos de
seguirlo, por lo que hacemos un pull (eliminar) y un push en nuestra lista de
personas que seguimos (línea 24), en otro caso, debemos hacer lo contrario.

Finalmente en la línea 32 y 34 se actualizan los dos perfiles, pero en esta ocasión,


observa que le enviamos el objeto session como parámetro, lo que hace que
estos cambios se realicen dentro de la misma transacción.

Página | 534
Finalmente, solo quedaría exportar la nueva función y agregar el router
correspondiente en el archivo api.js:

1. //Private access services (security)


2. router.get('/secure/relogin',userController.relogin)
3. router.get('/secure/suggestedUsers',userController.getSuggestedUser)
4. router.put('/secure/profile', userController.updateProfile)
5. router.post('/secure/follow', userController.follow)

Para probar los cambios, solo tenemos que presionar el botón se “seguir” o
“siguiendo" del perfil de cualquier usuario.

Servicio – Crear un nuevo Tweet

Este servicio nos permitirá crear un nuevo Tweet o crear una respuesta a un
Tweet existente. En el proyecto Mini Twitter es utilizado por el componente
Reply.js, el cual se utiliza desde la página principal o como parte del detalle de
un tweet para realizar una respuesta.

Veamos la ficha del servicio:

Nombre Crear un nuevo Tweet

URL /secure/tweets

URL N/A
params

Método POST

Headers authorization: token del usuario

Request
• message: Texto asociado al Tweet.
• image : Imagen asociada al Tweet.

1. {
2. "message": "¡hola mundo! este es mi primer Tweet",
3. "image": "<base64 img>"
4. }

535 | Página
Response
• Ok: valor booleano que indica si la operación fue
exitosa o no.
• tweet: Objecto con todos los datos del Tweet,
entre los que están:
o _id: ID asociado al Tweet.
o date: fecha de creación
o message: mensaje asociado al tweet
o Image: Imagen asociada al tweet.

1. {
2. "ok": true,
3. "tweet": {
4. "__v": 0,
5. "_creator": "593616dc3f66bd6ac4596328",
6. "message": "hola mundo",
7. "image": "<base64 img>",
8. "_id": "59f66ea3ceb9f6a00c7b3143",
9. "replys": 0,
10. "likeCounter": 0,
11. "date": "2017-10-30T00:13:23.293Z"
12. }
13. }

Lo primero que haremos será crear la función addTweet dentro del archivo
TweetController.js:

1. function addTweet(req, res, err){


2. if(req.body.tweetParent){ // Reply Tweet
3. createReplyTweet(req, res, err)
4. }else{ // New Tweet
5. createNewTweet(req, res, err)
6. }
7. }

Dado que la lógica para crear un Tweet y una respuesta es distinta, hemos
separado la funcionalidad en dos funciones. La función createNewTweet (línea 5)
la utilizaremos para crear un nuevo Tweet, mientras que la función
createReplyTweet (línea 3) es para crear las respuestas.

La función createNewTweet quedará de la siguiente manera:

1. // servicio que crea un nuevo Tweet


2. const createNewTweet = async (req, res, err) => {
3. console.log("createNewTweet =>")
4. let user = req.user
5.
6. let session
7. try {
8. session = await mongoose.startSession()
9. session.startTransaction()
10.
11. let newTweet = new Tweet({
12. _creator: user.id,

Página | 536
13. tweetParent: req.body.tweetParent,
14. message: req.body.message,
15. image: req.body.image
16. })
17.
18. //Update the user profile to increment the tweet counter
19. let updateProfile = await Profile.updateOne(
20. { _id: user.id }, { $inc: { tweetCount: 1 } }, { session })
21.
22. // If the user profile dont exist, throw error
23. if ((!updateProfile.ok) || updateProfile.nModified == 0)
24. throw new Error("No existe el usuario")
25.
26. // Save the new Tweet
27. newTweet = await newTweet.save({ session })
28. res.send({
29. ok: true,
30. tweet: newTweet
31. })
32.
33. session.commitTransaction()
34. } catch (error) {
35. console.log("error => ", error.message)
36. session.abortTransaction()
37. res.send({
38. ok: false,
39. message: error.message || "Error al guardar el Tweet",
40. error: error.error || error
41. })
42. }
43. }

Podrás ver que nuevamente hemos creado una transacción (líneas 8 y 9). Y lo
que seguiría es crear el tweet, el cual se realiza en tres partes. La primera es
crear el tweet mediante el schema Tweet (línea 11). El segundo paso es
incrementar el contador de tweet del usuario (línea 19), para lo cual utilizamos
el operador $inc, pues permite una actualización segura, ya que, sin importar el
valor actual, solamente le incrementará en 1. Si la actualización termino
correctamente, entonces podemos proceder con guardar el Tweet (línea 27).
Observa que para incrementar el contador de tweets y guardar el nuevo tweet
estamos enviando la referencia a la session, con lo que garantizamos que los dos
cambios se realicen en la misma transacción.

Si el último paso termino correctamente, entonces simplemente retornamos el


tweet creado.

Por otra parte, tenemos la función createReplyTweet la cual se verá de la


siguiente manera:

1. // Add reply to exist tweet


2. const createReplyTweet = async (req, res, err) => {
3. let user = req.user
4.
5. let session
6. try {
7. session = await mongoose.startSession()
8. session.startTransaction()
9.
10.

537 | Página
11. let newTweet = new Tweet({
12. _creator: user.id,
13. tweetParent: req.body.tweetParent,
14. message: req.body.message,
15. image: req.body.image
16. })
17.
18. // Increment replys in the parent tweet
19. let updatedTweet = await Tweet.updateOne(
20. { _id: req.body.tweetParent }, { $inc: { replys: 1 } })
21. .session(session)
22.
23. // Throw error if the parent tweet dont exist
24. if ((!updatedTweet.ok) || updatedTweet.nModified == 0)
25. throw new Error("No existe el Tweet padre")
26.
27. newTweet = await newTweet.save({ session })
28. res.send({
29. ok: true,
30. tweet: newTweet
31. })
32. session.commitTransaction();
33. } catch (error) {
34. session.abortTransaction()
35. console.log("error => ", error.message)
36. res.send({
37. ok: false,
38. message: error.message || "Error al guardar el Tweet",
39. error: err
40. })
41. }
42. }
43.

Cuando contestamos un tweet, lo manejamos como si fuera un nuevo tweet, pero


tiene algunas diferencias, la primera y más importante es que, lleva un campo
llamado tweetParent, el cual indica si este tweet es una respuesta de otro tweet,
por otro lado, no incrementa el número de tweets del usuario, pero si incrementa
el contado de respuestas del tweet padre, es por ello que primero incrementamos
el contador de respuestas del tweet padre (línea 19) y luego guardamos la
respuesta como un nuevo Tweet (línea 27). También podemos ver que utilizamos
la sesión para garantizar que estos dos cambios ocurran dentro de la misma
transacción.

Ahora solo nos restaría agregar únicamente la función addTweet a los exports y
agregar el router correspondiente en el archivo api.js:

1. //Private access services (security)


2. router.get('/secure/relogin',userController.relogin)
3. router.get('/secure/suggestedUsers',userController.getSuggestedUser)
4. router.put('/secure/profile', userController.updateProfile)
5. router.post('/secure/follow', userController.follow)
6. router.post('/secure/tweet', tweetController.addTweet)

Página | 538
Para probar los cambios, solo basta con crear un nuevo tweet desde la pantalla
principal, aunque la respuesta será difícil probar, pues de momento no podremos
ver el detalle del Tweet hasta implementar el servicio para consultar el detalle de
un tweet.

Fig. 157 –Probando la creación de un nuevo Tweet.

Servicio – Like

El servicio like nos permite indicar que un tweet es de nuestro agrado, con lo
cual, el tweet va incrementando un contador de likes. Aunque también le
podemos indicar que algo ya no nos gusta. Este servicio se utiliza desde el
componente Tweet.js cuando presionamos el botón del corazón.

Veamos la ficha del servicio:

Nombre Like

URL /secure/like

URL N/A
params

Método POST

Headers authorization: token del usuario

Request
• tweetID: ID del Tweet al que deseamos dar like
• like : Valor booleano que indica si queremos darle
like (true) o dislike (false).

1. {
2. "tweetID": "59ed5728022307a950b3c756",
3. "like": true

539 | Página
4. }

Response
• Ok: booleano que indica si la operación fue exitosa
o no.
• body: Objeto que contiene los datos actualizados
del Tweet.

1. {
2. "ok": true,
3. "body": {
4. "_id": "59ed5728022307a950b3c756",
5. "_creator": "593616dc3f66bd6ac4596328",
6. "message": "Mi libro de \"Patrones de diseño\"",
7. "image": "<Base 64 Image>",
8. "__v": 0,
9. "replys": 4,
10. "likeCounter": 1,
11. "date": "2017-10-23T02:42:48.679Z"
12. }
13. }

Iniciaremos creado la función like dentro del archivo TweetController.js, el cual


se vera de la siguiente manera:

1. // Incrementa los Liks de un Tweet


2. const like = async (req, res, err) => {
3. let user = req.user
4.
5. try {
6. let updateStatement = req.body.like
7. ? { $push: { likeRef: mongoose.Types.ObjectId(user.id) } }
8. : { $pull: { likeRef: mongoose.Types.ObjectId(user.id) } }
9.
10. // Find the tweet and increase/decrease like counter
11. //findOneAndUpdate
12. let tweet = await Tweet.findOneAndUpdate(
13. { _id: req.body.tweetID }, updateStatement)
14.
15. // If the tweet dont exist, throw error
16. if (tweet == null) throw { message: "No existe el Tweet solicitado" }
17.
18. res.send({
19. ok: true,
20. body: {
21. _creator: tweet._creator,
22. tweetParent: tweet.tweetParent,
23. date: tweet.date,
24. message: tweet.message,
25. likeRef: tweet.likeRef,
26. image: tweet.image,
27. replys: tweet.replys,
28. likeCounter: tweet.likeCounter += req.body.like ? 1 : -1
29. }
30. })
31. } catch (error) {
32. console.error("error => ", error.message)
33. res.send({
34. ok: false,

Página | 540
35. message: err.message || "Error al actualizar el Tweet",
36. error: error.error || error
37. })
38. }
39. }
40.

Darle like a un tweet es una tarea muy simple, pues solo es necesario agregar el
ID del usuario dentro del array likeRef del objeto Tweet. Por otra parte, si lo que
buscamos es quitar el like, solo tenemos que eliminar el ID del array.

El primer paso es determina la operación que vamos a realizar, es decir, si es un


like o un dislike y crear el objeto con las instrucciones para la actualización. Esto
lo hacemos en las líneas 7 y 8. Si el request tiene la propiedad like en true,
indica un like, por lo que hacemos un push al array likeRef (línea 7). Por otro
lado, si es false, realizaremos un pull para eliminar el ID del array likeRef (línea
8).

El siguiente paso es realizar la actualización mediante el método


findOneAndUpdate (línea 12) y retornar el tweet actualizado (línea 18). Solo hay
algo importante a resaltar. El método findOneAndUpdate primero buscar el
documento y luego lo actualiza, por lo que el virtual likeCounter no estar
actualizado con el nuevo incremento, por lo que sumamos o restamos en 1 según
la operación (línea 28).

Finalmente, solo restaría agregar esta nueva función a los exports y agregar el
router correspondiente al archivo api.js:

1. //Private access services (security)


2. router.get('/secure/relogin',userController.relogin)
3. router.get('/secure/suggestedUsers',userController.getSuggestedUser)
4. router.put('/secure/profile', userController.updateProfile)
5. router.post('/secure/follow', userController.follow)
6. router.post('/secure/tweet', tweetController.addTweet)
7. router.post('/secure/like', tweetController.like)

Para probar los cambios solo presionemos el botón del corazón en cualquier
Tweet:

Fig. 158 - Probando el servicio de like.

541 | Página
Servicio – Consultar el detalle de un Tweet

El último servicio que nos resta para terminar el API es de la consulta del detalle
de un tweet, el cual nos permite recuperar todas las respuestas asociadas a un
tweet.

Este servicio es utilizado al momento de dar click sobre cualquier tweet, donde
de forma modal, podemos ver todo el tweet con su detalle.

Veamos la ficha del servicio:

Nombre Like

URL /tweetDetails/:tweetID

URL tweetID: ID del Tweet a consular el detalle.


params

Método GET

Headers N/A

Request N/A

Response
• Ok: booleano que indica si la operación fue exitosa
o no.
• body: Objeto que contiene un Tweet con todo su
detalle
o _id: identificador único del Tweet.
o _creator: Perfil del usuario que creo el
Tweet.
o _date: fecha de creación del Tweet.
o Message: Texto del Tweet.
o Liked: indica si le dimos like al Tweet.
o likeCounter: contador de likes
o image: Imagen asociada al Tweet.
o Replys: números de respuestas
o reploysTweets: Arreglo de Tweets
correspondientes a las respuestas

1. {
2. "ok": true,
3. "body": {
4. "_id": "59ed5728022307a950b3c756",
5. "_creator": {
6. "_id": "593616dc3f66bd6ac4596328",
7. "name": "Oscar Blancarte.",
8. "userName": "oscar",
9. "avatar": "<Base 64 Image>"
10. },
11. "date": "2017-10-23T02:42:48.679Z",
12. "message": "Mi libro de \"Patrones de diseño\"",
13. "liked": false,

Página | 542
14. "likeCounter": 0,
15. "image": "<Base 64 Image>",
16. "replys": 1,
17. "replysTweets": [
18. {
19. "_id": "59f51e49830f6ac1c4c841a2",
20. "_creator": {
21. "_id": "593616dc3f66bd6ac4596328",
22. "name": "Oscar Blancarte.",
23. "userName": "oscar",
24. "avatar": "<Base 64 Image>"
25. },
26. "date": "2017-10-29T00:18:17.071Z",
27. "message": "dgnfdgh",
28. "liked": false,
29. "likeCounter": 0,
30. "replys": 0,
31. "image": null
32. },
33. ...
34. ]
35. }
36. }

Agregaremos la función getTweetDetails dentro del archivo TweetController.js,


el cual deberá verse de la siguiente manera:

1. const getTweetDetails = async (req, res, err) => {


2. let user = req.user || {}
3.
4. try {
5. let tweetId = req.params.tweet
6. if (!mongoose.Types.ObjectId.isValid(tweetId))
7. throw new Error("ID del Tweet Inválido")
8.
9.
10.
11. let tweet = await Tweet.findOne({ _id: tweetId }).populate("_creator")
12. if (tweet == null) throw { message: "No existe el Tweet" }
13.
14. let tweets = await Tweet.find(
15. { tweetParent: mongoose.Types.ObjectId(tweetId) })
16. .populate("_creator")
17. .sort('-date')
18.
19. tweets = tweets || []
20.
21. replys = tweets.map(x => {
22. return {
23. _id: x._id,
24. _creator: {
25. _id: x._creator._id,
26. name: x._creator.name,
27. userName: x._creator.userName,
28. avatar: x._creator.avatar || '/public/resources/avatars/0.png'
29. },
30. date: x.date,
31. message: x.message,
32. liked: x.likeRef.find(
33. likeUser => likeUser.toString() === user.id || null),
34. likeCounter: x.likeCounter,
35. replys: x.replys,

543 | Página
36. image: x.image,
37.
38. }
39. })
40.
41. res.send({
42. ok: true,
43. body: {
44. _id: tweet._id,
45. _creator: {
46. _id: tweet._creator._id,
47. name: tweet._creator.name,
48. userName: tweet._creator.userName,
49. avatar: tweet._creator.avatar
50. || '/public/resources/avatars/0.png'
51. },
52. date: tweet.date,
53. message: tweet.message,
54. liked: tweet.likeRef.find(
55. likeUser => likeUser.toString() === user.id || null),
56. likeCounter: tweet.likeCounter,
57. image: tweet.image,
58. replys: tweet.replys,
59. replysTweets: replys
60. }
61. })
62.
63. } catch (error) {
64. res.send({
65. ok: false,
66. message: error.message || "Error al cargar el Tweet",
67. e: error
68. })
69. }
70. }
71.

La consulta del detalle del Tweet puede aparentar complicada, pero en realidad
es muy simples y solo se requiere de dos pasos para obtener la información
necesaria. El primero es consular al Tweet del cual se requiere el detalle, para
eso, recuperamos el ID desde los URL params (línea 5) y luego realizar la
búsqueda del Tweet por medio del ID (línea 11), aprovechamos para realizar un
populate (join) con el perfil del usuario.

Ya con el tweet de la búsqueda anterior, podemos realizar una nueva búsqueda


en los tweets para traernos todos los tweets donde su padre sea el tweet anterior
(línea 14) y realizamos nuevamente un populate sobre el creador de las
respuestas. Finalmente, solo creamos la respuesta con el tweet padre y los
tweets hijos (línea 41).

En este punto, solo tenemos que exportar la nueva función y agregar el router
correspondiente:

1. //Public access services


2. router.get('/tweets/:user', tweetController.getUserTweets)
3. router.get('/tweets',tweetController.getNewTweets)
4. router.get('/usernameValidate/:username', userController.usernameValidate)
5. router.get('/profile/:user',userController.getProfileByUsername)
6. router.post('/signup', userController.signup)
7. router.post('/login', userController.login)

Página | 544
8. router.get('/followings/:user',userController.getFollowing)
9. router.get('/followers/:user',userController.getFollower)
10. router.get('/tweetDetails/:tweet', tweetController.getTweetDetails )

Para probar los cambios, solo tendremos que dar click sobre cualquier tweet y
un popup debería emerger con todo el detalle del Tweet:

Fig. 159 - Probando el detalle del Tweet.

Agregando validaciones a los servicios

En este punto podrás ver que los servicios funcionan adecuadamente si les
mandamos los parámetros adecuados, sin embargo, no hemos validado las
entradas para garantizar que todos los parámetros necesarios estén presentes
durante la ejecución del servicio.

Para validar los inputs tenemos el módulo express-validator que instalaremos


con el comando

npm install --save-dev express-validator

Para agregar una validación, es necesario agregar un array con todas las reglas
de validación al momento de crear el route, tal y como lo podemos ver en el
siguiente fragmento de código:

1. const { body, validationResult } = require('express-validator');

545 | Página
2.
3. app.post('/login', [
4. body('username').isEmail(),
5. body('password').isLength({ min: 5 })
6. ], (req, res) => {
7. const errors = validationResult(req);
8. if (!errors.isEmpty()) {
9. return res.status(422).json({ errors: errors.array() });
10. }
11.
12. User.create({
13. username: req.body.username,
14. password: req.body.password
15. }).then(user => res.json(user));
16. });

Lo que hacemos aquí es usar la función body para recuperar los valores del
request, en este ejemplo recuperamos el username y password (líneas 4 y 5) para
después aplicarles ciertas validaciones que viene definidas en express-validator.
En este ejemplo, validamos que el username sea un correo electrónico y que el
password tenga una longitud mínima de 5 caracteres.

Estas validaciones no impedirán que nuestro servicio se ejecute, en su lugar, la


función que definimos para procesar la solicitud se ejecutará, pero desde ella es
posible recuperar los errores utilizando la función validationResult, la cual nos
arrojará una lista con todos los errores detectados (línea 7). Finalmente, si la
lista NO esta vacía, quiere decir que se han encontrado errores.

Si bien esta forma de realizar las validaciones funciona, en lo particular no me


gusta, ya que contamina al servicio con la lógica de validación, por lo que a mí
me gusta separar las validaciones como un archivo independiente y desde allí
ejecutar las validaciones y rechazar la llamada antes de que el request llegue al
servicio. Veamos el modelo que propongo:

1. const { body, validationResult } = require('express-validator')


2.
3. const validationRules = () => {
4. return [
5. body('password').isLength({ min: 8 }),
6. body('username').isLength({ min: 5 })
7. ]
8. }
9.
10. const validate = (req, res, next) => {
11. const errors = validationResult(req)
12. if (errors.isEmpty()) {
13. return next()
14. }
15.
16. const extractedErrors = errors.array().map(
17. err => { return { [err.param]: err.msg } })
18.
19. return res.status(400).json({
20. ok: false,
21. errors: extractedErrors,
22. })
23. }
24.
25. module.exports = {

Página | 546
26. validationRules,
27. validate,
28. }

Hemos creados dos funciones, la primera (validationRules) define las reglas


para validar el request, en esta sección podemos definir todas las reglas que sean
necesarias, pero al final, debe de retornar un array con las reglas.

La segunda función (extractErrores) obtiene los errores con validationResult


(líneas 11) y si la lista esta vacía, entonces ejecutamos la función next (línea 13),
lo que hace que el servicio continúe con su flujo normal, sin embargo, si
encontramos errores, entonces respondemos con un código 400 (bad request) y
retornamos los errores.

Por otro lado, es necesario agregar estas validaciones cuando creamos el route:

1. import {validationRules, validate} from './validator.js'


2.
3. app.post('/user', validationRules(), validate , (req, res) => {
4. const errors = validationResult(req);
5. if (!errors.isEmpty()) {
6. return res.status(422).json({ errors: errors.array() });
7. }
8.
9. User.create({
10. username: req.body.username,
11. password: req.body.password
12. }).then(user => res.json(user));
13. });

Observa como hemos agregado la función validateRules y validate


encadenados, lo que hace que se ejecute en el orden en el que están definidos,
de esta forma, primero retornamos las reglas de validación, luego, cortamos la
ejecución si hay errores y finalmente, ejecutamos el servicio si no hay ningún
error.

Algunas de las reglas que podemos utilizar son:

• .isArray(options): valida que el campo sea un arreglo


o min: longitud mínima del arreglo.
o max: longitud máxima del arreglo
• .isString(): valida que el campo sea de tipo String
• .notEmpty(): valida que el campo no este vacio.
• .isLength(options): valida la longitud de un String
o min: longitud mínima esperada
o max: longitud máxima esperada
• .custom(): permite agregar una validación personalizada.
• .exist(): Permite validar si un campo está presente.

547 | Página
Puedes ver la lista completa de validaciones disponibles en la página web de
express-validator: https://express-validator.github.io/docs/validation-chain-
api.html

Validando el servicio signin

Siguiendo el procedimiento anterior, vamos a implementar las validaciones en un


archivo independiente, por este motivo, vamos a crear el archivo
userValidtor.js en el path /api/validators, el cual se verá de la siguiente
forma:

1. const { body, validationResult } = require('express-validator')


2. var Profile = require('../models/Profile')
3.
4. const userValidationRules = () => {
5. return [
6. body('password').isLength({ min: 8 })
7. .withMessage('Password too short, minimum 8 characters'),
8. body('password').isLength({ max: 30 })
9. .withMessage('Password too long, maximum 30 characters'),
10.
11. body('name').isLength({ min: 5 })
12. .withMessage('Name too short, minimum 5 characters'),
13. body('name').isLength({ max: 20 })
14. .withMessage('Name too long, maximum 50 characters'),
15.
16. body('username').isLength({ min: 5 })
17. .withMessage('Username too short, minimum 5 characters'),
18. body('username').isLength({ max: 15 })
19. .withMessage('Username too long, maximum 15 characters'),
20. body('username').custom(username => {
21. return Profile.find().byUsername(username.toLowerCase())
22. .then(profiles => {
23. if (profiles.length > 0)
24. return Promise.reject('The username is already in use')
25. })
26. })
27. ]
28. }
29.
30. const userValidate = (req, res, next) => {
31. const errors = validationResult(req)
32. if (errors.isEmpty()) {
33. return next()
34. }
35.
36. const extractedErrors = errors.array().map(
37. err => { return { [err.param]: err.msg } })
38.
39. return res.status(422).json({
40. ok: false,
41. errors: extractedErrors,
42. })
43. }
44.
45. module.exports = {
46. userValidationRules,
47. userValidate,

Página | 548
48. }

Hemos definido el método userValidatonUser en el cual hemos puesto todas las


reglas de validación que deberemos de ejecutar, que valida los campos password,
username y name. Validamos la longitud mínima y máxima y usamos el método
withMessage para definir el error que regresará el API cuando la regla no se
cumpla.

Por otra parte, el método userValidate es el que recupera los errores y corta la
ejecución si detecta un error.

El último paso sería agregar las validaciones al Route, por lo que vamos a ir al
archivo api.js y agregar lo siguiente:

1. const {userValidationRules, userValidate} = require('./validators/userValidator')


2.
3. router.post('/signup', userValidationRules(), userValidate, userController.signup)

Importamos las dos funciones que acabamos de crear en el archivo


userValidator.js y el luego las agregamos como parámetros al Route.

Para comprobar que esto se está cumpliendo, vamos a ejecutar una prueba desde
la herramienta Postman, veamos el resultado:

549 | Página
Si observas el request (parte superior), podrás ver que no hemos enviado
ninguno de los parámetros requeridos, por lo que en la respuesta (parte inferior),
podrás ver que nos lanza todos los errores.

Validando el servicio updateProfile

Para validar el servicio updateProfile vamos a utilizar la misma estrategia que


el servicio signup, por lo que crearemos el archivo profileValidator.js.

1. const { body, validationResult } = require('express-validator')


2.
3. const profileValidationRules = () => {
4. return [
5. body('description').isLength({ max: 180 })
6. .withMessage('Description too long, maximum 180 characters'),
7. body('name').isLength({ min: 5 })
8. .withMessage('Name too short, minimum 5 characters'),
9. body('name').isLength({ max: 20 })
10. .withMessage('Name too long, maximum 50 characters'),
11.
12. body('banner').isLength({ min: 1 })
13. .withMessage('Banner is required'),
14. body('banner').custom(banner => {
15. if (!banner) return true
16. var result = 4 * Math.ceil((banner.length / 3))
17. if (result / 1000 > 1024) throw new Error("Banner grande")
18. return true
19. }),
20.
21. body('avatar').isLength({ min: 1 })
22. .withMessage('Banner is required'),
23. body('avatar').custom(banner => {
24. if (!banner) return true
25. var result = 4 * Math.ceil((banner.length / 3))
26. if (result / 1000 > 1024) throw new Error("Banner grande")
27. return true
28. })
29. ]
30. }
31.
32. const profileValidate = (req, res, next) => {
33. const errors = validationResult(req)
34. if (errors.isEmpty()) {
35. return next()
36. }
37.
38. const extractedErrors = errors.array().map(
39. err => { return { [err.param]: err.msg } })
40.
41. return res.status(422).json({
42. ok: false,
43. errors: extractedErrors,
44. })
45. }
46.
47. module.exports = {
48. profileValidationRules,
49. profileValidate,

Página | 550
50. }

Como podrás ver, este archivo es exactamente igual al anterior, con la única
diferencia de que los campos que validamos son otro.

También agregaremos las validaciones en el route en el archivo api.js:

1. const { profileValidationRules, profileValidate } =


2. require('./validators/profileValidator')
3.
4. router.put('/secure/profile', profileValidationRules(), profileValidate,
5. userController.updateProfile)

Validando el servicio updateProfile

De la misma forma, vamos a crear reglas de validación para el servicio de


creación de tweets, para eso, vamos a crear el archivo tweetsValidator.js:

1. const { body, validationResult } = require('express-validator')


2.
3. const tweetValidationRules = () => {
4. return [
5. body('message').isLength({ min: 1 })
6. .withMessage('Message too short, minimum 1 characters'),
7. body('message').isLength({ max: 140 })
8. .withMessage('Message too long, maximum 140 characters'),
9.
10. body('image').custom(image => {
11. if (!image) return true
12. var result = 4 * Math.ceil((image.length / 3))
13. if (result / 1000 > 1024) throw new Error("very big picture")
14. return true
15. })
16. ]
17. }
18.
19. const tweetValidate = (req, res, next) => {
20. console.log("tweetValidate =>")
21. const errors = validationResult(req)
22. if (errors.isEmpty()) {
23. return next()
24. }
25.
26. const extractedErrors = errors.array().map(
27. err => { return { [err.param]: err.msg } })
28.
29. return res.status(422).json({
30. ok: false,
31. errors: extractedErrors,
32. })
33. }
34.
35. module.exports = {
36. tweetValidationRules,
37. tweetValidate,

551 | Página
38. }

Y agregamos las reglas en el archivo api.js:

1. const { tweetValidationRules, tweetValidate } =


2. require('./validators/tweetValidator')
3.
4. router.post('/secure/tweet', tweetValidationRules(), tweetValidate,
5. tweetController.addTweet)

Solo agregaremos validaciones para los servicios más importantes o que


actualizan a la base de datos, por lo que dejaremos de tarea implementar el resto
de validaciones.

Documentando el API REST

Documentar un API correctamente es clave para su usabilidad, pues permite a


terceros entender cómo se utiliza, que servicios están disponibles, como
ejecutarlos y que información se espera como entrada y salida. Por desgracia,
gran parte de las API’s que se desarrollan, no tiene una documentación
aceptable.

Por esa razón, vamos aprender a crear una documentación simple para nuestra
API, la cual podrás utilizar más adelante para documentar cualquier otra.

Introducción al motor de plantillas Pug

Para publicar la documentación por la web, utilizaremos el motor de plantillas


Pug, el cual permite generar documentos HTML de una forma más simple y
menos verbosa. Pug que anteriormente se llamaba Jade, está basado en Haml,
aun que mejora la sintaxis de una forma sorprendente. Te invito a que entres a
la página oficial de Pug, en donde podrás encontrar toda la información y
documentación actualizada.

Esta sección busca explicar los conceptos más básicos de Pug, ya que lo
utilizaremos para desarrollar la documentación del API, sin embargo, si se quiere
profundizar en el aprendizaje de este motor de plantillas, es recomendable
dirigirse a la documentación oficial o buscar una lectura específica del tema.

Página | 552
Sintaxis básica de Pug

Básicamente, Pug nos permite crear una plantilla escrita en su propio lenguaje y
este compila la plantilla para entregarnos un HTML puro y compatible con el
navegador.

Veamos el siguiente documento HTML:

1. <!DOCTYPE html>
2. <html lang="es">
3. <head>
4. <title>Pug</title>
5. <script type="text/javascript">
6. foo = true;
7. bar = function () {};
8. if (foo) {
9. bar(1 + 5)
10. }
11. </script>
12. </head>
13. <body>
14. <h1>Pug - node template engine</h1>
15. <div id="container" class="col">
16. <p>You are amazing</p>
17. <p>Jade is a terse and simple.</p>
18. </div>
19. </body>
20. </html>

Este mismo documento que acabamos de ver se puede simplificar con Pug, de
tal forma que el siguiente documento da como resultado el mismo documento
HTML que acabo de ver:

1. doctype html
2. html(lang='es')
3. head
4. title Pug
5. script(type='text/javascript').
6. foo = true;
7. bar = function () {};
8. if (foo) {
9. bar(1 + 5)
10. }
11. body
12. h1 Pug - node template engine
13. #container.col
14. p You are amazing
15. p Jade is a terse and simple.

Solo a simple vista, podemos ver una reducción considerable de líneas (20 vs
15), es decir, nos hemos ahorrado una cuarta parte y este rango incrementa con
documentos más grandes.

La otra gran diferencia que podemos apreciar, es que no tenemos etiquetas de


apertura y cierre, es su lugar solo definimos el nombre de la etiqueta inicial, pero
sin los símbolos <>.

553 | Página
Pug utiliza los “tabs” o espacios para identificar que elemento va dentro de otro,
ya que no cuenta con una etiqueta de apertura y cierre, es por ello, que es
sumamente importante respetar los tabuladores. Por ejemplo, Pug sabe que la
etiqueta head y body van dentro de html, debido a que estas dos tiene un tab más
que html. De la misma forma, Pug sabe que title va dentro de head por que
title tiene un tab más que titile.

Clases de estilo

Otra de las ventajas que ofrece Pug, es la forma en que no permite definir las
clases de estilo, pues solo tenemos que agregar un punto (.) antes de cada clase
de estilo. Veamos el siguiente ejemplo:

1. div.myclass.myclass2

Este ejemplo es equivalente a:

1. <div class="myclass myclass2">


2. </div>

Solo es necesario definir el nombre de la etiqueta cuando requerimos una en


especial, de lo contrario, podemos hacerlo de la siguiente manera:

1. .myclass.myclass2

Cuando un elemento empieza con punto (.). Pug asume que es un div, por lo
que el resultado anterior es el mismo que si iniciáramos con div.

Establecer un ID a un elemento

También podemos agregarle un ID a nuestros elementos:

1. .myclass.myclass2#myID

Solo es necesario poner el símbolo # y luego el ID que le queremos poner al


elemento. El resultado es el siguiente:

1. <div id="myID" class="myclass myclass2">


2. </div>

Página | 554
Por otra parte, si lo que buscamos es agregarle texto a un elemento, solo
tenemos que agregar un espacio en blanco y agregar el texto:

1. p.myclass.myclass2#myID Hello World!

El resultado:

1. <p id="myID" class="myclass myclass2">Hello World!</p>

Definir atributos a un elemento

Pug permite definir los atributos de varias formas, pero nos centraremos en las
dos principales formas. La primera y más utilizada es definir todas las
propiedades en línea, en donde cada atributo es colocado uno enseguida del otro,
pero separados con una coma y todos dentro de un par de paréntesis.

1. link(rel='stylesheet', href='/styles.css')

La otra forma, es definir un atributo por línea:

1. link(
2. rel='stylesheet'
3. href='/styles.css'
4. )

El resultado en los dos casos es el mismo:

1. <link rel="stylesheet" href="/styles.css">

Tipo de código

Pug no solo permite agregar fácilmente etiquetas HTML, si no que permite hacer
paginas dinámicas mediante la inclusión de fragmentos de código JavaScript, con
los cuales es posible agregar variables, ciclos, condiciones, etc.

Existen 3 formas de agregar JavaScript, las cuales son: Unbuffered, Buffered, y


Unescaped Buffered, las cuales analizaremos a continuación.

Unbuffered Code

Son fragmentos de código que no se escriben directamente en la salida, en


su lugar, son procesados como bloque JavaScript. Estos bloques inician con un

555 | Página
guion alto (-). Son utilizados para agregar estructuras de control o definir
variables. Ejemplo:

Pug HTML Output


1. - for (var x = 0; x < 3; x++) 1. <li>item</li>
2. li item 2. <li>item</li>
3. <li>item</li>

Como resultado tenemos un ciclo for que se ejecuta en 3 ocasiones, y en cada


iteración genera un elemento <li> con el texto ítem.

Buffered code

Son fragmentos de código que son evaluadas y el resultado es enviado al


documento HTML de salida. Estos fragmentos inician con el símbolo igual (=).

Pug HTML Output


1. p = 'Hello ' + 'world' 1. <p>Hello world</p>

Unescaped Buffered

Funciona exactamente igual que el anterior, con la diferencia de que el resultado


de la evaluación puede contener elementos HTML. El bloque inicia con el
símbolo (!=).

Pug HTML Output


p!= 'This code is' + ' <strong>not</stron <p>This code is <strong>not</strong> es
g> escaped!' caped!</p>

Iteraciones

Pug soporta dos tipos de iteraciones, each y while, las cuales funcionan
exactamente igual que en cualquier lenguaje de programación.

Each

El iterador each no requiere definirse dentro de un bloque unbuffered.

Pug HTML Output


1. ul 4. <ul>
2. each val in [1, 2, 3] 5. <li>1</li>
3. li= val 6. <li>2</li>
7. <li>3</li>
8. </ul>

Página | 556
Como resultado tenemos la iteración del arreglo definido en el mismo ciclo, el
cual consta te de 3 elementos. El valor de cada iteración se guarda en la variable
val, que luego es mostrada en pantalla utilizando un bloque buffered (=).

While

El ciclo while también funciona como en cualquier otro lenguaje de programación

Pug HTML Output


1. - var n = 0; 5. <ul>
2. ul 6. <li>0</li>
3. while n < 4 7. <li>1</li>
4. li= n++ 8. <li>2</li>
9. <li>3</li>
10. </ul>

Para generar el contador, hemos definido una variable en un bloque unbuffered,


luego realizamos el while con 4 iteraciones, mostrando el valor de la variable.

API home

El API Home o página de bienviva, debe de ser una página sencilla alojada en el
home de la URL del API, en este caso sería api.localhost:8080. En esta página se
aconsejable brindar un mensaje al usuario para que sepa que está en el API.
También se aconseja mostrar los términos de uso y una liga a la documentación
de los servicios disponibles.

Esta página ya la hemos creado en esta misma sección, pero no habíamos


analizado cómo funcionaba, la cual se ve de la siguiente manera:

Fig. 160 - API Home

557 | Página
Si regresamos al archivo api.js, podremos ver que hemos definido un router
para escuchar en la raíz del subdominio:

1. router.get('/', function(req, res) {


2. res.send(pug.renderFile(__dirname + '/../public/apidoc/api-index.pug'))
3. })

Pug nos proporciona el método renderFile, el cual sirve para compilar la plantilla
y darnos un documento HTML como respuesta. Puede recibir básicamente dos
parámetros, uno de la URL a la plantilla y el segundo es un objeto que sirve como
parámetros para la plantilla, aunque en este caso, solo utilizamos un parámetro.

Dicho esto, podríamos resumir que cuando el home (/) del api sea ejecutado,
Pug abrirá la plantilla api-index.pug y nos retornará el HTML de la página. Ahora
bien, seguramente te estarás preguntando como desarrollamos la plantilla.

1. doctype html
2. html
3. head
4. link(href='https://fonts.googleapis.com/css?family=Roboto', rel='stylesheet')
5. link(
6. rel='stylesheet'
7. href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css'
8. integrity='sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u'
9. crossorigin='anonymous'
10. )
11. link(rel='stylesheet', href='/public/apidoc/api-styles.css')
12. body
13. .api-alert
14. p.title Mini Twitter API REST
15. p.body
16. | Esta API es provista como parte de la aplicación Mini Twitter,
17. | la cual es parte del libro
18. a(href='#')
19. strong "Desarrollo de aplicaciones reactivas con React, NodeJS y MongoDB"
20. | , por lo que su uso es únicamente con fines educativos y por
21. | ningún motivo deberá ser empleada para aplicaciones productivas.
22. .footer
23. button.btn.btn-warning(data-toggle='modal', data-target='#myModal')
24. | Terminos de uso
25. a.btn.btn-primary(href='/catalog') Ver documentación
26.
27. #myModal.modal.fade(
28. tabindex='-1', role='dialog', aria-labelledby='myModalLabel',
29. aria-hidden='true')
30. .modal-dialog
31. .modal-content
32. .modal-header
33. button.close(type='button', data-dismiss='modal',
34. aria-hidden='true') ×
35. h4#myModalLabel.modal-title Terminos de uso
36. .modal-body
37. p El API de Mini Twitter es provista por Oscar Blancarte, autor del libro
38. strong "Desarrollo de aplicaciones reactivas con React, NodeJs y MongoDB"
39. | con fines exclusivamente educativos.
40. p Esta API es provista
41. strong "tal cual"
42. | esta, y el autor se deslizanda de cualquier problema o falla

Página | 558
43. | resultante de su uso. En ningún momento el autor será responsable
44. | por ningún daño directo o indirecto por la pérdida o publicación
45. | de información sensible.
46. strong El usuario es el único responsable por el uso y la información
47. | que este pública.
48. .modal-footer
49. button.btn.btn-primary(type='button', data-dismiss='modal') Cerrar
50.
51.
52. script(src='https://code.jquery.com/jquery.js')
53. script(src='//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js')

Analicemos primero que nada la estructura básica del documento. En la línea 1,


definimos el meta doctype, para que el navegador entienda que estamos
utilizando HTML5. En las líneas 2, 3 y 12, definimos las etiquetas <html>, <head>
y <body>.

Dentro del head importamos la fuente Robo, el framework Bootstrap e


importamos nuestras propias clases de estilo (api-styles.css).

El body está compuesto básicamente por dos secciones, el panel que podemos
ver en pantalla y un diálogo que muestra los términos de uso. La primera sección
(líneas 13 a 25) corresponde a lo que ve el usuario en pantalla, el cual contiene
un título (línea 14), el cuerpo o mensaje (líneas 15 a 21) y el footer, que es
donde ponemos los botones (líneas 22 a 25). Observemos que el botón para los
términos de uso (línea 23), contiene los atributos data-toggle y data-target, los
cuales son provistos por Bootstrap para crear paneles modales, el primer atributo
es el tipo de pantalla que queremos, en este caso modal y el segundo es el ID
del elemento que vamos a mostrar cuando se presione el botón. El otro botón
nos manda a /catalog, en donde estarán listados todos los servicios disponibles.

La segunda sección del archivo es el panel modal, el cual contiene el mismo ID


(myModal) que pusimos en el botón. Este panel contiene básicamente lo mismo,
un header, un body y un footer, con la diferencia, de que el botón del footer cierra
el modal.

Service catalog

Como parte del API, siempre deberemos tener una sección donde listemos los
servicios disponibles. Los servicios pueden ser mostrados por categorías si son
muchos o listar todos en una misma sección, si el número de servicios es
reducido, como es nuestro caso, una solo pantalla servirá para mostrarlos.

Antes de empezar a desarrollar esta sección, veamos el resultado final:

559 | Página
Fig. 161 - API Catalog.

Como podemos observar, el catálogo es simplemente una lista con los servicios
disponibles con la información básica:

Fig. 162 - Información de cada servicio.

Cada servicio contará con la siguiente información:

• Nombre: Nombre descriptivo del servicio


• Descripción: Una breve nota acerca de lo que hace el servicio
• URL: Dirección en la que responde el servicio
• Método: Método o verb en el cual responde el servicio
• Autenticación: Indicador que le dice al usuario si el servicio requiere
autenticación.

Página | 560
Lo primero que crearemos será, el archivo api-catalog.pug en el path
/public/apidoc, el cual se ve de la siguiente manera:

1. doctype html
2. html(lang="es")
3. head
4. title Mini Twitter API REST
5. link(href='https://fonts.googleapis.com/css?family=Roboto', rel='stylesheet')
6. link(rel='stylesheet'
7. href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css'
8. integrity='sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u'
9. crossorigin='anonymous')
10. link(rel='stylesheet', href='/public/apidoc/api-styles.css')
11. body
12. .container
13. .row
14. .col-xs-12
15. .method-templete
16. .list-group
17. each item in services
18. a.list-group-item(href=item.apiURLPage)
19. if item.secure
20. span.badge secure
21. h4.list-group-item-heading #{item.title}
22. span.label.label-success #{item.method}
23. span.label.label-primary #{item.url}
24. p.list-group-item-text #{item.desc}
25. script(src='https://code.jquery.com/jquery.js')
26. script(src='//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js')

Esta página recibe como parámetro un objeto llamado “services”, que no es más
que un array con los datos de todos los servicios disponibles, el cual vamos a
iterar en la línea 17 para representar cada servicio. Vamos a utilizar la variable
“ítem” para guardar los datos de cada servicio. Los atributos disponibles para
cada servicio son:

• apiURLPage: URL en la que podemos encontrar la documentación


del servicio
• title: nombre del servicio
• method: método en el que responde
• url: URL en la que responde
• desc: descripción del servicio
• secure: booleano que indica si el servicio requiere un token de
autenticación
El segundo será crear un router que escuche en el path /catalog en el archivo
api.js:

1. router.get('/', function(req, res) {


2. res.send(pug.renderFile(__dirname + '/../public/apidoc/api-index.pug'))
3. })
4.
5. router.get('/catalog', function(req, res){
6. const meta = require('../public/apidoc/meta/catalog.js')
7. res.send(
8. pug.renderFile(__dirname + '/../public/apidoc/api-catalog.pug', meta))
9. })

561 | Página
Como podrás ver, este router muestra el archivo api-catalog.pug y le manda
como parámetro el objeto meta, el cual es un archivo que contiene la lista de
servicios, el cual vamos a explicar.

Ahora bien, esta página requiere del archivo catalog.js, el cual deberemos de
crear en el path /public/apidoc/meta y se verá de la siguiente manera:

1. const loginPost = require('./login-post.js')


2. const reloginGet = require('./relogin-get.js')
3. const signupPost = require('./signup-post.js')
4. const profileGet = require('./profile-get.js')
5. const profilePut = require('./profile-put.js')
6. const followersGet = require('./followers-get.js')
7. const followingsGet = require('./followings-get.js')
8. const suggestedUsersGet = require('./suggestedUsers-get.js')
9. const followPost = require('./follow-post.js')
10. const usernameValidateGet = require('./usernamevalidate-get.js')
11. const tweetsGet = require('./tweets-get.js')
12. const tweetsusernameGet = require('./tweetsusername-get.js')
13. const addtweetsPost = require('./addtweets-post.js')
14. const tweetsdetailsGet = require('./tweetsdetails-get.js')
15. const tweetlikePost = require('./tweetlike-post.js')
16.
17. module.exports = {
18. services: [
19. loginPost,
20. reloginGet,
21. signupPost,
22. profileGet,
23. profilePut,
24. followersGet,
25. followingsGet,
26. suggestedUsersGet,
27. followPost,
28. usernameValidateGet,
29. tweetsGet,
30. tweetsusernameGet,
31. addtweetsPost,
32. tweetsdetailsGet,
33. tweetlikePost
34. ]
35. }

Este archivo solo exporta un objeto llamado services, el cual es creado a partir
de una serie de objetos, es decir un archivo por servicio.

La idea es la siguiente, vamos a crear un archivo independiente que contenga los


metadatos de un servicio, de esta forma, vamos a tener un archivo por servicio.

Vamos a explicar la estructura que deberá tener cada archivo, la cual es la misma
para todos, pero la información cambia:

1. module.exports = {
2. apiURLPage: "/catalog/addtweets-post",
3. title:"Creación de nuevo Tweet",
4. desc:"Servico utilizado para la creación de un nuevo Tweet",

Página | 562
5. secure: true,
6. url: "/secure/tweets",
7. method:"POST",
8. urlParams: [],
9. requestFormat:"{}",
10. dataParams: "{}",
11. successResponse: "{}",
12. errorResponse:"{}"
13. }

Analicemos los campos que tiene:

• apiURLPage: URL en la que encontramos la documentación del servicio


• title: nombre del servicio
• desc: descripción del servicio
• secure: booleano que indica si el servicio tiene seguridad
• url: URL donde responde el servicio.
• method: método en el que responde el servicio
• urlParams: URL en la que responde el servicio
• requestFormat: formato del request
• dataParams: Un JSON con un ejemplo del request
• successResponse: Ejemplo de una respuesta
• errorResponse: ejemplo de una respuesta con error.

Debido a que son un total de 15 archivos y que son bastante repetitivos, vamos
a limitarnos a mostrar solo uno, con la única finalidad darnos una idea de cómo
quedaría un archivo terminado. El resto de archivos lo podemos encontrar en el
repositorio de GitHub.

El siguiente archivo corresponde al servicio “Consulta de seguidores”:

1. module.exports = {
2. apiURLPage: "/catalog/followers-get",
3. title:"Consulta de seguidores de un usuario determinado",
4. desc:"Mediante este servico es posible recuperar los seguidores de un usuario d
eterminado por el url param 'username'",
5. secure: false,
6. url: "/followers/:username",
7. method:"GET",
8. urlParams: [
9. {
10. name: "username",
11. desc: "Nombre de usuario",
12. require: true
13. }
14. ],
15. requestFormat:"",
16. dataParams: "",
17. successResponse: "{\r\n \"ok\":true,\r\n \"body\":[\r\n {\r\n
\"_id\":\"5938bdd8a4df2379ccabc1aa\",\r\n \"userName\":\"emmanuel\",\r\n

563 | Página
\"name\":\"Emmauel Lopez\",\r\n \"description\":\"Nuevo en Twitte
r\",\r\n \"avatar\":\"<Base 64 Image>\",\r\n \"banner\":\"<Base 6
4 Image>\"\r\n },\r\n {\r\n \"_id\":\"5938bdd8a4df2379ccabc1aa\
",\r\n \"userName\":\"carlos\",\r\n \"name\":\"Carlos Hernandez\"
,\r\n \"description\":\"Nuevo en Twitter\",\r\n \"avatar\":\"<Bas
e 64 Image>\",\r\n \"banner\":\"<Base 64 Image>\"\r\n }\r\n ]\r\n}
",
18. errorResponse:"{\r\n \"ok\": false,\r\n \"message\": \"No existe el usuario
\"\r\n}"
19. }

Podemos ver que la documentación de este servicio lo vamos a encontrar en


“/catalog/followers-post”, vemos el nombre, la descripción, un request y
response de ejemplo, los cuales deberá estar correctamente especificado para
ser representados como un String. También tenemos una lista de URL params,
en el cual podemos indicar el nombre, la descripción y si es requerido o no.

Al final, deberemos tener una estructura igual que la siguiente:

Fig. 163 - Listado de archivos

Service documentation

La última página que nos faltaría, es donde podemos ver toda la documentación
del servicio, la cual se verá de la siguiente manera:

Página | 564
Fig. 164 - Documentación de un servicio.

Para crear esta nueva sección, deberemos crear el archivo api-method.pug, el


cual deberá estar en el siguiente path /public/apidoc.

1. doctype html
2. html(lang="es")
3. head
4. title Mini Twitter API REST
5. link(rel='stylesheet',
6. href='//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css')
7. script(
8. src='//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js')
9. script.
10. hljs.initHighlightingOnLoad();
11. link(href='https://fonts.googleapis.com/css?family=Roboto', rel='stylesheet')
12. link(rel='stylesheet',
13. href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css',
14. integrity='sha384-
BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u',
15. crossorigin='anonymous')

565 | Página
16. link(rel='stylesheet', href='/public/apidoc/api-styles.css')
17. body
18. .container
19. .row
20. .col-xs-12
21. .method-templete
22. if secure
23. span.secure-icon &#128274;
24. .form-group
25. label(for='name') Nombre
26. output#name #{title}
27. .form-group
28. label(for='desc') Descripción
29. output#desc #{desc}
30. .form-group
31. label(for='url') URL
32. output#url #{url}
33. .form-group
34. label(for='method') Method
35. output#method #{method}
36. .form-group
37. label(for='urlParams') URL Params
38. ul.list-group1
39. each item in urlParams
40. li.list-group-item
41. strong= item.name + ': '
42. span= item.desc
43. if item.require
44. span.badge.badge-warning.badge-pill requerido
45. else
46. li.list-group-item Sin parámetros
47. .form-group
48. label(for='requestFormat') Formato del request
49. <pre><code class="json">#{requestFormat}</code></pre>
50. .form-group
51. label(for='dataParams') Request
52. <pre><code class="json">#{dataParams}</code></pre>
53. .form-group
54. label(for='successResponse') Respuesta OK
55. <pre><code class="json">#{successResponse}</code></pre>
56. .form-group
57. label(for='errorResponse') Respuesta Error
58. <pre><code class="json">#{errorResponse}</code></pre>
59. if secure
60. .alert.alert-danger
61. strong &#128274; Servicio con seguridad
62. p Este es un servicio con seguridad habilitada, para poder ser
63. | ejecutado, es requerido que se le envíe el
64. strong token
65. | dentro del header
66. strong authorization
67. | de lo contrario, el servicio negará el acceso.

Mediante este archivo, creamos un simple formulario, el cual mostrará cada uno
de los valores contenidos en los objetos JSON que acabamos de analizar.

De las líneas 24 a 35 mostramos los campos title, desc, url y method, después
de esto, realizamos un each (línea 39) para cada URL param definido.

Para los campos requestFormat, dataParams, successResponse y errorResponse,


utilizamos la librería highlight, la cual permite darle formato al texto para verse
como código. Es por ese motivo que agregamos los valores dentro de un <pre>

Página | 566
con un atributo correspondiente al tipo de documento (class="json"). Cuando
la librería se active (línea 10), reconocerá las clases de estilo y dará formato
automáticamente.

Finalmente, en la línea 59 validamos si el servicio es seguro para mostrar una


leyenda que advierta de la seguridad del servicio.

Ya con este último archivo hemos completado la documentación del servicio y ya


solo nos queda navegar por todas las secciones y comprobar los resultados.

Algunas observaciones o mejoras al API

Esta sección la defino para nombrar todas las mejores que podríamos agregar al
API, las cuales por practicidad y no complicar mucho más el API, he decidido
darles una solución “rápida”, la cual puede no ser la mejor forma de implementar.
Todas las mejoras aquí planteadas las puedes tomar como ejercicios para
mejorar tus habilidades en el desarrollo de API’s.

Aprovisionamiento de imágenes

Sin duda, el aprovisionamiento de imágenes es por mucho, un de las áreas de


mejor más importantes, debido a que actualmente el API sirve las imágenes en
formato base 64 dentro de una propiedad del Tweet o del Perfil.

Esta estrategia puede resultar cómoda y fácil de implementar, pues solo


agregamos el base 64 al Schema de Mongoose y guardamos. Cuando
consultamos la imagen también es muy simple, ya que simplemente regresamos
el base 64 al navegador y él sabrá como mostrarla.

El problema con esta estrategia es que al retornar la imagen como base 64 dentro
de un objeto JSON, impide que el navegador utilice el cache para no cargar una
imagen que ya cargo antes. Por ejemplo, la foto del perfil:

567 | Página
Fig. 165 - Foto de perfil en base64

En la imagen anterior, vemos al usuario posicionado dentro del detalle de un


Tweet y podemos ver en este momento, 5 veces la foto del perfil. Como la imagen
se carga mediante base 64, el navegador carga la misma imagen cada vez que
la requiere.

La solución a este problema, es siempre regresar un URL de donde podamos


recuperar la imagen, de esta forma el navegador podrá determinar que un
recurso ya lo ha cargado en el pasado y simplemente lo cargará del cache. Un
ejemplo de esto lo podemos ver en el siguiente objeto Profile:

1. {
2. "ok": true,
3. "body": {
4. "_id": "593616dc3f66bd6ac4596328",
5. "name": "Name",
6. "description": "Descripción",
7. "userName": "user name",
8. "avatar": "http://api.site.com/profile/avatar/593616dc3f66bd6ac4596328",
9. "banner": "http://api.site.com/profile/banner/593616dc3f66bd6ac4596328",
10. "tweetCount": 44,
11. "following": 0,
12. "followers": 3,
13. "follow": false
14. }
15. }

Podemos apreciar los campos avatar y banner que en lugar de tener una imagen
en base 64, tiene un URL que lleva a donde está la imagen.

Tenemos dos formas de implementar esto, la primera es utiliza un servicio de


almacenamiento externo como Amazon S3, Google Storage, Dropbox, etc. que
nos permita subir la imagen a la nube y luego simplemente nos proporcione un
URL en donde podamos recuperar la imagen, entonces en MongoDB guardamos
esta URL en lugar del base 64.

Página | 568
La segunda forma es seguir guardado el base 64 dentro de Mongo, pero
proporcionar un servicio del API que recupere la imagen por URL. El servicio
podrá tener URL params para saber qué imagen necesitamos y de que
usuario/tweet, por ejemplo /resources/:userId/avatar y
/resources/:userId/banner, con estos URL params podemos recuperar el Perfl
solicitado y hora si regresar la imagen en base64. Si bien la imagen la seguimos
mandando en base 64, el navegador es lo suficiente inteligente para saber que
un recurso solicitado por URL ya lo tiene en cache y evitar solicitarlo nuevamente.

Guardar la configuración en base de datos

En este momento, toda la configuración relacionada con el API y la aplicación, la


hemos guardado en el archivo config.js, sin embargo, en la práctica, esto no es
una buena solución, ya que cada cambio, requiere un reinicio de la aplicación.

Para solucionar este problema, podríamos guardar toda la configuración en la


base de datos, para lo cual, podríamos crear una colección para guardar la
configuración. Con la única excepción de las propiedades para la conexión a la
base de datos por obvias razones.

Documentar el API por base de datos

Como acabamos de ver, es necesario crear un archivo JavaScript para cada uno
de los servicios que tenemos, lo cual podría ser bastante complicado de
administrar, para ello, podríamos crear una nueva colección para guardar la
documentación de los servicios y simplemente recuperarla cuando sea necesaria.

569 | Página
Resumen

Finalmente, hemos aprendido a crear un API completamente desde cero,


aplicando toda la teoría que hemos venido aprendiendo sobre NodeJS y Express.
También hemos aprendido a utilizar subdominios y como estos influyen en la
creación de un API exitosa, junto con los subdominios, hemos analizado que es
CORS y la forma que tenemos para habilitar el consumo de recursos de un
dominio diferente al de la aplicación.

Junto con el API, hemos creado una página especial para documentar cada uno
de los servicios que ofrece nuestra API, utilizando para ello, el motor de plantillas
Pug.

En este momento, ya estamos solo a un paso de finalizar nuestro proyecto, y


solo nos falta lanzar nuestro proyecto a producción, lo cual, es lo que estaremos
abordando en el siguiente capítulo.

Página | 570
Producción
Capítulo 18

Una de las cosas más importantes cuando desarrollamos una aplicación, es


pasarla a producción, pues será cuando finalmente la aplicación estará expuesta
al público en general. En esta etapa, debemos optimizar la página para que
consuma me menor cantidad de recursos y el usuario experimente un menor
tiempo de carga. Por otra parte, definir un canal de comunicación segura es clave
para proteger los datos del cliente y ganar una mayor confianza por parte del
usuario. Finalmente, tener una arquitectura tolerante a fallas, es clave para
garantizar la disponibilidad de la aplicación a lo largo del tiempo.

En este capítulo analizaremos técnicas para que el pase a producción sea lo más
simple posible, pero también implementar todas estas prácticas que harán de
nuestro sitio más rápido, seguro y robusto.

Producción vs desarrollo

Si ya tienes tiempo en el mundo del desarrollo de software, sabrás la diferencia


que existe entre un ambiente de desarrollo y otro de producción, sin embargo,
me gustaría abordar el tema para que quede claro para todos.

Primero que nada, definamos que es un ambiente de producción y uno de


desarrollo:

• Desarrollo: es un ambiente de pruebas en donde el desarrollador tiene


carta abierta para realizar casi cualquier maniobra, como actualizar los
sistemas, borrar o insertar información, instalar o desinstalar cosas, etc.
Este ambiente debe de ser utilizado únicamente por los programadores o
personas de TI que siguen construyendo o solucionando issues.
• Producción: es el ambiente utilizado por el usuario final, el cual no
puede ser manipulado con fines de pruebas o incluso actualizado sin un
proceso de calidad previo. Este ambiente no debe de ser manipulado por
desarrolladores o persona de TI sin una justificación, como puede ser,
solucionar un Issue o actualizar la versión para agregar nuevos features.

571 | Página
Desde luego que en las grandes empresas hay más ambientes que solo desarrollo
y producción, como el ambiente de pruebas (para testing), QA (pruebas de UAT)
y Stress (pruebas de carga) y quizás algunos ambientes más. Sin embargo,
nosotros abordaremos solo desarrollo y producción.

Un error muy común es creer que el ambiente de producción, es el ambiente que


apunta la base de datos de producción, o sea a la información de verdad, y es
cierto, pero no es solo eso. Un ambiente de producción además de apuntar a una
base de datos real, debe contar con configuraciones especiales que ayuden a su
desempeño, seguridad y alta disponibilidad. Es por esta razón que en este
capítulo nos centraremos exclusivamente en preparar la aplicación para correr
en un ambiente productivo.

Alta disponibilidad

La alta disponibilidad es súper esencial para cualquier aplicación en producción,


pues nos garantiza el funcionamiento de nuestra aplicación ante una falla. Lograr
que una aplicación sea infalible es imposible, pero al menos podemos
implementar técnicas que reduzcan ese margen al mínimo.

Cluster

Una de las principales técnicas de alta disponibilidad son los cluster, los cuales
son un conjunto de servidores trabajando como uno solo, de tal forma que, si
uno falla, los demás pueden seguir operando.

Podemos implementar un cluster de dos formas, la primera es tener varias


instancias de la aplicación corriendo en el mismo servidor físico y la segunda es
tener varias instancias de la aplicación corriendo en múltiples servidores físicos.

Desde luego que tener múltiples servidores físicos es lo mejor, pues si todo un
equipo falla, otros podrán seguir operando. sin embargo, esto está fuera del
alcance de este libro, pues para lograr eso es necesario más configuraciones,
herramientas y equipos adicionales que no tenemos en un ambiente local.

Por otra parte, tenemos el cluster en un solo equipo, el cual podemos


implementar fácilmente. Esta técnica consiste en tener varias instancias del
servidor corriendo en el mismo equipo, por lo que, si uno se cae por alguna razón,
el cluster lo apagará e iniciará una nueva instancia, de tal forma que siempre
exista un número determinado de instancias del servidor corriendo.

Otra de las cosas a tomar en cuenta es que, NodeJS se ejecuta en un solo hilo,
por lo que en procesadores multicores, no se aprovechará su potencial, por este
motivo, el cluster es una buena opción para lanzar múltiples procesos y así
mejorar el performance ante la carga de trabajo.

Página | 572
Lo primero que tenemos que hacer es instalar la librería cluster, mediante el
siguiente comando:

npm install --save-dev cluster@0.7.7

El segundo paso será agregar todo el inicio del server dentro de una función, la
cual podremos reutilizar para crear las diferentes instancias del servidor. Para
ello, actualizaremos el archivo server.js para dejarlo de la siguiente manera:

1. var express = require('express');


2. var app = express();
3. var bodyParser = require("body-parser")
4. var path = require('path')
5. var webpack = require('webpack')
6. var config = require('./webpack.config')
7. var compiler = webpack(config)
8. var mongoose = require('mongoose')
9. var configuration = require('./serverConfig')
10. var vhost = require('vhost')
11. var api = require('./api/api')
12.
13. function startServer() {
14. var opts = {
15. appname: "Mini Twitter",
16. poolSize: 10,
17. autoIndex: false,
18. bufferMaxEntries: 0,
19. loggerLevel: "error", //error / warn / info / debug
20. keepAlive: 120,
21. validateOptions: true,
22. useNewUrlParser: true,
23. useUnifiedTopology: true,
24. useFindAndModify: false
25. }
26.
27. let connectString = configuration.mongodb.connectionString
28. mongoose.connect(connectString, opts, function (err) {
29. if (err) throw err;
30. console.log("==> Conexión establecida con MongoDB");
31. })
32.
33. app.use('*', require('cors')());
34.
35. app.use('/public', express.static(__dirname + '/public'))
36. app.use(bodyParser.urlencoded({ extended: false }))
37. app.use(bodyParser.json({ limit: '10mb' }))
38.
39. if (process.env.NODE_ENV !== 'production') {
40. app.use(require('webpack-dev-middleware')(compiler, {
41. noInfo: true,
42. publicPath: config.output.publicPath
43. }))
44. }
45.
46. app.use(vhost('api.*', api));
47. app.use(vhost('minitwitterapi.reactiveprogramming.io', api));
48.
49. app.get('/*', function (req, res) {
50. res.sendFile(path.join(__dirname, 'index.html'))
51. });

573 | Página
52.
53. app.listen(configuration.server.port, function () {
54. console.log('Example app listening on port 8080!')
55. });
56. }
57.
58. if (require.main === module) {
59. startServer();
60. } else {
61. module.exports = startServer;
62. }

De las líneas 13 a 56 hemos agregado todo el inicio del server dentro de la función
startServer, después en las líneas 58 a 62 definimos la forma en que se debe de
ejecutar el server. Cuando un archivo es ejecutado directamente mediante el
comando node, se establece el valor require.main = ‘module’, por lo que si
ejecutamos esta clase directamente (node server.js) simplemente se ejecutará
la función de inicio del server. Por otra parte, si el archivo es ejecutado por otro
archivo, entonces solamente exportamos la función startServer para ser
utilizada por fuera.

Esto último es importante debido a que si ejecutamos el servidor en modo


desarrollo, lo que pasará es que se ejecutará la línea 59, es decir, el server
iniciará sin más, sin embargo, cuando corramos la aplicación en modo productivo,
el server no arrancará de inmediato, si no que en su lugar, se ejecutará la línea
61, exportando la función startServer para ser ejecutada N veces por el cluster
que analizaremos a continuación.

Ahora, crearemos un nuevo archivo llamado cluster.js en la raíz del proyecto


(/), el cual se verá de la siguiente forma:

1. var cluster = require('cluster');


2.
3. console.log("ENV ==> " , process.env.NODE_ENV)
4.
5. if(cluster.isMaster){
6. require('os').cpus().forEach(function(){
7. startWorker()
8. });
9.
10. cluster.on('disconnect', function(worker){
11. console.log(
12. 'CLUSTER: Worker %d disconnected from the cluster.', worker.id)
13. });
14.
15. cluster.on('exit', function(worker, code, signal){
16. console.log(
17. 'CLUSTER: Worker %d died with exit code %d (%s)',
18. worker.id, code, signal);
19. startWorker()
20. });
21.
22. } else {
23. require('./server.js')()
24. }
25.

Página | 574
26. function startWorker() {
27. var worker = cluster.fork()
28. console.log('CLUSTER: Worker %d started', worker.id)
29. }

La idea es que ahora iniciemos el servidor mediante este archivo, en lugar del
archivo server.js. Cuando este archivo es ejecutado, automáticamente se
convierte en el proceso master (línea 5) lo que indica que la expresión
cluster.isMaster retornará true. Esto hará que se ejecute la función startWorker
(línea 7) por cada CPU que tenga el equipo, es decir, si nuestra máquina es de 8
núcleos, se levantarán 8 instancias del servidor, si nuestro procesador es de 6
núcleos, entonces 6 instancias se levantarán y así sucesivamente.

Lo siguiente, es registrar un evento para realizar una acción cuando un server se


desconecte (línea 10), en ese caso, solo mandamos un mensaje en pantalla para
saber lo que paso.

Por otra parte, registramos el evento “exit” (línea 15), el cual nos permitirá
realizar una acción cuando un servidor se apague, en tal caso, mandamos un
mensaje en pantalla y volvemos a ejecutar la función startWorker para reponer
la instancia del server que fallo.

Ahora bien. La función startWorker tiene como finalidad ejecutar el método fork,
el cual inicia un nuevo proceso, ejecutando de nuevo este archivo pero con una
diferencia, y es que ahora cluster.isMaster será igual a false, lo que hará que
se ejecute la función startServer del archivo server.js.

Una vez que hemos creado el cluster, solo restaría ejecutarlo para comprobar su
funcionamiento, por lo tanto, comenzaremos con apagar el server y volvemos a
iniciar el server mediante node cluster.js:

575 | Página
Fig. 166 - Iniciando un cluster.js

Tras ejecutar el cluster, podemos observar cómo se han iniciado 8 procesos, pues
tengo un equipo con 8 cpus. Si vamos al administrador de tareas, podremos ver
varios procesos, los cuales corresponden a cada instancia del cluster + los
procesos propios del cluster:

Fig. 167 - Cluster process.

Una vez en los procesos, procedemos con terminar alguno, tiendo cuidado de no
matar el proceso del cluster, el cual podemos distinguir porque es el que menos
memoria consume:

Página | 576
Fig. 168 - Cluster recovery.

Como podemos ver, el cluster ha detectado que el nodo 4 ha muerto y ha iniciado


el worker 9. El cluster es muy importante en producción, porque existe ocasiones
que un bug, un flujo no controlado correctamente, problemas con la memoria o
saturación del servidor, da como resultado que el proceso del servidor se caiga,
lo que ocasionaría que la aplicación ya no esté disponible, sin embargo, con el
cluster, lo que pasaría es que ante cualquier falla, una nueva instancia del
servidor se ejecutará para reemplazar la anterior, logrando con esta forma, que
siempre exista un proceso activo capaz de atender las peticiones del usuario.

Puertos

Internet trabaja exclusivamente con los puertos 80 para HTTP y 443 para HTTPS,
por lo que configurar nuestra aplicación para trabajar en estos puertos es
indispensable, de lo contrario, los usuarios no podrán acceder a nuestra página
con solo poner el dominio, si no que tendrán que adivinar el puerto en el cual
responde la aplicación.

Cuando el usuario entra a google.com, lo que hace el navegador internamente


es enviar una solicitud GET a google.com:80, por otra parte, cuando entramos
por https, por ejemplo https://google.com, el navegador realiza la petición al
puerto 443, es decir https://google.com:443.

Dado que nuestro proyecto escucha en el puerto 8080, será muy difícil que un
usuario pueda acceder a nuestra aplicación. Para solucionar esto tenemos dos
opciones, la primera y más simples es, cambiar el puerto de NodeJS al 80. La
segunda opción es crear un proxy que escuche en el puerto 80 y luego
redirecciones la llamada al nuestro servidor en el puerto configurado en NodeJS,
esto se puedo lograr con Apache, Nginx, etc.

Configurar un servidor proxy queda fuera del alcance de este libro, sin embargo,
quería mencionar la alternativa por si quieres investigar más a profundidad. Esto
nos deja únicamente con la primera opción, que es cambiar el puerto en NodeJS.
Para ello, tendremos que regresar al archivo serverConfig.js y modificar el
puerto:

1. module.exports = {

577 | Página
2. server: {
3. port: 80
4. },
5. mongodb: {
6. connectionString: "<CONNECTION_STRING>"
7. },
8. jwt: {
9. secret: "<JTW_SECRET>"
10. }
11. }

Tras realizar este simple paso y reiniciar el servidor, podremos acceder a la


aplicación con tan solo poner en el navegador “localhost” sin necesidad de
especificar el puerto.

Fig. 169 - Probando el puerto 80.

Posibles problemas con el puerto 80

Al ser un puerto estándar, el 80 es muy demandado


por muchas aplicaciones que lo pueden utilizan sin que
nos demos cuenta, por lo que, si al iniciar la aplicación
sale un error de que el puerto está siendo utilizado,
tendremos que identificar que aplicación lo utiliza y
detenerlo. También, en ocasiones nos solicita
privilegios como administrador para utilizarlo.

Comunicación segura

Proporcionar un canal seguro para que la información viaje entre el cliente y el


servidor, es ciertamente indiscutible, pues cualquier mensaje que viaje
desprotegido, puede ser fácilmente capturado y utilizado para hacer daños.
Alguna de la información que puede ser capturada son, los password, datos de

Página | 578
nuestros clientes, información sensible como datos de las tarjetas de crédito, etc.
En realidad, cualquier dato que viaje puede ser recuperado.

Por este motivo, utilizar conexiones seguras con HTTPS es indispensable, pues
protege la información encriptándola durante su viaje por internet, y una vez que
llega al destinatario, solo el cliente o el servidor sabrán como descifrarla.

Para establecer una comunicación segura por HTTPS, es necesario un certificado,


con el cual cifraremos todas las comunicaciones.

Existen dos formas de obtener un certificado, la primera es crearlo nosotros


mismo y la segunda es obtenerlo por medio de una autoridad emisora de
certificados.

Certificados emitidos por autoridades

Los certificados emitidos por autoridades de internet pueden ser comprados a


diferentes empresas u obtenidos de forma gratuita por medio de Letsencript, los
cuales recopilan información de nuestra empresa y nos pueden cobran una
cantidad para emitirlos.

Estos certificados pueden ser validados por los navegadores, lo que habilita el
candado que podemos ver en la barra de navegación:

Fig. 170 - Validación de un certificado SSL comprado.

Este tipo de certificado es considerado de confianza por el navegador, por lo


que nos permite el acceso al sitio sin ningún problema y advierte al usuario que
todas las comunicaciones con esta página son seguras, lo que le da al usuario la
confianza de introducir datos sensibles como password y tarjetas de crédito.

579 | Página
Existen varios proveedores que nos pueden vender certificados, como GoDaddy,
Comodo, Namecheap, Digicert, etc. Dado que la única diferencia que existe entre
todos los proveedores es el precio, puedes inclinarte por el que tenga el mejor
precio. En lo personal yo utilizo los certificados de Letsencript, pues pueden ser
emitidos de forma gratuita, con la única condición de que debes de tener un
dominio comprados.

Certificados ligados a los dominios

Los certificados comprados solo sirven para el dominio


que fue comprado, por lo que si lo utilizamos para un
dominio diferente, el navegador lanzara todas las
alertas y no permitirá al usuario entrar directamente.

Certificados auto firmados

Por otro lado, tenemos los certificados auto firmados, los cuales pueden ser
emitidos por quien sea, incluso, podemos crear nuestros propios certificados
sin ningún costo.

Los certificados comprados y los auto firmados tiene EXACTAMENTE el mismo


nivel se seguridad, sin embargo, tiene una enorme diferencia, y es que cuando
utilizamos un certificado que no ha sido emitido por una autoridad, el navegador
lanzará una pantalla de “PELIGRO” antes de cargar la página:

Fig. 171 - Advertencia de conexión no segura.

Página | 580
Esta pantalla provocará que los usuarios salgan corriendo del sitio y solo los más
valientes tendrán el valor de entrar.

Para que el navegador nos permite acceder a la página, nos pedirá que
agreguemos el sitio a las excepciones de seguridad, por lo que tendremos que
dar click en “Avanzado” y luego en “Añadir excepción”.

Una vez realizado esto, en navegador nos permitirá entrar al sitio, pero nos
indicará que el sitio no es seguro:

Fig. 172 - Advertencia de sitio no seguro.

A pesar de todas las advertencias, tengo que resaltar que la seguridad es


EXACTAMENTE la misma que adquirir un certificado por una autoridad, sin
embargo, el navegador advierte al usuario de que el certificado no es seguro
porque no hay nadie que avale al dueño del certificado y por ende al sitio web.

En este punto, te estarás preguntando, entonces para que pueden servir este
tipo de certificados, y la respuesta es simple, se utilizan con regularidad para
sitios de intranet o que solo lo acceder personas de confianza de la misma
empresa, las cuales saben que pueden confiar en el sitio y en el certificado. Sin
embargo, para el público en general, es totalmente desaconsejado.

Instalando un certificado en nuestro servidor

Dado que comprar un certificado y su domino correspondiente como un ejemplo


no es una opción, vamos a aprender instalando un certificado autoformado.
Aunque el procedimiento para instalar uno de paga es el mismo, solo cambia
quien lo genera y el procedimiento para obtenerlo.

581 | Página
Sea cual sea el certificado que utilicemos, al final del día, tendremos dos archivos,
un certificado y una llave, que será lo que necesitamos para agregar HTTPS a
nuestro servidor.

Para auto generar nuestro certificado, tendremos que descargar una librería
criptográfica que nos permite utilizar SSL. La más popular es OpenSSL, la cual
es OpenSource.

En Windows puedes descargarlo de:

http://gnuwin32.sourceforge.net/packages/openssl.htm

mientras que para Linux lo podemos descargar en:

https://www.openssl.org

El proceso de instalación dependerá del sistema operativo, por lo que tendremos


que seguir las instrucciones correspondientes para cada sistema operativo. Una
vez instalado, probemos lanzar el comando ssl en la línea de comandos, si el
comando no es reconocido, tendremos que agregar la dirección <install-
path>/bin al path de las variables de entorno.

Ya que OpenSSL está correctamente instalado, crearemos la carpeta cert en la


raíz del proyecto (/) y nos ubicaremos dentro de la carpeta desde la línea de
comandos, luego ejecutamos el siguiente comando:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365

Los parámetros son los siguientes:

• Req: comando para solicitar la generación de un certificado


• -x509: indica el formato del certificado
• -newkey: comando para la generación de la llave del certificado y rsa:2048
es el algoritmo de cifrado (RSA) a 2048 bits
• -keyout: parámetro para indicar donde dejar la llave y key.pem es el nombre
de la llave generada.
• -out: comando para indicar donde dejar el certificado y cert.pem es el
nombre del certificado generado.
• -days: indica el número de días que tendrá vigencia el certificado.

Tras ejecutar el comando, nos pedirá un password y una serie de datos


relacionados, con la empresa o la persona que está emitiendo el certificado:

Página | 582
Fig. 173 - Creando el certificado con OpenSSL.

Una vez creados los certificados, los podremos ver desde nuestro editor:

Fig. 174 - Certificados creados exitosamente.

El siguiente paso será modificar el archivo server.js para agregar los


certificados:

1. var express = require('express');


2. var app = express();
3. var bodyParser = require("body-parser")
4. var path = require('path')
5. var webpack = require('webpack')
6. var config = require('./webpack.config')
7. var compiler = webpack(config)
8. var mongoose = require('mongoose')
9. var configuration = require('./serverConfig')
10. var vhost = require('vhost')
11. var api = require('./api/api')
12. var fs = require('fs')
13. var http = require('http')
14. var https = require('https')
15.
16. function startServer() {
17.
18. . . .
19.
20. https.createServer({
21. key: fs.readFileSync('./certs/key.pem'),
22. cert: fs.readFileSync('./certs/cert.pem'),
23. passphrase: '1234'
24. }, app).listen(configuration.server.securePort, () => {

583 | Página
25. console.log(`Example app listening on port
26. ${configuration.server.securePort}!`)
27. });
28.
29. http.createServer(app).listen(configuration.server.port);
30. }
31.
32. if (require.main === module) {
33. startServer();
34. } else {
35. module.exports = startServer;
36. }

Vamos a importar los módulos fs, http y https, los cuales ya viene por default
con NodeJS, por lo que no hay que instalar nada adicional.

Lo que sigue es levantar el servidor utilizando los certificados, por ello creamos
el servidor mediante el método createServer de la línea 20, el cual permite
agregar los parámetros key, cert, passphrase, los cuales corresponden a los
archivos del certificado. El key corresponde a la llave privada y cert a la llave
pública.

También podemos ver que hemos agregado la propiedad securePort en el archivo


serverConfig.js que tendrá el valor 443 para levantar el server en el puerto
seguro.

1. module.exports = {
2. server: {
3. port: 80,
4. securePort: 443
5. }
6. }

Finalmente, en la línea 29 registramos otro server que responsa en el puerto 80,


con la finalidad de que la aplicación continúe funcionando en el puerto no seguro.
Aun que en producción siempre sería mejor quitarlo.

Posibles problemas con el puerto 443

Al igual que el puerto 80, el puerto 443 es muy


solicitado por las aplicaciones, por lo que si el puerto
ya está siendo utilizado, tendremos que detener la
aplicación que lo utiliza.

Guardamos los cambios, reiniciamos el server y probamos entrar directamente a


“localhost” para ver cómo nos redireccionará a https:

Página | 584
Fig. 175 - Probando el canal seguro HTTPS.

Error común

Es altamente probable que cuando accedas a la


aplicación por el puerto 443, te aparezca la página en
blanco, debido a que el certificado del API no ha sido
agregado a la excepción de los sitios seguros, por lo
que tendás que entrar en https://api.localhost y
aceptar el certificado.

Habilitar el modo producción

Cuando comenzamos a escribir el API les comenté que webpack-dev-server es un


servidor integrado que se utiliza únicamente para entornos de desarrollo, pues
este servidor no está optimizado para producción, por lo que hay que condicionar
la ejecución de este servidor cuando ejecutemos en ambiente productivo.

Iniciar la aplicación en modo producción es realmente simple, pues solo basta


con definir la variable de entorno NODE_ENV=production. Esta variable pude ser
accedida desde NodeJS mediante process.env.NODE_ENV, sin embargo, definir la
variable, no hace más que pasarle el parámetro a NodeJS y seremos nosotros los
que tendremos que realizar acciones dependiendo del valor de esta variable.

Para definir esta variable, nos iremos al archivo package.json y modificaremos la


sección scripts para dejarla de la siguiente manera:

1. "scripts": {
2. "start": "npm run build && npm run start_windows",
3. "dev": "nodemon server.js",

585 | Página
4. "build": "webpack -p",
5. "start_linux": "NODE_ENV=production&&node cluster.js",
6. "start_windows": "set NODE_ENV=production&&node cluster.js"
7. }

Al agregar estos nuevos scripts, nos permite ejecutar la aplicación de una forma
más simple, ya que podemos ejecutar la aplicación en modo desarrollo mediante
el comando npm run dev o en producción mediante npm start.

Cuando ejecutamos npm run dev, lo que hacemos es que en realidad ejecutamos
el comando node server.js. y cuando ejecutamos npm start hacemos dos cosas,
lo primero es que se ejecuta el comando build, el cual transpila la aplicación para
producción mediante el comando webpack -p y luego, ejecutamos el comando
marcado en amarillo en la línea 2. Este comando podría variar según el sistema
operativo del servidor. En este caso, estamos ejecutando en Windows, por lo
tanto ejecutamos el script start_windows, el cual corresponde con el script de la
línea 6, pero si estamos en Linux, debemos de cambiar ese comando por
start_linux. La diferencia entre start_windows y start_linux es la forma en que
establecemos la variable de entorno NODE_ENV.

Ahora bien, para comprobar el ambiente, agregaremos la siguiente línea en el


archivo server.js antes de la conexión de la base de datos.

1. console.log("ENV ==> " , process.env.NODE_ENV)

Finalmente ejecutaremos el comando npm start para ver los resultados en la


consola:

Fig. 176 - Validando la variable NODE_ENV.

Como puedes ver, en la consola se aprecia que el ambiente dice “production”,


con lo cual, ya podemos empezar a realizar acciones dentro de nuestra aplicación
para optimizarla.

Sin embargo, esto no evita que webpack-dev-server inicie, por lo que tendremos
que hacer un pequeño cambie en el archivo server.js, el cual podemos ver a
continuación:

Página | 586
1. function startServer() {
2.
3. . . .
4.
5. if (process.env.NODE_ENV !== 'production') {
6. app.use(require('webpack-dev-middleware')(compiler, {
7. noInfo: true,
8. publicPath: config.output.publicPath
9. }))
10. }
11.
12. . . .
13. }

Si nos damos cuenta, en la línea 5 hemos condicionado la ejecución del servidor


de Webpack para que se ejecute solamente cuando la variable de entorno
NODE_ENV sea diferente de “production”.

El último paso será cambiar el archivo webpack.config.js para indicarle que el


bundle producido será para producción, por lo que cambiaremos la propiedad
mode a “production”:

1. module.exports = {
2. mode: "production",
3. ...
4. }

Ya con esta explicación, solo nos resta apagar el servidor e iniciarlo nuevamente
con el comando npm start. Una vez iniciado, intentamos entrar a la aplicación
Mini Twitter para comprobar que ahora si podemos ver la aplicación.

Fig. 177 - Aplicación en modo productivo.

587 | Página
Una vez que estés en la aplicación, quiero que observar que el icono de React ha
cambiado de color rojo al color azul, y si le damos click, nos arrojará un mensaje
indicándonos que la aplicación está corriendo en modo productivo. De lado
izquierdo puedes ver como se veía la aplicación antes de los cambios, del lado
derecho, como se ve ahora.

Podrás también ejecutar la aplicación en modo desarrollo con el comando npm


run dev y cambiando la propiedad mode de webpack.config.js a development.

Icono de React

Si no logras ver el icono de React en la parte superior


del navegador, es porque no has instalado el plugin
necesario, en tal caso, puedes regresar al inicio del
libro para aprender como instalar el plugin React
Developer Tools.

NOTA: si quieres volver a ejecutar la aplicación nuevamente en modo desarrollo,


tan solo es necesario ejecutar el comando nodemon server.js.

Hosting y dominios

Una vez que toda nuestra aplicación ha sido configurada para operar en
producción, solo nos restaría conseguir un Hosting y un dominio apropiado para
nuestra aplicación.

Lo primero y más importante, es conseguir el dominio perfecto para nuestra


aplicación, pues será el nombre para que el mundo pueda acceder a la aplicación,
sin embargo, dado la demanda, la gran mayoría de los dominios con nombre
comunes o simples, ya han sido adquiridos, por lo que tendrás que validar si el
domino está disponible.

Una de las herramientas que utilizo para comprobar la disponibilidad de un


dominio es https://who.is/, en la cual, solo tenemos que poder el dominio que
queremos y nos arrojará si está disponible o no, y de no estar disponible, te da
datos del actual dueño, por si quisieras contactarlo para negociar la compra.
Desde aquí mismo te permite hacer la compra del dominio, sin embargo, en mi
experiencia, tiene los costos más altos del mercado, por lo que yo recomiendo
comprarlos a Google directamente en https://domains.google.

Para asegurar que el API sigue funcionando en nuestro dominio, tendremos que
modificar el vhost del archivo server.js:

1. app.use(vhost('api.<domain>', api));

Página | 588
Donde <domain> es el dominio que hemos comprado, por ejemplo
minitwitter.com o reactiveprogramming.io, de tal forma que quedaría,
api.minitwitter.com o api.reactiveprogramming.io.

El siguiente paso es comprar un hosting en donde podamos alojar nuestra página,


y para esto, también hay muchas opciones, aunque en lo particular, a mí me
gusta utilizar DigitalOcean, pues permite alquilar servidores desde 5 USD al mes,
un precio bastante accesible y que soportan perfectamente un aplicación en
NodeJS con una carga moderada. Además, si la demanda crece y necesitas
aumentar el server, puede hacerlo dinámicamente con solo crecer el plan y no
hace falta configurar nada.

Una vez que tiene tanto el dominio como el hosting, tendrás que configurar el
DNS apuntar tu dominio a tu servidor. Por suerte, DigitalOcean también tiene el
servicio de DNS sin ningún costo.

En el servidor tendrás que instalar NodeJS como ya lo habíamos explicado al


inicio de este libro, descargar la aplicación e instalar todas las dependencias
mediante:

npm install

Finalmente, arrancamos la aplicación:

npm start

Si hemos hecho todo bien, ya deberíamos poder acceder a la aplicación


directamente con nuestro dominio.

589 | Página
Resumen

En este capítulo hemos aprendido como llevar efectivamente una aplicación hasta
producción. Optimizando muestro aplicación para darle una mejor experiencia al
usuario.

También hemos aprendido a crear un canal seguro mediante HTTPS y hemos


visto los dos tipos de certificados que podemos conseguir, es decir, los auto
firmados y los comprados.

Finalmente, hemos aprendido a sacarle el mejor provecho a nuestro procesador,


creando varias instancias de nuestra aplicación, al mismo tiempo que la hacemos
más robusta ante la caída de uno de sus nodos.

En este punto, hemos terminado por completo todas las unidades de este libro y
hemos aprendido a crear aplicaciones reactivas con React, NodeJS y MongoDB y
hemos aprendido desde cómo crear un Hello World en React, hasta crear toda
una API REST con persistencia en MongoDB.

Sin duda, creo que, tras finalizar este libro, no deberías de tener problemas para
iniciarte con un proyecto real, pues has aprendido todas las fases del desarrollo
de una aplicación con React, desde el FrontEnd hasta el BackEnd, incluso,
pasando por la persistencia con MongoDB y el pase a producción.

Página | 590
CONCLUSIONES FINALES
¡Felicidades! Si estas leyendo estas líneas, es porque seguramente has concluido
de leer este libro, lo que te convierte en un programador Full Stack capaz de
desarrollar una aplicación completa utilizando React, NodeJS, Express y MongoDB
(Stack MERN).

Como ya te habrás dado cuenta a lo largo de este libro, desarrollar una aplicación
completa, requiera de varias tecnologías, las cuales, por lo general son
enseñadas de forma independiente, dejando al programador la tarea de
investigar como unir todas las tecnologías para crear una solo aplicación
funcional.

El objetivo que me plante al momento de desarrollar este libro, es que el


estudiante pudiera desarrollar sus propias aplicaciones con MERN e incluso,
pueda acceder a mejores oportunidades de trabajo, por lo que si he logrado
cualquiera de estas dos cosas, me doy por satisfecho.

Recuerda que el mundo de las tecnologías es un ambiente muy dinámico, donde


lo que aprendemos hoy, probablemente mañana no sirve, por lo que te invito a
no dejar de aprender y seguir estudiando diariamente.

Finalmente, no me queda más que agradecerte por permitirme ser parte de tu


crecimiento profesional y por la confianza depositada en mí al momento de
adquirir este libro.

Si crees que este libro alcanzó tus expectativas y te fue de utilidad, te


invito a dejarnos tu reseña en
http://reactiveprogramming.io/react/comentarios y compartir con tus amigos
la referencia a este libro.

GRACIAS.

591 | Página

También podría gustarte