React Workshop
Workshop - Paso 1
Buenísimo, ya tenemos una idea de cómo hacer los componentes de nuestra app del
clima. Vamos al archivo templates/template.css
donde va a estar el css de la
app ya preparado, lo abrimos y copiamos todo el contenido y lo pegamos en
nuestro src/App.css
ya existente. Hagamos lo mismo con el archivo
templates/template.js
y src/App.js
donde ya van a estar preparadas las
clases (className
en React, recuerdan? 😜) con los estilos recién copiados.
Una vez que reemplazamos el código en nuestros archivos App.js y App.css vamos a
generar una API key para poder utilizar el servicio de la API del clima de
Weatherbit.io, la página para registrarse es
esta. Elegí Weatherbit porque Apixu
se puso bastante restrictiva y Weatherbit fue una de las más simples de usar que
encontré. Warning: Van a tener que loguearse a cada rato, tienen medio roto
el login parece, pero es lo mejor que encontré a cuanto info que brinda la API.
Bueno, repasamos antes de comenzar, deberíamos tener:
- Todo el código del archivo
template.css
(click para acceder al archivo) en nuestro ->src/App.css
- Todo el código del archivo
template.js
(click para acceder al archivo) en nuestro ->src/App.js
- Nuestra API Key de Weatherbit, la podemos ver en la página de Weatherbit.io en el Dashboard
Una vez que hagamos eso, si todavía tenemos corriendo el servidor, deberíamos
ver cómo se debería ver nuestra app terminada (falta componentizar todo,
pequeño detalle). Si no lo vemos, es posible que haya que refrescar la página,
sino es posible que no estemos corriendo el servidor, en cuyo caso debemos ir a
la línea de comando y volver a ejecutar npm start
.
Empecemos - Consiguiendo nuestra data
Vamos a encarar esto primero consiguiendo los datos y teniéndolos a mano en el
estado de nuestro componente para luego vamos a separar en componentes, pasando
todos los datos (mediante props) que necesitemos pasar. Hacer API calls
,
llamadas a las APIs, en React es quizás algo no tan simple como uno cree que
debería ser, pero hay un post que lo explica bastante bien. Acá vamos a explicar
lo necesario, pero si quieren ampliar pueden leer
el post acá (en inglés).
Antes de hacer el fetch
para traer la data, tenemos que saber dónde pedirla 😝
Según la docu de Weatherbit, si le 'pegamos' a esta dirección, vamos a tener el
pronóstico para el día de hoy + 6 días (sí, el día de hoy está incluido cuando
hacemos days=7
, por suerte 😀)
https://api.weatherbit.io/v2.0/forecast/daily?city=Buenos+Aires,Argentina&key=<YOUR_API_KEY>&days=7
Donde dice <YOUR_API_KEY>
ponemos nuestra key generada cuando nos registramos,
y si vamos a esa dirección en el navegador (con nuestra key pegada) vamos a
ver un JSON con toda la data que necesitamos (si se veo medio feo podemos
copiar y pegar el texto del JSON en JSON Formatter
y va a quedar más claro cómo están compuestos los objetos, o buscar alguna
extensión del navegador para formatear JSON, y en la
docu de Weatherbit (en inglés)
explica qué es cada parte del objeto, aunque vamos a ir viendo qué necesitamos
para cada parte de nuestra app) Como podemos ver, estamos buscando el clima en
la ciudad de Buenos Aires, Argentina, pero cambien el texto por su ciudad o por
la que quieran buscar en el mundo! 😁
Y ahora la pregunta del millón: dónde hacemos la llamada a la API en nuestro componente?
Existe lo que se llama "Métodos de los ciclos de vida" (Lifecycle methods) de nuestros componentes, son parte de React y son para que podamos hacer distintas cosas en distintos momentos de la vida de nuestro componente. En este diagrama podemos verlos bien y cuándo se ejecuta cada uno:
Diagrama de Ciclos de vida de componentes
El método donde vamos a hacer el fetch hacia la API de Weatherbit es
componentDidMount()
, con la idea de que primero nuestro componente se monte
(y renderice) correctamente y en ese momento buscar los datos para que se
actualice el estado de nuestro componente causando que React lo re-renderice
(no es algo que afecte a la performance, como dijimos, renderiza lo justo y
necesario nomás). Antes de hacer el fetch, tenemos que saber algo sobre usar
keys en el frontend de nuestras apps.
Seguridad con APIs y variables de entorno
Cuando trabajamos en equipo y también cuando utilizamos datos sensibles que no
queremos compartir con el mundo para que no se abusen de ellos (por ejemplo,
nuestra API key, que pertenece a nuestro usuario), se utilizan variables de
entorno en un archivo .env
, el cual puede tener una extensión distinta si es
que son variables del entorno de desarrollo local (.env.development.local) que
utilizamos cuando estamos desarrollando en nuestra PC o variables de producción
(.env.production) que son para cuando nuestra aplicación ya está funcionando
en el mundo exterior. Entonces vamos a aprovechar una de las bondades que nos da
create-react-app
y vamos a generar un archivo de desarrollo local para usar
nuestra API key sin tener que luego preocuparnos por la seguridad de la misma si
subimos nuestro código a github por ejemplo. Según
la documentación (en inglés),
debemos crear un archivo en la raíz de nuestro proyecto (sería junto al
package.json, el .gitignore, etc.) y lo vamos a llamar .env.development.local
(sí, comienza con un punto, al igual que el .gitignore). Dentro debemos
definir nuestra variable de entorno, que por convención se escriben en
mayúsculas y según la documentación de create-react-app, DEBE comenzar con
"REACT_APP". Podemos llamarla REACT_APP_API_KEY
para saber a qué nos referimos
cuando la querramos usar. Entonces dentro del archivo debemos tener una línea
como la siguiente:
REACT_APP_API_KEY = 'c4321dd709e94986a41d00XXXXXXXXXX'
Ya con tener esa línea podemos usar esta variable para hacer el fetch, para
utilizarla debemos anteponer process.env.
+ nuestra variable a utilizar. En
nuestro caso podemos usarla sumando strings "a la vieja usanza", o en mi opinión
de forma más prolija, utilizando
template strings de ES6,
lo cual quedaría de la siguiente manera:
`https://api.weatherbit.io/v2.0/forecast/daily?city=Buenos+Aires,Argentina&key=${process.env.REACT_APP_API_KEY}&days=7`
Donde podemos observar que el string en su totalidad va a estar envuelto entre
dos backticks `
y al momento de utilizar la variable, debe estar entre los
caracteres ${}
, queda más limpio que "string" + variable + "string", no?
También se pueden utilizar expresiones entre esos caracteres, es súper útil!
IMPORTANTE: En este momento ya teniendo nuestro archivo generado con la data
de nuestra API key, hay que reiniciar el servidor local de desarrollo para que
nuestra app pueda ver el .env.development.local
, sino vamos a tener problemas
después y no vamos a poder usar la variable de entorno. Así que vamos a nuestra
terminal donde esté corriendo el servidor y hacemos Ctrl + C
y luego corremos
el comando npm start
. Ahora podemos seguir sin problemas 😁
Bueno, ya tenemos nuestra API key y la vamos a tener resguardada a la hora de subir nuestro código a algún lado (no se va a subir ese archivo porque figura en el .gitignore), ya no nos queda otra que arrancar con el fetch, manos a la obra!
Esto es lo que vamos a hacer:
- Crear un estado en nuestro componente donde vamos a guardar los datos que vayamos a buscar a la API
- "Pegarle" a la API usando
fetch()
y guardando la respuesta en formato JSON en el estado - Empezar a utilizar los datos de ese estado en el componente
App
que tenemos
Vamos a crear el estado (tal como vimos antes, se hace en el constructor del componente):
/* [...] */
class App extends Component {
// Agregamos el constructor y definimos el estado
constructor(props) {
super(props);
this.state = {
current: {},
forecast: [],
location: {}
};
}
render() {
/* [...] */
Ya vimos más arriba qué recibimos en el JSON y un link a la docu de Weatherbit para ver para qué nos sirve cada cosa, pero pasemos en limpio:
En nuestro objeto de respuesta de la API, vamos a tener algunos datos sueltos
como ser el nombre de la ciudad, el código del país, latitud y longitud... Pero
lo que más nos interesa estudiar es el Array (colección de cosas) de nombre
data
, el cual tiene objetos con el clima de los 7 días que pedimos incluyendo
hoy (el día actual es el primer objeto en el array)
Por lo que podemos ver, en la parte de arriba (el div
con la clase 'top')
vamos a usar un objeto con algunos datos de los que están sueltos y el primer
objeto de data
.
Vamos a manipular un poco los datos que nos lleguen de la API para intentar
hacer más amena la transición a esta versión del workshop (si hiciste la versión
de Apixu, este refactor espero que no te complique la existencia). En current
vamos a guardar el primer valor del array data
, que como dijimos antes, va a
ser el clima del día corriente. Luego en forecast
guardamos el resto de los
días para usarlos en la parte inferior y location
va a ser un objeto que vamos
a armar a mano con una estructura parecida a un objeto que venía de la anterior
API (Apixu te odio 😑). Vayamos entonces a hacer el fetch()
a la dirección que
obtuvimos antes, con nuestra API_KEY y como dijimos, va a ser adentro del método
componentDidMount()
:
/* [...] */
class App extends Component {
constructor(props) {
/* [...] */
}
// Definimos el método componentDidMount con el fetch
componentDidMount() {
fetch(
`https://api.weatherbit.io/v2.0/forecast/daily?city=Buenos+Aires,Argentina&key=${process.env.REACT_APP_API_KEY}&days=7`
)
.then(response => response.json())
.then(jsonData => {
const current = jsonData.data.shift();
const forecast = jsonData.data;
const location = {
city_name: jsonData.city_name,
country_code: jsonData.country_code,
state_code: jsonData.state_code
};
this.setState({ current: current, forecast: forecast, location: location });
});
}
render() {
/* [...] */
Vamos a analizar un poco lo que hicimos en el método, primero hicimos el
fetch
(el link es a la documentación para ampliar) y esto nos retorna un objeto
Promise, es una promesa de la que vamos a obtener nuestros datos. Utilizando
programación funcional con function chaining, podemos decir que then
(entonces), una vez obtenida la response
(respuesta), la retorne en
formato JSON, usando el método json()
. Luego siguiendo con la cadena de
funciones, ese JSON va a caer en el siguiente then
y lo recibimos en
jsonData
, donde está nuestra data del clima que necesitamos manipular. Como
dijimos, en current
guardamos el primer objeto usando el método
shift()
de arrays, lo cual ya nos deja a data
con los 6 días restantes, esos los
metemos en forecast
y luego nos armamos location
con un objeto con los datos
que queremos mostrar, actualizando luego el estado y guardando todo lo que
generamos recién en cada parte del estado de nuestro componente. Algo copado que
podemos hacer es, que si nuestra variable (o constante) tiene el mismo nombre
que lo que queremos modificar en el estado, se puede poner de la siguiente
manera:
this.setState({ current, forecast, location })
Queda más compacto y a gusto de cada uno, yo lo prefiero así, más cortito 😁
Buenísimo, ya tenemos los datos en el estado de nuestro componente, ahora nos falta empezar a usarlos!
Un truquito (ponele) para que no nos quede taaan largo nuestro código al usar los datos de nuestro estado (this.state.clima.blablabla cada vez), podemos usar destructuring de ES6:
const { location, current, forecast } = this.state
Esto debemos insertarlo en el render()
de nuestro componente, antes del
return
. Luego podemos acceder a nuestra data por ejemplo haciendo
location.city_name
, current.temp
, etc. Ya vimos cómo acceder a los datos que
recibimos en el estado, así que les dejo un rato para ir acomodando toda la data
en la parte de arriba de nuestro componente... ... ... Listo? Ahhh te tira
errores? undefined
por todos lados? Bienvenido/a a Javascript 🤣🤣 Nah, para
evitar estas cosas existe algo súper útil que es el conditional rendering
(link a la docu),
donde le decimos que renderice cuando ya nuestra data esté "fetcheada" y
disponible. Esto lo logramos con un valor booleano (true o false) en el
estado del componente que debemos alterar una vez que terminemos con el fetch.
Entonces agregamos en el estado isLoaded: false
para indicar que todavía no
está cargada nuestra data. Luego en el fetch()
actualizamos nuestro estado e
indicamos que ya junto con nuestra data cargada, cambiamos isLoaded
a true
:
this.setState({ current, forecast, location, isLoaded: true }). Y luego al
renderizar, retornamos según esté cargada o no nuestra data, verificando con el
operador ternario
de la siguiente forma:
render() {
const { location, current, forecast, isLoaded } = this.state;
return isLoaded ? (
{/* Nuestro componente */}
) : (
<div className="App">
Cargando...
</div>
)
}
Bueno, el tema de la imagen del clima de arriba es un poco complejo porque
Weatherbit adentro de nuestro objeto del clima actual nos provee un objeto
weather
con una propiedad icon
, lo cual es un string con unos caracteres
raros como "c02d" 🤔🤔🤔 Pero si escarban en la documentación de Weatherbit (o
les paso mejor los links directo 😜) van a ver que cada código responde a un
iconito distinto del clima en este link
Entonces vamos a tener que bajar los iconitos uno por uno? 🤦♂️🤦♀️ Nooooo,
tranquis, que Weatherbit es copado y nos provee un pack de íconos que ya tiene
cada uno el nombre correspondiente al código que nos llega desde la API ;D Así
que vayamos a este link y bajemos el
último pack con todas las imágenes, Weatherbit weather Icons, y podemos
descomprimirlo en /src
para que nos quede /src/icons
y nuestras imágenes png
adentro.
(Gracias Pablo (@wbkpablo) por pasarme el link del pack y coparte a avisarme todos los problemas del workshop)
Ahora debemos indicarle al elemento img
dónde tiene que ir a buscar las
imágenes para cada iconito que nos llega, y vamos a hacer que los busque de
forma dinámica.
Esto lo podemos hacer con los template strings de los que hablamos antes pero
con una salvedad: necesitamos que nuestra app de react se entere de las imágenes
que están en esa carpeta, usando require()
. Sino tendríamos que copiar nuestra
carpeta directo en /public
y no es algo recomendable... Así que les dejo
intentarlo y si no les sale les dejo cómo sería acá abajo:
<img
src={require(`./icons/${current.weather.icon}.png`)}
alt="Clima principal"
className="image"
/>
Una vez que terminamos con la parte de arriba, debería quedarnos algo así
nuestro App.js
:
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
constructor(props) {
super(props)
this.state = {
current: {},
forecast: [],
location: {}
isLoaded: false
}
}
componentDidMount() {
fetch(
`https://api.weatherbit.io/v2.0/forecast/daily?city=Buenos+Aires,Argentina&key=${process.env.REACT_APP_API_KEY}&days=7`
)
.then(response => response.json())
.then(jsonData => {
const current = jsonData.data.shift();
const forecast = jsonData.data;
const location = {
city_name: jsonData.city_name,
country_code: jsonData.country_code,
state_code: jsonData.state_code
};
this.setState({ current, forecast, location, isLoaded: true });
});
}
render() {
const { location, current, forecast, isLoaded } = this.state
return isLoaded ? (
<div className="App">
<div className="container">
<div className="top">
<img
src={require(`./icons/${current.weather.icon}.png`)}
alt="Clima principal"
className="image"
/>
<p className="temp">{current.temp} °C</p>
<h4 className="city">
{location.city_name}, {location.state_code}, {location.country_code}
</h4>
<div className="temphr">
Max: {current.max_temp} °C, Min: {current.min_temp} °C, H: {current.rh} %
</div>
</div>
<div className="bottom">
<div className="card">
<h5>Lunes</h5>
<h6>2018-09-26</h6>
<img src={logo} alt="Clima diario" className="imagebottom" />
<div className="minmax">
<p>
<span className="tempmax" />
26
</p>
<p>
<span className="tempmin" />
15
</p>
</div>
</div>
<div className="card">
<h5>Martes</h5>
<h6>2018-09-27</h6>
<img src={logo} alt="Clima diario" className="imagebottom" />
<div className="minmax">
<p>
<span className="tempmax" />
26
</p>
<p>
<span className="tempmin" />
15
</p>
</div>
</div>
<div className="card">
<h5>Miércoles</h5>
<h6>2018-09-28</h6>
<img src={logo} alt="Clima diario" className="imagebottom" />
<div className="minmax">
<p>
<span className="tempmax" />
26
</p>
<p>
<span className="tempmin" />
15
</p>
</div>
</div>
<div className="card">
<h5>Jueves</h5>
<h6>2018-09-29</h6>
<img src={logo} alt="Clima diario" className="imagebottom" />
<div className="minmax">
<p>
<span className="tempmax" />
26
</p>
<p>
<span className="tempmin" />
15
</p>
</div>
</div>
<div className="card">
<h5>Viernes</h5>
<h6>2018-09-30</h6>
<img src={logo} alt="Clima diario" className="imagebottom" />
<div className="minmax">
<p>
<span className="tempmax" />
26
</p>
<p>
<span className="tempmin" />
15
</p>
</div>
</div>
<div className="card">
<h5>Sábado</h5>
<h6>2018-10-01</h6>
<img src={logo} alt="Clima diario" className="imagebottom" />
<div className="minmax">
<p>
<span className="tempmax" />
26
</p>
<p>
<span className="tempmin" />
15
</p>
</div>
</div>
</div>
</div>
</div>
) : (
<div className="App">Cargando...</div>
)
}
}
export default App
Lo tenés igual? Parecido? Bueno, si ves que te trabaste con algo o no te sale, podés copiar y pegar o leer el código por algún gotcha de algo que no te esté saliendo 😁
Listo, ya tenemos la parte de arriba andando perfectamente y ahora vamos a atacar la parte de abajo. Analicémosla un poco antes...
La idea de React y los componentes es tener partes reutilizables de código, entonces, qué vemos que se repite y pueda reutilizarse en nuestro componente? Eso que se repite podemos separarlo en un componente aparte. Ya lo identificaste?