Bienvenidos sean a este post, hoy crearemos una aplicacion de react.
Si vienen de los posts anteriores ya hemos creado algunas pero no en conjunto. Es decir, hoy veremos una aplicacion mas parecida a la vida real. Porque esta tendra varios componentes para distintas tareas y los mismos interactuan entre si. En este caso crearemos uno que sera una lista de productos, donde podremos ver detalles de cada uno. Nuestro primer paso sera crear la aplicacion y para ello ejecutaremos el siguiente comando:
s$ npx create-react-app libros --template typescript
Esto nos creara nuestra aplicacion base, nuestro siguiente paso sera descargar el siguiente archivo con una serie de imagenes:
Ingresen en el directorio de la aplicacion y vayan al directorio public generen un nuevo directorio con el nombre de images, una vez realizado simplemente extraigan los archivos en ese directorio y ya esta todo listo para usarse. Nuestro siguiente paso sera instalar unos elementos basicos para una mejor visualizacion y para ello ejecutaremos los siguientes comandos:
$ npm install --legacy-peer-deps @material-ui/core
$ npm install --legacy-peer-deps @material-ui/icons
Estos dos comandos nos instalaran distintos elementos que usaremos en un momento. La opcion –legacy-peer-deps es porque estos paquetes estan deshechados. Lo siguiente sera ir a index.tsx y modificaremos el codigo existente de la siguiente manera:
src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Si el codigo de este archivo es igual al que ven, no deben modificarlo. De lo contrario si porque sino tendran un problema al renderizar los distintos componentes. Este basicamente es el encargado de renderizar al objeto App que comparte el archivo App.tsx pero de eso hablaremos en un rato. Con esto realizado, lo siguiente sera crear un nuevo archivo en el directorio src con el nombre de Libros.tsx y le agregaremos el siguiente codigo:
src/Libros.tsx
export interface ILibro {
id: number;
nombre: string;
imagen: string;
descripcion: string;
datos?: IDatos;
}
export interface IDatos {
autor: string;
anyo: number;
precio: number;
}
export class Biblioteca {
itemSeleccionado(id: number) { }
libros: ILibro[] = [
{
id: 1,
nombre: "The Call of Cthulhu",
imagen: "/images/cthulhu.png",
descripcion: "Creo que lo más misericordioso del mundo es la incapacidad de la mente humana para correlacionar todos sus contenidos. Vivimos en una plácida isla de ignorancia en medio de mares negros del infinito, y no estaba previsto que debiéramos viajar muy lejos.",
datos: {
autor: "H.P. Lovecraft",
anyo: 1928,
precio: 15.75,
}
},
{
id: 2,
nombre: "Conan, the barbarian",
imagen: "/images/barbaro.png",
descripcion: "Conan el Cimmerio. De pelo negro, ojos hoscos, espada en mano, un ladrón, un saqueador, un asesino... Descubre cómo empezó todo. Conan el Bárbaro tuvo cien imitadores. Descubra por qué con estos cuentos de sus primeros años de vida.",
datos: {
autor: "Robert E. Howard",
anyo: 1934,
precio: 4.99,
}
},
{
id: 3,
nombre: "Hyperborea",
imagen: "/images/hyperborea.png",
descripcion: "El ciclo hiperbóreo, o de Hiperbórea, es una serie de relatos fantásticos del escritor norteamericano Clark Ashton Smith que se desenlazan en la isla de Hiperbórea, mítica isla de la Antigua Grecia ubicada más allá de Tracia, donde vivía el dios griego del viento del norte Bóreas.",
datos: {
autor: "Clark A. Smith",
anyo: 1971,
precio: 11.99,
}
},
{
id: 4,
nombre: "Conan, the bucaneer",
imagen: "/images/bucanero.png",
descripcion: "Conan, que ahora tiene poco más de treinta años y capitán corsario del Wastrel, se ve envuelto en la política del reino de Zingara cuando busca un tesoro mítico en la Isla Sin Nombre. En su aventura se mezclan la princesa Chabela, hija de un rey zingara moribundo, el corsario Zarono y el hechicero estigio Thoth-Amon.",
datos: {
autor: "Robert E. Howard",
anyo: 1971,
precio: 19.99,
}
},
{
id: 5,
nombre: "The color out of space",
imagen: "/images/color.png",
descripcion: "La historia está contada en primera persona por un ingeniero encargado de hacer un estudio para edificar un embalse en un remoto paraje a las afueras de Arkham. Allí encuentra un área de terreno extraño denominada erial maldito que es distinta a todas y le causa sensaciones muy poco agradables.",
datos: {
autor: "H. P. Lovecraft",
anyo: 1927,
precio: 11.45,
}
}
]
}
Parece mucho codigo pero no lo es tanto, en la primera parte definimos una interfaz que sera para almacenar los datos de cada libro, en este almacenaremos el identificador, titulo, una descripcion y la imagen y finalmente algunos datos adicionales pero que seran opcionales. Para este ultimo caso esta la segunda interfaz que almacenara datos como el autor, el año de publicacion y el precio. Y finalmente tenemos una clase que sera la contenedora de los libros, por eso la llamamos Biblioteca, y esta constara de un metodo para cuando seleccionamos un libro, itemSeleccionado, y una propiedad llamada libros. Esta sera del tipo de la primera interfaz y la haremos array para almacenar varios donde pasaremos cada uno de los datos de los libros. Nuestro siguiente paso sera crear un nuevo archivo en el directorio src con el nombre de VerColeccion.tsx y le agregaremos el siguiente codigo:
src/VerColeccion.tsx
import React from 'react';
import { Box } from '@material-ui/core';
import { VerItem } from './VerItem';
import { Biblioteca } from './Libros';
export class VerColeccion extends React.Component<Biblioteca> {
constructor(props: Biblioteca) {
super(props);
this.itemSeleccionado = this.itemSeleccionado.bind(this);
}
itemSeleccionado(id: number) {
console.log(`itemSeleccionado: ${id}`);
this.props.itemSeleccionado(id);
}
render() {
let libros = this.props.libros.map((libro) => {
return (
<VerItem key={libro.id} {...libro}
onSeleccionado = {this.itemSeleccionado}/>
)
});
return (
<Box display="flex" flexWrap="wrap">
{libros}
</Box>
)
}
}
Primero importaremos todos los elementos que necesitaremos. El primero sera para acceder a todos los elementos de react, el siguiente sera para acceder al elemento Box de los que instalamos al comienzo, la siguiente es una clase que aun no creamos pero pronto lo haremos y la ultima es la clase que creamos anteriormente con todos los libros.
Despues definiremos la clase que transformaremos en componente de react y la haremos del tipo Biblioteca. Lo primero que haremos sera definir el constructor para iniciar a props mediante super y luego uniremos el metodo itemSeleccionado de la clase Biblioteca con el de esta clase mediante bind. Lo siguiente sera definir el metodo itemSeleccionado donde mostrara un mensaje en el log de la consola y llamara al de la clase Biblioteca para pasarle el valor recibido. Con esto definido pasamos a llamar a render donde primero definiremos un nuevo objeto llamado libros y en este almacenaremos el resultado de aplicar a la funcion map en la propiedad libros de la clase Biblioteca. En este tenemos un argumento que recibira el valor de cada pasada y devolveremos un objeto del tipo VerItem el cual recibira un valor key que contendra el id de cada libro y pasaremos el resto de las propiedades de cada libro mediante el propagador de objetos, del cual hablamos en este post, y tambien tendra un llamado a un escuchador de eventos para cuando es seleccionado y le pasaremos el metodo itemSeleccionado. Y antes de finalizar pasaremos al Box donde estaran todos los libros en el objeto definido anteriormente.
Ahora debemos definir a la clase VerItem y para ello crearemos un nuevo archivo con el nombre VerItem.tsx y le agregaremos el siguiente codigo:
src/VerItem.tsx
import { Card, CardHeader, CardMedia } from '@material-ui/core';
import React from 'react';
import { ILibro } from './Libros';
import './VerItem.css';
export interface IVerItem extends ILibro {
onSeleccionado(id: number): void;
}
export class VerItem extends React.Component<IVerItem> {
constructor(props: IVerItem) {
super(props);
this.onSeleccionado = this.onSeleccionado.bind(this);
}
render() {
return (
<div className="ver-libro-card">
<Card onClick={this.onSeleccionado}>
<CardHeader title={this.props.nombre}
subheader={this.props.datos?.autor}
/>
<CardMedia
className="card-media-image"
image={this.props.imagen}
/>
</Card>
</div>
)
}
onSeleccionado() {
this.props.onSeleccionado(this.props.id);
}
}
Hablemos primero sobre los elementos que importaremos. El primero sera para generar las tarjetas de los libros y estos los tomaremos de material-ui, componentes que instalamos al inicio, luego tenemos a react como siempre, el siguiente es ILibro para poder utilizar este tipo de dato y por ultimo un archivo de css que contendra algunas clases.
Lo primero que haremos sera definir una interfaz y la haremos heredera de ILibro, esto lo hacemos asi para que esta interfaz pueda acceder a las propiedades de cada libro. En esta declaramos un metodo llamado onSeleccionado que sera la encargada de actuar cuando sea seleccionado un libro. Despues tenemos la definicion de nuestra clase y esta tambien sera un componente de react mediante la herencia y ademas del tipo de la interfaz anterior para poder acceder a las propiedades de los libros. En el constructor iniciaremos a props mediante super y uniremos el onSeleccionado de la clase anterior con el de este objeto, tal como vinimos haciendo hasta ahora, y luego tenemos a nuestro render. En este crearemos la tarjeta (card) para cada libro donde mostraremos el titulo, al autor y la imagen correspondiente a la cubierta del mismo. Y para ello usaremos a CardHeader para mostrar el titulo y autor, y a CardMedia para mostrar la imagen. En este ultimo objeto le aplicamos una clase para regular la imagen, y al div que contiene a la tarjeta le aplicamos otra clase. Estas clases las definiremos en el archivo css que importamos al inicio.
Por ultimo tenemos la definicion de la funcion que unimos en el constructor y sera la del archivo anterior y le pasamos el id, tambien de props, con todo esto comentado crearemos un nuevo archivo con el nombre de VerItem.css y le agregamos el siguiente codigo:
src/VerItem.css
.card-media-image {
height: 150px;
background-size: contain !important;
margin-bottom: 20px;
}
.ver-libro-card {
padding: 20px;
min-width: 300px;
}
Aqui tenemos las dos clases que aplicaremos a las tarjetas, la primera sera para las imagenes y la segunda sera para la tarjeta general. Lo siguiente sera ir a App.tsx y lo modificaremos de la siguiente manera:
src/App.tsx
import React from 'react';
import { VerColeccion } from './VerColeccion';
import { Biblioteca, ILibro } from './Libros';
import './App.css';
export interface IAppProps {};
export interface IAppEstado { libro: ILibro | null; };
const instanciaBiblioteca = new Biblioteca();
class App extends React.Component<IAppProps,IAppEstado> {
constructor(props: IAppProps) {
super(props);
this.verDetalles = this.verDetalles.bind(this);
this.state = { libro: null }
}
render() {
return (
<div>
<VerColeccion
{...instanciaBiblioteca}
itemSeleccionado={this.verDetalles} />
</div>
)
}
verDetalles(id: number) {
console.log(`App.verDetalles: llamado`);
}
}
export default App;
Al comienzo importaremos todo lo necesario, en este caso primero a la clase React para crear nuestro componente, despues la clase encargada de mostrar toda la biblioteca, la siguiente es para importar los datos de la biblioteca y la interfaz para manejar cada libro y por ultimo la clase relacionada a este archivo, aunque no tendra injerencia en nuestra aplicacion.
Luego definimos dos interfaces, para poder usar las propiedades y los estados. La de las propiedades la dejaremos en blanco pero la de estado le agregaremos una propiedad para manejar cada libro, por el momento no lo usaremos asi, y luego definimos un objeto de la clase Biblioteca para tener acceso a todos los datos.
La clase App sera un componente de React y del tipo de las interfaces que definimos anteriormente. Lo primero que haremos sera ejecutar el constructor para iniciar a props mediante super, unir la funcion para ver detalles con el metodo local, tal como hicimos con casos anteriores, y por ultimo iniciamos a la propiedad libro de estado, state, y lo siguiente sera renderizar al componente. En este caso pasamos un div que contendra al objeto para ver todos los libros, y como dato le pasamos el objeto que creamos de Biblioteca y con el operador de propagacion de objetos lo pasaremos. Y tambien asociaremos la propiedad itemSeleccionado con el metodo verDetalles de este objeto. Por ultimo, definiremos este metodo para que no nos devuelva ningun error. Si lo prueban se vera de la siguiente manera:

Ya tenemos la base de nuestra aplicacion, ahora pasaremos a agregar la opcion para ver los detalles de cada uno. Para ello debemos crear un nuevo archivo con el nombre de VerDetalles.tsx y le agregaremos el siguiente codigo:
src/VerDetalles.tsx
import React from "react";
import { AppBar, Button, Dialog, Grid, IconButton, Paper, Slide, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Toolbar } from "@material-ui/core";
import { TransitionProps } from "@material-ui/core/transitions/transition";
import { Close } from "@material-ui/icons";
import { ILibro } from './Libros';
import './VerDetalles.css';
export interface IDetallesProps {
open: boolean;
libro: ILibro | null;
manejarCierre(): void;
}
const Transicion = React.forwardRef(function Transition(
props: TransitionProps & { children?: React.ReactElement },
ref: React.Ref<unknown>) {
return <Slide direction="left" ref={ref} {...props} />;
});
export class VerDetalles extends React.Component<IDetallesProps> {
constructor(props: IDetallesProps) {
super(props);
this.manejarCierre = this.manejarCierre.bind(this);
}
render() {
return (
<div className="full-screen-details-dialogue">
<Dialog
fullScreen
open={this.props.open}
TransitionComponent={Transicion}>
<AppBar>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={this.manejarCierre}
aria-label="close">
<Close></Close>
</IconButton>
</Toolbar>
</AppBar>
<div className="detalles-libro-top">
<Paper className="detalles-libro-body">
<Grid container spacing={5}>
<Grid item>
<img className="imagen-grande"
src={this.props.libro?.imagen} />
</Grid>
<Grid item xs container direction="column"
justify="flex-start" align-items="stretch">
<Grid item>
<h1>{this.props.libro?.nombre}</h1>
</Grid>
<Grid item>
<h1>{this.props.libro?.descripcion}</h1>
</Grid>
<Grid item>
<TableContainer component={Paper}>
<Table aria-label="tabla">
<TableHead>
<TableRow>
<TableCell>Datos del libro</TableCell>
<TableCell> </TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Autor</TableCell>
<TableCell>
{this.props.libro?.datos?.autor}
</TableCell>
</TableRow>
<TableRow>
<TableCell>Publicado</TableCell>
<TableCell>
{this.props.libro?.datos?.anyo}
</TableCell>
</TableRow>
<TableRow>
<TableCell>Precio</TableCell>
<TableCell>
{this.props.libro?.datos?.precio}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
</Grid>
</Grid>
</Paper>
</div>
</Dialog>
</div>
)
}
manejarCierre() {
console.log(`Detalles: manejarCierre()`);
this.props.manejarCierre();
}
}
Primero como siempre pasamos todos los elementos que necesitamos importar, el primero sera para crear el componente de react. Los siguientes son componentes de @material-ui para poder crear la ventana de dialogo. Esta contendra todos los datos de cada libro, todos los definidos en Libros.tsx. Luego de estos elementos tenemos uno que se encargara de las transiciones de este dialogo para cuando aparezca y desaparezca. El siguiente sera el icono que usaremos para representar el boton para cerra el dialogo. Lo siguiente es la interfaz para los tipos de cada libro y por ultimo importamos el archivo con las clases que aplicaremos al dialogo.
Nuestro siguiente paso sera la creacion de una interfaz que usaremos para las propiedades y en ella tendremos tres:
- open, que sera para abrir el dialogo cuando seleccionemos un libro
- libro, que sera para tomar todos los datos del libro
- manejarCierre, que sera para cerrar el dialogo
Lo siguiente sera definir una constante llamada Transicion la cual usaremos para cuando se muestre o cierre el dialogo con los detalles. Con esto definido lo siguiente sera la definicion del componente para mostrarlo. Primero en el constructor iniciaremos a las props y uniremos el manejarCierre del llamado con el local, como hicimos en todos los casos. En el render parece un codigo complicadisimo pero es mas lleno de elementos que otra cosa. Primero establecemos el div que contendra todo y le aplicamos una clase. Luego pasamos el objeto Dialog para crear nuestro cuadro de dialogo, este ira a pantalla completa mediante el fullscreen, almacenara el valor de open de las propiedades para saber si debe abrirse o cerrarse y por ultimo le estableceremos la transicion que creamos para que se aplique al momento de abrirlo o cerrarlo. El siguiente bloque sera la barra de la aplicacion que tendra una barra de herramientas, y el icono del boton donde lo ubicaremos, le establecemos el color y que use la accion de cerrar, manejarCierre, al momeno de clickearlo, y por ultimo usamos el elemento Close para mostrarlo, cerramos todos los elementos anteriores.
Despues tenemos un div con una clase del archivo css que importamos al inicio. Esta contendra un objeto del tipo Paper, tambien con una clase del archivo, y dentro tendremos al elemento Grid que se encargara de contener los distintos elementos.
El primero sera el contenedor general, luego otro que sera para la imagen del libro y a este le aplicamos tambien la clase del archivo encargada de manejar a la imagen. El siguiente Grid es como contenedor del resto que usaremos para mostrar cada uno de los elementos de cada libro. Como son la descripcion, titulo y los datos adicionales. Observen que por cada uno tenemos un Grid y en el caso de los datos de autor, año de publicacion y precio aplicamos los elementos para crear una tabla y puedan ser mostrados en este formato. Tambien en cada caso los datos tienen al operador de opcional (?) para cuando el dato no sea cargado. Por ultimo, tenemos la definicion del metodo que usamos para cerrarlo y este llama al metodo pero de las propiedades, es decir al que llama a esta clase. Con esto tenemos todo listo para mostrar los detalles de cada libro pero de aqui nos falta el archivo con las clases utilizadas. Para ello debemos crear un nuevo archivo con el nombre de VerDetalles.css y le agregaremos el siguiente codigo:
src/VerDetalles.css
.imagen-grande {
height: 400px;
background-size: contain !important;
margin: 30px;
}
.detalles-libro-top {
margin-top: 90px;
}
.detalles-libro-body {
margin-left: 50px;
margin-right: 50px;
}
Como dijimos, estos son los estilos que aplicamos a distintos elementos el archivo anterior. Como son para la imagen, encabezado y cuerpo de los Paper, respectivamente. Antes de realizar nuestra ultima modificacion, ejecutaremos los siguientes comandos:
$ npm install --legacy-peer-deps underscore
s$ npm install --legacy-peer-deps @types/underscore
El modulo y sus archivos de declaracion seran para poder acceder a una herramienta que nos sera util para buscar los libros. Con esto realizado debemos ir a App.tsx donde modificaremos el codigo de la siguiente manera:
import React from 'react';
import { VerColeccion } from './VerColeccion';
import { VerDetalles } from './VerDetalles';
import { Biblioteca, ILibro } from './Libros';
import * as _ from 'underscore';
import './App.css';
export interface IAppProps {};
export interface IAppEstado {
detalles: boolean;
libro: ILibro | null;
};
const instanciaBiblioteca = new Biblioteca();
class App extends React.Component<IAppProps,IAppEstado> {
constructor(props: IAppProps) {
super(props);
this.verDetalles = this.verDetalles.bind(this);
this.manejarCierre = this.manejarCierre.bind(this);
this.state = {
detalles: false,
libro: null
}
}
render() {
return (
<div>
<VerColeccion
{...instanciaBiblioteca}
itemSeleccionado={this.verDetalles} />
<VerDetalles open={this.state.detalles}
libro={this.state.libro}
manejarCierre={this.manejarCierre}/>
</div>
)
}
verDetalles(id: number) {
console.log(`App.verDetalles: llamado`);
let encontrao = _.find(
instanciaBiblioteca.libros,
(libro: ILibro) => {
return libro.id === id
});
if (encontrao) {
this.setState({
detalles: true,
libro: encontrao
});
}
}
manejarCierre() {
console.log(`App: manejarCierre()`);
this.setState({
detalles: false,
libro: null
})
}
}
export default App;
Al inicio agregaremos dos lineas de importacion, la primera sera para poder acceder a la clase VerDetalles y la otra es para acceder al modulo que instalamos anteriormente. Observen que usamos a * as _ para asignarle este nuevo alias y utilizarlo de esta forma. La siguiente modificacion es en la interfaz IAppEstado donde agregamos una propiedad llamada detalles que sera de tipo boolean, la cual la usaremos en unos instantes. La siguiente modificacion es en el constructor donde ahora uniremos a la funcion manejarCierre con el metodo local y en state iniciaremos a la propiedad nueva con el valor de false. Esta propiedad sera la encargada de mostrar los detalles, cuando posea el valor de true los mostrara y cuando sea false los ocultara. La siguiente modificacion es en el render donde ahora agregamos el elemento VerDetalles y este tendra varias propiedades. La primera es open y se ajustara en base al valor de detalles, realizando lo comentado anteriormente dependiendo del valor, despues tenemos una llamada libro que sera para pasar todo el contenido del libro y el ultimo es para enviar cual es el metodo para cerrar el detalle.
La siguiente modificacion es en verDetalles donde ahora crearemos un objeto que almacenara el resultado de la funcion find del modulo underscore, por eso le ponemos el alias antes, y este buscara en el objeto libros al id recibido con el id del libro. Si este es encontrado y almacenado se ejecutara el condicional siguiente y procede a cambiar el estado de detalles a true para mostrarlos y le asignamos el valor almacenado a libro. Este es el encargado de permitir que se realice todo. Por ultimo, tenemos al metodo manejarCierre que se encarga de cambiar el estado de estas propiedades para cerrar el dialogo. Dado que a detalles lo establece como false, y libro como null. Con esto realizado podemos probarlo para ver como funciona ahora:
En el video pueden ver como ahora clickeando sobre cualquiera de los libros en el listado nos muestra una nueva ventana de dialogo para los detalles de cada libro. En este caso tenemos un tema muy particular porque llamamos a un complemento desde otro complemento y solo se afectan a los complementos involucrados. Antes de finalizar les dejo un link con todos los archivos de la aplicacion, por si necesitan compararlos:
En resumen, hoy hemos creado una aplicacion en react desde cero, desde su primer comando hasta como agregar componentes que utilizan otros componentes, como implementarlos, como configurarlos, como hemos implementado algunos temas vistos anteriormente. Espero les haya resultado de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donatión
It’s for site maintenance, thanks!
$1.50
