El objetivo final de este proyecto es crear un catalogo de Productos el cual podamos filtrar por categorias, agregar nuevos productos, tener la vista individual de cada uno de los productos y agregar a un carrito de compras.
Para esto vamos a ir paso a paso viendo todas las caracteristicas de React, yendo de conceptos y ejercicios mas simples, a complejisar nuestra aplicación con conceptos mas avanzados.
El Proyecto va estar separado en X Partes estas serán:
Lo primero que vamos a hacer es crear el boilerplate para nuestro proyecto. Para esto necesitamos un index.html
para mostrar nuestra aplicación, una configuración de webpack
que bundlee nuestro código a un solo archivo que usemos en nuestro index.html, y finalmente nuestra configuración básica de React.ç
Antes de arrancar a escribir archivos iniciemos nuestro proyecto. npm init
para crear un package.json e instalemos nuestras dependencias.
Las de desarrollador:
$ npm install --save-dev webpack babel-preset-env babel-preset-stage-0
Y las dependencias de produccion:
$ npm install --save react react-dom
Crea el archivo index.html
. Recuerda las carácteristicas mas importantes que tiene que tener este: Un div
con un id específico en el cual vamos a renderizar nuestra app, y un script
que requerirá nuestro bundle.js
.
Para nuestra configuración de webpack vamos a necesitar crear el archivo webpack.config.js
. En este vamos a necesitar varias cosas:
module.exports = {
entry:'./src/index.js',
output: {
path: __dirname,
filename: './public/bundle.js',
},
devtool: 'source-map',
resolve: {
extensions: ['.js', '.jsx'],
},
module: {
loaders: [
{
test: /(\.js|\.jsx)$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: { presets: [ 'env', 'stage-0' 'react'] },
},
],
},
};
En entry le decimos donde es el punto de inicio de nuestra app. En output decimos donde y como se va a generar nuestro archivo bundle Recorda que este tiene que ser igual al que esta usando index.html
. Agregamos el devtool source-map
para tener errores mas descriptivos.El resolve
nos va a permitir escribir archivos con extensiones tanto .js
como .jsx. Finalmente el loader de babel esta usando tres presets
env
stage-0y
react. Los dos primeros nos permitirán usar los features mas nuevos de JavaScript. El segundo
react` va a transpilar nuestro código JSX a JS.
Ahora en tu package.json
crea el comando "build"
dentro de tus scripts que ejecute "webpack -w"
. Ahora cuando corras npm run build
en tu consola vas a buildear tu aplicación y webpack se va a quedar observando por cambios gracias al flag -w
.
Ahora crea tu archivo de entrada. Este recuerda que va tener q importar Rect
de react
y render
de react-dom
.
Usa render y muestra un div con el texto 'hello world' dentro del div#app
que creamos para mostrar nuestra aplicación.
Ahora corre en la terminal el nuestro comando que habíamos creado antes npm run build
y abrí tu index.html en el browser. Si ves el 'Hello World' mostrandose en la ventana del browser y no tenes ningun error en la consola ni la terminal, seguramente hicimos todo bien.
Una vez que tengas todo esto funcionando ya estamos para ponernos a laburar en nuestra aplicación!
Por ultimo vamos a inicializar flow en nuestro proyecto para poder tiparlo. Si todavia no instalaste flow corre: npm install -g flow-bin
. Luego corré:
$ flow init
Esto va a crear nuestro archivo .flowconfig
que nos permita usar flow en nuestra aplicación. Recordá que para poder chequear los tipos en un archivo necesitas agregar /* @flow */
al principio de cada archivo el cual quieras incluir.
Ahora cada vez que quieras chequear el estado de tu archivo podés correr el comando `flow check.
También para un desarrollo más fácil recomendamos integrar los errores de flowtype a tu IDE. Para eso hay dos recomendaciones, usar el linter propio de flow agregando esto a nuestro .flowconfig
:
[lints]
all=warn
[options]
include_warnings=true
o integrarlo a eslint usando el paquete eslint-plugin-flowtype.
Ademas instala flow-bin
en tu proyecto y flow-typed
globalmente.
$ npm install -g flow-typed
$ npm install --save-dev flow-bin
Ahora ejecuta flow-typed install
para instalar las definiciones de las dependencias en tu proyecto, y recuerda de instalar cada dependencia que vayas agregando.
Nuestros componentes van a estar divididos en dos tipos, contenedores y presentacionales, normalmente estos se suelen dividir en dos carpetas distintas donde vamos a tener a los containers
y los components
.
Los contenedores van a ser esos componentes que tengan un estado. Estos normalmente los escribiremos como clases:
class App extends React.Component {
this.state = {
name: 'Guille';
}
render() {
<div>
<Profile name={this.state.name} />
<div>
}
}
Mientras que los presentacionales no tienen estado (statless), y como dice su nombre solo muestran una vista dependiendo los props que reciben. Los escribimos normalmente como funciones puras:
const Profile = ({ name }) => {
<div>
{name}
</div>
}
Ahora que diferenciamos los componentes contenedores y presentacionales. Definamos que va a necesitar nuestra aplicación.
- Un Contenedor de nuestra
App
que maneje el estado, este sería un arreglo de todos los productos. - Un
Grid
de Productos que itere sobre cada producto y cree... - Un
Item
de un Producto en si.
Por lo que vemos tenemos un contenedor y dos componentes presentacionales. Crea la carpeta containers
y components
. Define App
, dentro de contiainers
, que tenga un arreglo de productos en su estado, por ahora podes usar este arreglo, más adelante se lo pediremos a una API.
[
{
"id": 1,
"name": "Licensed Frozen Sausages",
"price": "39.00",
"image": "http://lorempixel.com/640/480/food",
},
{
"id": 5,
"name": "Unbranded Soft Fish",
"price": "228.00",
"image": "http://lorempixel.com/640/480/sports",
},
{
"id": 9,
"name": "Refined Cotton Salad",
"price": "531.00",
"image": "http://lorempixel.com/640/480/transport",
},
];
Pasá como prop el arreglo de productos a el componente Grid
que tenes que definir dentro de components
, y dentro del Grid
itera sobre cada Producto y pasaselo a un nuevo componente Item
que renderizará la información del producto en si.
No te olvides que dentro del iterador el componente tiene que recibir un
key
único, aprovechemos que tenemos el id de cada producto!
NO TE OLVIDES DE FLOW Hay muchas cosas aca que podemos typar por ejemplo podemos crear una type para cada producto, tambien podemos typear el estado y las props de cada componente!
Todo muy lindo, pero no nos sirve de nada nuestros productos harcodeados vayamos a la API a buscarlo! Pero aca surge una gran pregunta, donde deberíamos poner el request? Por ahi te surgen dos opciones inmediatas. La primera puede ser en el constructor
de la clase, pero el constructor puede ser llamado sin que sea renderizado el elemento, por lo que estaríamos haciendo un request por un componente que quizas nunca se muestre. Otra opción que podría surgir es hacerlo dentro del render
. Pero recordemos que el render
es llamado cada vez que el componente se actualiza tanto sus props como su estado. Por lo que hacer un request que actualice el estado, haría correr el render, que volvería a hacer el request, que volvería hacer el render y así.
Para mucho de este tipo de problemas React nos da varíos lifecycle hooks en los cuales podemos hacer acciones dependiento el momento del ciclo de vida del componente estos son (en orden de ejecución):
Viendo las opciones la ideal para nuestro problema va a ser componentDidMount
ya que nos aseguramos que vamos a cambiar el estado de un componente que ya esta montado en el DOM.
Ahora solo haz un request a /products
usando la librería de tu preferencia fetch
, axios
, etc... y una vez que tengas los resultados actualiza el estado del componente con el nuevo arreglo de productos usando el método this.setState
.
También no te olvides de cambiar la inicialización de tu estado con un arreglo de productos vacío.
Agrega un mensaje de Loading mientras los productos estan siendo fetcheados que desaparezca cuando los productos hayan llegado.
Genial! Ya tenemos nuestra grilla de productos funcionando ahora vayamos a la 3 Parte del proyecto
Lo que vamos a hacer ahora es tener un sidebar el cual nos muestre las categorías, que podamos seleccionar y filtrar los productos. Este componente debería tambien estar dentro de App
.
Vas a necesitar un sidebar que muestre una lista de todas las categorías (estas las podes conseguir en la API en /categories
). Tu lista debería incluir la opcion de mostrar todos y por debajo deberían estar las categorías de la API
Una vez que estes mostrando la lista vamos a tener que pasar a lo dificil, ¿Cómo vamos a hacer para filtrar los productos por una categoría específica cuando hagamos click en una de ellas?
Lo primero que tenemos que definir es la función que vamos a correr cuando el usuario haga click. En este caso deberíamos recibir el id
de la categoría y guardar en el estado que categoría fue elegida.
Como cualquier click handler vamos a recibir información del evento como argumento por lo que nosotros podemos usar el event.target
para saber que categoría fue clickeada. Otra posibilidad es definir la función por cada categoría y pasarle el id dentro de la función por ejemplo estas son las dos posibilidades:
// Usando el event object con event.target.id
<li id={category.id} onClick={switchCategory}>{category.name}</li>
// pasando el id de la categoria
<li onClick={() => switchCategory(category.id)}>{category.name}</li>
Ambos approach tienen sus ventajas y desventajas. En la primera estamos agregando información que necesitamos en nuestro controlador a la vista lo cual puede no quedar muy prolijo, pero es una estrategia común para los que vienen de otras librerias como jquery
. Pero en el segundo caso estamos definiendo una nueva función para cada elemento lo cual puede ser un poco innecesario y lento. Elige el que mas haga sentido para vos y continuá.
Ahora una vez que sabemos cual categoría esta seleccionada (si la hay), podemos decidir filtrar o no filtrar los productos. Simplemente en el lugar donde estamos iterando los productos agrega un filter
que decida que productos se renderizan y cuales no.
Ademas no te olvides de que ahora el mensaje de Loading tiene que aparecer hasta que tanto los productos y las categorias hayan sido recibidos por la página.