├── src
├── favicon.png
├── images
│ ├── top.png
│ ├── props.jpg
│ ├── react.png
│ ├── state.jpg
│ ├── diagrama.png
│ └── gatsby-icon.png
├── components
│ ├── Main.js
│ ├── Footer.js
│ ├── Showhide.js
│ ├── Image.js
│ ├── ElasticScroll.js
│ ├── Layout.js
│ ├── Header.js
│ ├── SEO.js
│ ├── NavButtons.js
│ ├── Framework.js
│ └── Nav.js
├── css
│ ├── index.js
│ ├── prism.js
│ └── base.js
├── monkey-patch.js
├── utils.js
├── pages
│ ├── 404.js
│ ├── index.mdx
│ ├── props-y-state.mdx
│ ├── intro.mdx
│ ├── paso-03.mdx
│ ├── paso-02.mdx
│ └── paso-01.mdx
└── prism-extensions.js
├── .prettierrc
├── gatsby-node.js
├── gatsby-ssr.js
├── LICENSE
├── README.md
├── .gitignore
├── gatsby-browser.js
├── gatsby-config.js
├── traduccion
├── Componentes.md
└── JSX.md
├── package.json
└── templates
├── template.js
└── template.css
/src/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agustinmulet/reactworkshop/HEAD/src/favicon.png
--------------------------------------------------------------------------------
/src/images/top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agustinmulet/reactworkshop/HEAD/src/images/top.png
--------------------------------------------------------------------------------
/src/images/props.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agustinmulet/reactworkshop/HEAD/src/images/props.jpg
--------------------------------------------------------------------------------
/src/images/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agustinmulet/reactworkshop/HEAD/src/images/react.png
--------------------------------------------------------------------------------
/src/images/state.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agustinmulet/reactworkshop/HEAD/src/images/state.jpg
--------------------------------------------------------------------------------
/src/images/diagrama.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agustinmulet/reactworkshop/HEAD/src/images/diagrama.png
--------------------------------------------------------------------------------
/src/images/gatsby-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agustinmulet/reactworkshop/HEAD/src/images/gatsby-icon.png
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "proseWrap": "always"
6 | }
7 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implement Gatsby's Node APIs in this file.
3 | *
4 | * See: https://www.gatsbyjs.org/docs/node-apis/
5 | */
6 |
7 | // You can delete this file if you're not using it
8 |
--------------------------------------------------------------------------------
/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file.
3 | *
4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/
5 | */
6 |
7 | // You can delete this file if you're not using it
8 |
--------------------------------------------------------------------------------
/src/components/Main.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 | import { MEDIA } from './Framework'
3 |
4 | const Main = styled.main`
5 | ${MEDIA.lg} {
6 | margin-left: 250px;
7 | }
8 | `
9 |
10 | export default Main
11 |
--------------------------------------------------------------------------------
/src/css/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import BaseCSS from '../css/base'
3 | import PrismCSS from '../css/prism'
4 |
5 | function CSS() {
6 | return (
7 | <>
8 |
11 | La página que estás buscando no existe{' '}
12 |
13 | 😅
14 |
15 |
25 °C
13 |23 | 24 | 26 25 |
26 |27 | 28 | 15 29 |
30 |38 | 39 | 26 40 |
41 |42 | 43 | 15 44 |
45 |53 | 54 | 26 55 |
56 |57 | 58 | 15 59 |
60 |68 | 69 | 26 70 |
71 |72 | 73 | 15 74 |
75 |83 | 84 | 26 85 |
86 |87 | 88 | 15 89 |
90 |98 | 99 | 26 100 |
101 |102 | 103 | 15 104 |
105 |Hola {props.nombre}!
77 | 78 | export default Saludo 79 | ``` 80 | 81 | Guardamos y vemos cómo nuestro componente nos saluda 👋👋👋 82 | 83 | **IMPORTANTE:** Si queremos usar Props en un componente de clase, tenemos que 84 | usar `this`, ya que los componentes de clase generan un contexto y las props 85 | vienen "de regalo". Si hiciésemos un _upgrade_ de nuestro componente funcional 86 | `Saludo` a uno de clase, podríamos usar las props recibidas de la siguiente 87 | forma: 88 | 89 | ```jsx 90 | import React, { Component } from 'react' 91 | 92 | class Saludo extends Component { 93 | render() { 94 | returnHola {this.props.nombre}!
95 | } 96 | } 97 | 98 | export default Saludo 99 | ``` 100 | 101 | Probalo y vas a ver que es lo mismo :) Pero dejémoslo como componente funcional, 102 | ya que es buena práctica que los componentes que solamente muestran data sean 103 | funcionales. Caso que necesitemos, hacemos el upgrade a componente de clase. 104 | 105 | ## State 106 | 107 | El **State** o estado, es mutable, es algo que puede variar. Está encapsulado y 108 | ojo, no debemos mutarlo indiscriminadamente como pisamos una variable \(lo cual 109 | no nos deja hacer React al estar encapsulado\). El State es un objeto que es 110 | propiedad del componente donde se lo declara y su scope o alcance está limitado 111 | al mismo componente. Suele ser el punto de partida para obtener los datos que 112 | luego van a ser pasados como props a los hijos. 113 | 114 | Al cambiar el estado en un componente, los componentes que tengan props que 115 | dependan de ese estado, **React**-cionan \(see what I did there? ;D ya habrán 116 | hecho mil veces ese chiste jajaja\) y se re-renderizan con la data actualizada, 117 | de eso se encarga React internamente y es casi mágico ✨ 118 | 119 | La forma de cambiar el estado de un componente es utilizando el método 120 | `setState` pasando como parámetro un objeto donde le indicamos qué partes del 121 | estado queremos cambiar. Luego React se encarga de hacer todos los cambios de 122 | forma **asíncrona** (cuidado con esto, no es instantáneo el cambio de estado), 123 | actualizar el estado, el virtual DOM y el DOM por nosotros. 124 | 125 |donde está 288 | renderizado nuestro componente `Saludo`, si vemos bien, al hacer click se 289 | actualiza el texto donde hubo un cambio, lo demás se mantiene igual. Genial, no? 290 | 291 | Nuestro componente `App` debería quedar de la siguiente forma: 292 | 293 | ### App.js 294 | 295 | ```jsx 296 | import React, { Component } from 'react' 297 | import './App.css' 298 | 299 | import Saludo from './components/Saludo' 300 | 301 | class App extends Component { 302 | constructor(props) { 303 | super(props) 304 | this.state = { 305 | nombre: 'nombre', 306 | } 307 | this.handleClick = this.handleClick.bind(this) 308 | } 309 | 310 | handleClick() { 311 | this.setState({ nombre: 'Agustin' }) 312 | } 313 | 314 | render() { 315 | return ( 316 |
93 | Edit src/App.js and save to reload.
94 |
140 | Edit src/App.js and save to reload.
141 |
Hola Mundo!
190 | ``` 191 | 192 | Y así de simple tenemos un componente nuevo, en un solo renglón 😜 \(obvio que 193 | es un componente simple, después se va a ir complejizando la cosa\). Este 194 | renglón vamos a tener que escribirlo entre los imports y la declaración de 195 | nuestro componente `App`. 196 | 197 | ```jsx 198 | import React, { Component } from "react"; 199 | import logo from "./logo.svg"; 200 | import "./App.css"; 201 | 202 | //Acá va nuestro nuevo componente 203 | const Saludo = () =>Hola Mundo!
; 204 | 205 | class App extends Component { 206 | /* [...] */ 207 | ``` 208 | 209 | Genial, si guardamos el archivo en este momento vemos que nuestra web app se 210 | actualiza automáticamente y en la consola React nos chilla porque tenemos 211 | definido el componente pero no lo estamos utilizando. Sabemos que adentro de 212 | Saludo vive nuestro componente que definimos, ahora **cómo lo usamos?**: Donde 213 | querramos que vaya nuestro componente \(en JSX\), escribimos `Hola Mundo!
224 | 225 | class App extends Component { 226 | render() { 227 | return ( 228 |
234 | Edit src/App.js and save to reload.
235 |
Hola Mundo!
;` 273 | - Exportamos nuestro componente para que pueda ser utilizado fuera de este 274 | archivo: `export default Saludo;` 275 | 276 | Listo, guardamos lo que hicimos y vamos a editar nuestro componente padre `App` 277 | para traer nuestro nuevo componente: 278 | 279 | - Borramos la línea donde definíamos a nuestro componente `Saludo`: 280 | ~~`const Saludo = () =>Hola Mundo!
;`~~ 281 | - Importamos nuestro componente desde el archivo recién creado \(escribimos esta 282 | línea donde estaba la línea que recién borramos\): 283 | `import Saludo from "./components/Saludo";` 284 | 285 | Guardamos y si todo sale bien, debería estar todo funcionando igual que hasta 286 | recién. 287 | 288 | Nuestros componentes deberían quedar así: 289 | 290 | ### App.js 291 | 292 | \(Limpié un poco para que quede solamente nuestro nuevo componente\) 293 | 294 | ```jsx 295 | import React, { Component } from 'react' 296 | import './App.css' 297 | 298 | import Saludo from './components/Saludo' 299 | 300 | class App extends Component { 301 | render() { 302 | return ( 303 |Hola Mundo!
321 | 322 | export default Saludo 323 | ``` 324 | 325 | Ya hiciste tu primer componente, **felicitaciones!!!!** 🎉🎉🎉🎉🎉🎉 326 | 327 | Ahora necesitamos aprender **Props**, **State** y algunas cosas más de 328 | componentes para poder seguir, no me odies, yo sé que es mucha teoría. 329 | -------------------------------------------------------------------------------- /src/pages/paso-03.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Workshop - Paso 3 3 | path: /paso-03/ 4 | index: 5 5 | --- 6 | 7 | import { Tabs, TabList, Tab, TabPanels, TabPanel } from '@reach/tabs' 8 | 9 | ## Usando Netlify para nuestro proyecto 10 | 11 | Bueno, como algunos habrán visto, casi todos mis slides y proyectos los tengo 12 | subidos a [Netlify](https://www.netlify.com), es \(más que\) un hosting bastante 13 | copado y facilita mucho el subir proyectos hechos por ejemplo con 14 | `create-react-app`. También tienen algo que se llama Netlify CLI (Command Line 15 | Interface) y Netlify Functions. Es decir, vamos a usar varias cosas del paquete 16 | de Netlify 😎 17 | 18 | Antes que nada, vamos a usar una 19 | [lambda function \(en inglés\)](https://www.netlify.com/docs/functions/) para 20 | mantener a salvo nuestra API key que tenemos en nuestro archivo 21 | `.env.development.local`! Para los que hicieron la versión anterior de este 22 | workshop, es parecido a lo que hacíamos antes, pero los amigotes de webtask 23 | parece que no aceptan nuevos registros... 🙄 \(Gracias 24 | [Iván Meyer](https://twitter.com/ivanmeyer_) por avisarme\) 25 | 26 | Empecemos por crear una carpeta en la raíz de nuestro proyecto que se llame 27 | `functions`, debería quedar a la misma altura que nuestro `package.json` y demás 28 | carpetas. Dentro de esa carpeta creamos un archivo con extensión js cuyo nombre 29 | va a definir la url después a donde tengamos que hacer nuestro `fetch`, por 30 | ejemplo le ponemos de nombre `getWeather.js`. 31 | 32 | Ahora todo lo que tengamos ahí adentro de nuestra _lambda function_ va a 33 | funcionar como un estilo de backend para nosotros. Lo que vamos a hacer 34 | básicamente para pedir la data del clima sería algo así: 35 | 36 | `Frontend usando fetch -> Lambda Function usando request -> Apixu` 37 | 38 | Explicado en palabras, desde nuestro frontend usando `fetch`, hacemos una 39 | petición a una URL que apunta a nuestra Lambda Function, desde la cual usando 40 | `request` hacemos la petición a Apixu. Luego la data fluye al revés con la 41 | respuesta de lo que nos retorne el servicio de Apixu. Si todavía no se entiende 42 | (es muy probable, porque no explico muy bien 😅), 43 | [este link](https://blog.makeitreal.camp/que-es-un-api/#cómo-funciona-un-web-api) 44 | puede ser de gran ayuda. 45 | 46 | Una vez adentro del archivo `getWeather.js`, vamos a crear un endpoint nuevo, el 47 | cual debe quedar así: 48 | 49 | ```javascript 50 | exports.handler = (event, context, callback) => { 51 | //Acá va nuestro código 52 | } 53 | ``` 54 | 55 | Wow, y qué es eso? 🤪 Esto se parece un poco más a cómo sería código en NodeJS 56 | \(un poco nomás\), pero a no asustarse que hay que modificar un par de cosas y 57 | ya lo sacamos andando 😉 Vamos de a poco: `exports.handler` representa nuestra 58 | función y es el punto de entrada cuando hagan una petición a nuestro endpoint, 59 | la cual recibe por parámetros un evento, un contexto y un\(a\) callback. Donde 60 | **event** es un objeto donde vamos a tener datos de la petición que recibimos 61 | del Frontend \(tipo de petición HTTP, ya sea GET, POST, etc., datos recibidos 62 | del Frontend si es una petición POST; digamos que es donde pasa toda la magia\), 63 | **context** pensé que iba a tener un contexto desde dónde se hacía la petición o 64 | algo así pero parece que no, entonces no le encuentro mucha utilidad 🤷♂️ y 65 | **callback** es una función que utilizamos para retornar data procesada al 66 | frontend o avisamos que hubo un error. 67 | 68 | - [Docu de Lambda Functions de AWS (son las que usamos con Netlify)](https://docs.aws.amazon.com/es_es/lambda/latest/dg/nodejs-prog-model-handler.html) 69 | - [Docu de NodeJS para leer más sobre transacciones HTTP (en inglés)](https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/) 70 | 71 | Empecemos por chequear qué tipo de petición nos estarían haciendo, nosotros 72 | desde el Frontend vamos a hacer una petición de tipo `GET` utilizando `fetch()` 73 | como ya hicimos. La información de la petición la encontramos en 74 | `event.httpMethod`, así que dentro de nuestra función hacemos: 75 | 76 | ```javascript 77 | exports.handler = (event, context, callback) => { 78 | //Chequeamos el tipo de petición HTTP, puede ser GET, POST... 79 | if (event.httpMethod === 'GET') { 80 | //Acá tenemos que ir a buscar la data a Weatherbit y retornarla en el 81 | //body de un objeto usando la función callback 82 | } 83 | } 84 | ``` 85 | 86 | Bien, ahora tenemos que traernos la dirección de la API que nos la traemos del 87 | Frontend y necesitamos una forma de traernos la data porque `fetch()` no nos 88 | sirve acá \(en el "backend"\). Empecemos por tener la URL de la API a mano así 89 | después la usamos para ir a buscar la data, en el renglón siguiente al 90 | `exports.handler` y afuera del `if` nos guardamos la URL de la petición a Weatherbit 91 | en una constante: 92 | 93 | ```javascript 94 | const request_url = `https://api.weatherbit.io/v2.0/forecast/daily?city=Buenos+Aires,Argentina&key=${process.env.REACT_APP_API_KEY}&days=7` 95 | ``` 96 | 97 | Por el momento vamos a dejar así como está la constante ya que la vamos a 98 | configurar más adelante de forma súper fácil 😉 99 | 100 | Buenísimo, ya tenemos la URL para buscar la data y sabemos que nuestra API key 101 | va a estar a salvo. Ahora tenemos que ir a buscar la data a nuestra 102 | `request_url` y retornarla en la respuesta de la función `callback`. Para hacer 103 | esto tenemos un paquete llamado `request` que es bastante intuitivo y copado, 104 | vamos a agregarlo a nuestro proyecto y luego lo incorporamos en la Lambda 105 | Function. 106 | 107 | En la terminal, en la raíz de nuestro proyecto corremos el siguiente comando: 108 | 109 | ```bash 110 | npm install request --save 111 | ``` 112 | 113 | Y una vez que se instala ya podemos usarlo en `getWeather.js`, primero lo 114 | importamos \(afuera de `exports.handler`, arriba de todo\): 115 | 116 | ```javascript 117 | const request = require('request') 118 | ``` 119 | 120 | Un momento, que importar no se hace con `import`? No me mientas, Agustin! 🤬 121 | Bueno, no, no les miento 😅 como dijimos que esto funcionaría como un backend, 122 | así es como se importan paquetes en NodeJS por ejemplo. 123 | 124 | Ahora que ya lo tenemos importado, vamos a usarlo! Agregamos esto adentro de 125 | nuestro `if`: 126 | 127 | ```javascript 128 | if (event.httpMethod === 'GET') { 129 | request.get(request_url, function(error, res, body) { 130 | if (error) { 131 | callback(null, { 132 | statusCode: 500, 133 | body: error, 134 | }) 135 | } else { 136 | callback(null, { 137 | statusCode: 200, 138 | body: body, 139 | }) 140 | } 141 | }) 142 | } 143 | ``` 144 | 145 | Pero esto cada vez se complica más! 🤬🤬🤬 Bueno, parece complejo pero yo creo 146 | que explicando cada parte se entiende bien lo que estamos haciendo: Con 147 | `request.get` vamos a ir a buscar a nuestra URL la data de Weatherbit, luego esa data 148 | que se consiga, se recibe en la función que está como segundo parámetro de 149 | `get`, en la cual tendremos en `error` el objeto de error si llegase a haber 150 | alguno \(crucemos los deditos para que no 🤞\), en `res` tendremos otro objeto 151 | de respuesta de esta función y en `body` vamos a tener la data que recibamos de 152 | hacer el `request.get` a la API de Weatherbit, es decir, el JSON que antes obteníamos 153 | al hacer el `fetch()` en el Frontend. Luego chequeamos si hay un error, y si lo 154 | hay retornamos en la función `callback` dos parámetros: el primero se utiliza 155 | para informar errores, pero vamos a enviar `null` para luego poder enviar al 156 | Frontend un objeto \(como segundo parámetro\) informando el código de estado 500 157 | \(código de error\) y un `body` con los detalles del error. Lo mismo hacemos 158 | después, en el caso que no haya habido fallo, informamos un código de estado 200 159 | \(todo ok ✅\) y en `body` mandamos lo que nos retorna el resultado de la API de 160 | Weatherbit. 161 | 162 | Con esto podríamos decir que ya estaría hecha la _Lambda Function_ de manera 163 | básica para hacer lo que necesitamos: No exponer nuestra API key y que nos 164 | busque la data que necesitamos. Nos debería haber quedado algo así: 165 | 166 |28 | 29 | 26 30 |
31 |32 | 33 | 15 34 |
35 |173 | 174 | {parseInt(dia.max_temp)} 175 |
176 |177 | 178 | {parseInt(dia.min_temp)} 179 |
180 |{current.temp} °C
318 |341 | 342 | {parseInt(dia.max_temp)} 343 |
344 |345 | 346 | {parseInt(dia.min_temp)} 347 |
348 |{current.temp} °C
445 |{current.temp} °C
557 |626 | 627 | {parseInt(dia.max_temp)} 628 |
629 |630 | 631 | {parseInt(dia.min_temp)} 632 |
633 |{current.temp} °C
415 |429 | 430 | 26 431 |
432 |433 | 434 | 15 435 |
436 |444 | 445 | 26 446 |
447 |448 | 449 | 15 450 |
451 |459 | 460 | 26 461 |
462 |463 | 464 | 15 465 |
466 |474 | 475 | 26 476 |
477 |478 | 479 | 15 480 |
481 |489 | 490 | 26 491 |
492 |493 | 494 | 15 495 |
496 |504 | 505 | 26 506 |
507 |508 | 509 | 15 510 |
511 |