Tablón de Películas con React Native. PARTE 4 | Estilos y Componentes

Como desarrollador web tal vez estas acostumbrado a trabajar con los diferentes web frameworks que existen como Bootstrap, Foundation. Si eres desarrollador en React, tal vez estas mas familiarizado con web frameworks orientados a componentes, como React Bootstrap, Material-UI, pero a diferencia de estos, en React Native al carecer de el DOM y del CSS, tendemos que trabajar con el componente de StyleSheets que incluye React Native y que al principio puede que de miedo, pero con el paso del tiempo te das cuenta que es otro feature mas que podemos bien aprovechar. Lo bueno y lo malo es que en React Native, los “native frameworks”, (si podemos llamarlos así), apenas están naciendo, tenemos a lo mucho, dos frameworks bastante fuertes el primero es react-native-material-design, pero su documentación apenas es escasa, aunque tiene componentes bastante lindos para construir interfaces en Material Desgin, el segundo y el mas popular y mejor documentado es Native Base, que incluye estilos para iOS(Human), Android (Material).

Para fines de este curso, nos limitaremos a utilizar flexbox y los estilos de manera nativa, para demostrar los sencillo que es construir la interfaz sin la necesidad de librerías terceras.

Antes de comenzar, dejare esta Cheat Sheet de flexbox para tener una guía donde consular siempre


Primero necesito limpiar el código para comenzar a trabajar desde lo mínimo necesario

// src/App.js
import React, { Component } from 'react'
import { StyleSheet, Text, View, Image, ListView } from 'react-native'
import request from 'superagent'
const URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json'
class App extends Component {
constructor() {
super()
    this.requestSuperAgent = this.requestSuperAgent.bind(this)
    this.state = { loaded: false }
}
componentDidMount() {
this.requestSuperAgent()
}
requestSuperAgent() {
request
.get(URL)
.then(res => {
this.setState({loaded: true})
})
.catch(err => {
console.log(err)
})
}
render() {
return (
<View style={styles.container}>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
rightContainer: {
flex: 1,
},
title: {
fontSize: 20,
marginBottom: 8,
textAlign: 'center',
},
year: {
textAlign: 'center',
},
thumbnail: {
width: 53,
height: 81,
},
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
})
export default App

Te darás cuenta que agregue una función, this.setState, y this.state, en el constructor y el el callback de la petición asíncrona, esto se debe a que state, es una parte elemental de React, hace que tu aplicación funcione en tiempo real, ya que al actualizar el state en React actualizas todo el componente de manera “reactiva”, y es tan facil como declarar el state en el constructor, y cuando necesites que este se actualice solo tienes que llamar a la función setState(), un dato importante es que nunca deberías mutar el state

this.state.movies = nuevasMovies // Malo
this.setState({movies: nuevasMovies}) // bueno

En la anterior parte, “Network”, obtuvimos el response de un pequeño JSON, con el que trabajaremos ahora.

El modelo de la respuesta es el siguiente

{
"total": 24,
"movies": [
{
"id": "11494",
"title": "Chain Reaction",
"year": 1996,
"mpaa_rating": "PG-13",
"runtime": 106,
"release_dates": {
"theater": "1996-08-02",
"dvd": "2001-05-22"
},
"ratings": {
"critics_rating": "Rotten",
"critics_score": 16,
"audience_rating": "Spilled",
"audience_score": 27
},
"synopsis": "",
"posters": {
"thumbnail": "http://resizing.flixster.com/DeLpPTAwX3O2LszOpeaMHjbzuAw=/53x77/dkpu1ddg7pbsk.cloudfront.net/movie/11/16/47/11164719_ori.jpg",
"profile": "http://resizing.flixster.com/DeLpPTAwX3O2LszOpeaMHjbzuAw=/53x77/dkpu1ddg7pbsk.cloudfront.net/movie/11/16/47/11164719_ori.jpg",
"detailed": "http://resizing.flixster.com/DeLpPTAwX3O2LszOpeaMHjbzuAw=/53x77/dkpu1ddg7pbsk.cloudfront.net/movie/11/16/47/11164719_ori.jpg",
"original": "http://resizing.flixster.com/DeLpPTAwX3O2LszOpeaMHjbzuAw=/53x77/dkpu1ddg7pbsk.cloudfront.net/movie/11/16/47/11164719_ori.jpg"
}
]
}

Nos regresa un arreglo de “Peliculas”, el cual podemos aprobechar para un componente nativo de React Native, llamado ListView, el cual depende de un origen de datos o dataSource, y un método para renderizar cada una de las filas en la lista

/* el componente final*/
<ListView
dataSource={this.state.dataSource} // data source
renderRow={this.renderMovie} // metodo para renderizar
style={styles.listView}
/>

El data source viene de el callback por lo que tenemos que actualizar el estado cuando la petición se haga correctamente, pero primero tenemos que definir el método nativo para renderizar las filas, que se declara en el state, justo en el constructor

this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
})
, // este metodo renderiza las filas
loaded: false
}

entonces podemos actualizar el state cuando la petición se realice

requestSuperAgent() {
request
.get(URL)
.then(res => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(JSON.parse(res.text).movies),
loaded: true
})

})
.catch(err => {
console.log(err)
})
}

Cabe aclarar que el método que usamos lo tomamos del mismo state, y luego lo remplazamos por el mismo método, pero ya en función a renderizar la petición del callback

Posteriormente tenemos que agregar un método que renderizara cada una de las filas, aquí es donde podemos darle los estilos que queremos que tome cada fila

Este método recibe una y cada una de las películas en el ListView y retorna un componente como el que escribimos, con estilos, y la estructura que queramos que tenga

Igualmente, podemos acceder a cada uno de los indices de cada pelicula en particular, que como notaremos, recibe como parametro, el elemento en cuestion que va a renderizar la ListView (movie)

renderMovie(movie) {
return (
<View style={styles.container}>
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
<Text style={styles.title}>{movie.title}</Text>
<Text style={styles.year}>{movie.year}</Text>
</View>
</View>
)
}

Abras notado que igual estamos aprobechando un componente nativo de React Native llamado <Image>, que igual hay que importar de la librería como todos los demás, recibe como prop una ruta local o bien, una ruta de Internet, como hace el componente <img> en web, en este caso usamos la url de la imagen que nos retorna el json de facebook

Ahora solo falta agregar una condición que evalué si la petición ya ser resolvió y de ser así, que cargue la lista para evitar que la lista renderize en vacío y tire un error cuando aun no termina la petición asíncrona, para eso el el elemento loaded que si notaste estaba incluido en el state en los ejemplos pasados, que es un simple booleano. Esta condición la pondremos en el Render principal

render() {
if (!this.state.loaded) {
return this.renderLoadingView()
}
   return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderMovie}
style={styles.listView}
/>
)
}
renderLoadingView() {
return (
<View style={styles.container}>
<Text>
Cargando...
</Text>
</View>
)
}

Si todo salio bien hasta ahora, ya estas listo para hacer peticiones a internet, si tuviste errores no olvides revisar el código en Github, esta todo el código listo y actualizado