- Introducción del curso
- Configuracion del entorno
- Conceptos básicos
- Introducción sobre los conceptos básicos de React
- Introdución a JSX
- Componentes en ReactJS
- Entendiendo las props
- Funciones y elementos como props
- Inmutabilidad de las props
- Props por defecto
- Gestion del estado en ReactJS
- Actualizar estado mediante setState
- Propagación del estado
- Inicialización del estado mediante Props
- Renderizado condicional y listas
- React Developer Tools
- Eventos
- Formularios
- Children y PropTypes
- Ciclo de Vida de los Componentes
- Ciclo de actualización
- Ciclo de desmontaje
- Ciclo de Error
- Buenas Prácticas
- PROYECTO - Buscador de peliculas online
- Preparando el entorno de nuestra aplicación
- Creando el componente SearchFrom
- Usando Fetch para obtener resultados de busqueda desde una API
- Creando componentes reutilizables y mejorando el layout
- Mejoras en la implementación de búsqueda
- Enrutado básico
- Separando la página Home
- Creando una SPA con React Router
- Página 404
- Publicando con Surge
- Redux, gestionando el estado global de tu aplicación
- Proyectos de los estudiantes
https://www.udemy.com/course/aprendiendo-react/ Creado por: Miguel Duran
-
Install
- NodeJS
- Visual Studio Code
- Create React App
-
Create test app https://create-react-app.dev/
$>npx create-react-app my-app
$>npm start
- app.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
- Instalar plugin eslint
- Crear el fichero .eslintrc
{
"extends": "react-app"
}
- Declarativo: indicamos qué y cómo.
- Basado en componentes:
- Partes mas pequeñas de la interfaz.
- Cada componente encapsula su estado.
- Código más reusable, mantenible y pequeño.
- Programación reactiva: cambio de estado renderiza el componente.
- Virtual DOM: genera una copia del árbol de elementos del navegador para solo hacer los mínimos cambios necesarios para reflejar el estado de nuestros componentes.
- Eventos sintéticos
- Abstración de los eventos nativos de los navegadores
- Compatibilidad cross browsing sin necesidad de más librerías.
- Soporte para todos los eventos que puedas necesitar desde click y double click, hasta eventos móviles como touchstaert y touchend.
- Server Side Rendering el mismo código que se renderiza en el cliente lo tenemos en el servidor.
Es una sintaxis para generar componentes en React
https://babeljs.io/repl
const element = <h1>Hello, world</h1>
Se convierte en:
var element = React.createElement("h1", null, "Hello, world");
const element = <h1>{2 + 2}</h1>
Se convierte en:
var element = React.createElement("h1", null, 2 + 2);
function multiplicar(a,b) { return a * b }
const mostrar = true
const element = <h1>{mostrar ? multiplicar(2,3) : "nada que mostrar"}</h1>
Se convierte en:
function multiplicar(a, b) {
return a * b;
}
var mostrar = true;
var element = React.createElement("h1", null, mostrar ? multiplicar(2, 3) : "nada que mostrar");
const image = <img src="http://aaa/image.jpg"/>
Se convierte en:
var image = React.createElement("img", { src: "http://aaa/image.jpg" });
const element = <div><h1>Hola mundo</h1><h2>subtitulo</h2></div>
Se convierte en:
var element = React.createElement("div", null,
React.createElement("h1", null, "Hola mundo"),
React.createElement("h2", null, "subtitulo"));
Hay 3 formas de crear componentes: como funcion, como arrow function y como clase que hereda de Component.
- Componente como funcion
function Hello(props) {
return <h2>{props.title}</h2>
}
- Componente como Arrow Function
const HelloArrow = (props) => <h2>{props.title}</h2>
- Componente como clase que hereda de Component
class HelloComponent extends Component {
render() {
return <h2>{this.props.title}</h2>
}
}
- Usando los componentes
function App() {
return (
<div className="App">
<header className="App-header">
<Hello title="Ole ole ole"/>
<HelloArrow title="Ole ole ole ARROW"/>
<HelloComponent title="ole ole ole, COMPONENT!"></HelloComponent>
</header>
</div>
);
}
export default App;
Pasando arrays y objetos como props
<MyComponent
arrayOfProps={[3,6,9]}
person={{name:'Atanasio', age:43}}
isActivated={false}
multiply={(n) => n * 4}
number={33}
text="Titulo" ></MyComponent>
class MyComponent extends Component {
render() {
const isActivated = this.props.isActivated ? 'on' : 'off'
const arrayTranform = this.props.arrayOfProps.map(this.props.multiply)
return (
<div>
<span>{this.props.text}</span>
<span>{this.props.number}</span>
<span>{isActivated}</span>
<span>{arrayTranform.join(', ')} </span><br></br>
<span>nombre: {this.props.person.name} tiene {this.props.person.age}</span>
</div>
)
}
}
<MyComponent
arrayOfProps={[3,6,9]}
multiply={(n) => n*4}></MyComponent>
class MyComponent extends Component {
render() {
const arrayTranform = this.props.arrayOfProps.map(this.props.multiply)
return (
<div>
<span>{arrayTranform.join(', ')} </span><br></br>
</div>
)
}
}
const {isActivated, arrayOfProps, person, multiply} = this.props
class MyComponent extends Component {
render() {
const {title} = this.props
return (
<div>
{title}
</div>
)
}
}
<MyComponent
title={<h1>vamos ahi!</h1>}></MyComponent>
- No se pueden modificar las propiedades.
- Excepcion cuando se intenta modificar una property
TypeError: Assignment to constant variable.
class Title extends Component {
render() {
return <h3>{this.props.text}</h3>;
}
}
Title.defaultProps = {
text: 'titulo x defecto'
}
- Ejemplo de componente con estado:
class Contador extends Component {
constructor() {
super()
this.state = { contador: 1}
}
render() {
return <span>{this.state.contador}</span>
}
}
- React es declarativo y reactivo, al cambiar el estado se pinta el component.
class Contador extends Component {
constructor() {
super()
this.state = { contador: 1}
setInterval(() => {
this.setState({contador: this.state.contador + 1})
}, 1000)
}
render() {
return <span>{this.state.contador}</span>
}
}
- Cada cambio en el state padre provoca el renderiazado de todos los hijos.
- El flujo es uniderccional.
class Contador extends Component {
constructor(props) {
super(props)
this.state = { contador: this.props.contadorInitial}
setInterval(() => {
this.setState({contador: this.state.contador + 1})
}, 1000)
}
import React, {Component} from 'react'
class ComponenteA extends Component {
render() {
return <p>componente A</p>
}
}
class ComponenteB extends Component {
render() {
return <p>componente B</p>
}
}
function userConditional(mostrarA) {
if (mostrarA)
return <ComponenteA/>
return <ComponenteB/>
}
export default class ConditonalSection extends Component {
constructor() {
super()
this.state = {mostrarA: true}
}
render() {
return (
<div>
<h4>Conditional Rendering</h4>
{userConditional(this.state.mostrarA)}
</div>
)
}
}
const conditionalComponent = this.state.mostrarA ? <ComponenteA/> : <ComponenteB/>
class Lista extends Component {
render() {
const numbers = [1,2,3,4,5]
return (
<div>Lista:
{numbers.map((e, index) => {return <p key={index}>num: {e}</p>})}
</div>
)
}
}
- Definir objetos:
[
{
"id": "7f9b3b74-8721-11ea-bc55-0242ac130003",
"name": "CEED",
"company": "KIA"
},{
"id": "1d09517b-a124-4c1f-8a32-f317dd6b368e",
"name": "CARENS",
"company": "KIA"
},{
"id": "4ef53ef2-88e2-43d7-8c45-f12a9f2010c8",
"name": "CORSA",
"company": "OPEL"
}
]
- Definir Componentes
import cars from './data/cars.json'
class CarItem extends Component {
render() {
const {car, id} = this.props
return (
<li>
<p>Key: {id}</p>
<p>Car Company: {car.company}</p>
<p>Car Name: {car.name}</p>
<br/>
</li>
)
}
}
class ListaObjetos extends Component {
render() {
return (
<div><h3>Lista Objetos</h3>
<ul>
{cars.map(car => {return <CarItem id={car.id} car={car}/>})}
</ul>
</div>
)
}
}
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<h4>Eventos</h4>
<button onClick={() => alert("sorpresa!!")} >Click on me!!</button>
</div>
);
}
export default App;
- Eventos sintenticos: recubrimientos sobre los eventos nativos para que sean compatibles en distintos navegadores.
- https://es.reactjs.org/docs/handling-events.html
class App extends Component {
handleClick(e) {
//e es un evento sintetico.
console.log (e)
console.log (e.nativeEvent)
alert ('toma toma!!')
}
render() {
return (
<div className="App">
<h4>Eventos</h4>
<button onClick={this.handleClick} >Click on me!!</button>
</div>
);
}
}
- Hay que enlazar el evento con el contexto, esto se puede hacer:
- Añadiendo al constuctor del componente la instrucción de enlazar
this.handleMouseMove = this.handleMouseMove.bind(this)
- Usando una arrow function al declarar la funcion, ya que las arrow functions enlazan siempre el contexto desde el que se declaran.
class App extends Component {
constructor() {
super()
this.state = { mouseX: 0, mouseY: 0 }
//this.handleMouseMove = this.handleMouseMove.bind(this)
}
handleMouseMove = (e) => {
const {clientX, clientY} = e
this.setState({mouseX: clientX, mouseY: clientY})
}
render() {
return (
<div className="App">
<h4>Eventos</h4>
<button onClick={this.handleClick} >Click on me!!</button>
<div
onMouseMove={this.handleMouseMove}
style={{border:'1px solid #000', marginTop: 10, padding: 10}} >
<p>{this.state.mouseX}, {this.state.mouseY}</p>
</div>
</div>
);
}
}
-
https://github.com/AntonioDiaz/aprende_react/tree/master/03_formularios
-
Usar el componente formulario desde app.js
import Forms from './sections/forms';
function App() {
return (
<div className="App">
<Forms></Forms>
</div>
);
}
- Crear componente formulario
import React, {Component} from 'react'
export default class Forms extends Component {
//metodo para recoger los datos.
handleClick(e) {
e.preventDefault()
const name = document.getElementById('name').value
const twitter = document.getElementById('twitter').value
console.log({name, twitter})
}
render() {
return (
<div>
<h4>Fromularios</h4>
<form>
<p>
<label>Nombre</label>
<input type="text" id="name" placeholder="pon tu nombre"/>
</p>
<p>
<label>Twitter</label>
<input type="text" id="twitter" placeholder="pon tu Twitter"/>
</p>
<button onClick={this.handleClick}>Enviar</button>
</form>
</div>
)
}
}
- For the html pasa a ser htmlFor en react
<label htmlFor='name' >Nombre</label>
- Ref: recupera la referencia de un objeto en el arbol DOM.
- No se utiliza en formularios.
- Se utiliza para integrar librerias externas.
export default class Forms extends Component {
//metodo para recoger los datos.
handleClick = (e) => {
e.preventDefault()
const name = this.inputName.value
const twitter = document.getElementById('twitter').value
console.log({name, twitter})
}
render() {
return (
<div>
<h4>Fromularios</h4>
<form>
<p>
<label htmlFor='name' >Nombre</label>
<input
id="name"
placeholder="pon tu nombre"
ref={i => this.inputName = i}/>
</p>
....
handleChange = (e) => {
console.log("handleChange")
console.log(e.target.checked)
}
Steps:
- Crear las referencias.
- En el evento onchange asignar los valores a las referencias.
export default class Forms extends Component {
constructor() {
super()
this.state = {
inputName: "",
inputTwitter: "@",
inputTerms: true
}
}
//metodo para recoger los datos.
handleSubmit = (e) => {
e.preventDefault()
console.log(this.state)
}
handleChange = (e) => {
this.setState({inputTerms: e.target.checked})
}
render() {
return (
<div>
<h4>Fromularios</h4>
<form onSubmit={this.handleSubmit}>
<p>
<label htmlFor='name' >Nombre</label>
<input
id="name"
placeholder="pon tu nombre"
ref={i => this.inputName = i}
onChange={e => this.setState({inputName: e.target.value})}
value={this.state.inputName}/>
</p>
<p>
<label htmlFor="twitter">Twitter</label>
<input
id="twitter"
ref={i => this.inputTwitter = i}
onChange={e => this.setState({inputTwitter: e.target.value})}
type="text"
placeholder="pon tu Twitter"
value={this.state.inputTwitter}/>
</p>
<p>
<label>
<input
onChange={this.handleChange}
type="checkbox"
checked={this.state.inputTerms}/>
Aceptar condiciones
</label>
</p>
<button onClick={this.handleClick}>Enviar</button>
</form>
</div>
)
}
}
- Se utiliza para layouts reutilizables.
- {this.props.children}: para acceder al contenido de una etiqueta.
class Box extends Component {
render() {
return (
<div style={{border:'1px solid #000', margin:5, padding: 5}}>
{this.props.children}
</div>
);
}
}
function App() {
return (
<div className="App">
<h4>Children Props</h4>
<Box>Hola Layout 01</Box>
<Box>Hola Layout 02</Box>
</div>
);
}
- Article example:
class Article extends Component {
render() {
return(
<section>
<h2>{this.props.title}</h2>
<p><em>Escrito por {this.props.author}</em></p>
<Box>{this.props.date}</Box>
<article>
{this.props.children}
</article>
</section>
)
}
}
- Usando
<Article
author="Antoine"
date={new Date().toLocaleDateString()}
title="Articulo de Angular JS">
<p><strong>AngularJS</strong> (comúnmente llamado Angular.js o AngularJS 1), es un framework de JavaScript de código abierto, mantenido por Google, que se utiliza para crear y mantener aplicaciones web de una sola página.</p>
</Article>
- Instalar la librería:
npm install prop-types -SE
- Importarlas y declarar los tipos de datos y si son obligatorios.
import PropTypes from 'prop-types'
...
class Article extends Component {
static propTypes = {
author: PropTypes.string.isRequired
}
constructor(props) {
super(props)
}
render() {
...
}
}
- Ciclo de vida: fases de ejecución por las que pasa un componente de React.
- Hay 3 fases:
- Montaje
- Se ejecuta siempre y solo lo hace una vez
- Contruye el componente con su estado inicial
- Obtiene las props
- Bindeamos métodos de Clase
- Primera ejecución de render()
- Actualización
- Por defecto se ejecuta cada vez que recibe props o se actualiza su estado
- Podemos controlar cuando el componente necesita renderizarse de nuevo
- Desmontaje
- Eliminamos listener
- Eliminamos referencias al DOM
- Montaje
- Ciclo de montaje
- Se ejecuta siempre y sólo lo hace una vez
- Contruye el component en su estado inicial
- Obtiene las props
- Primera ejecución del método render
- Termina con el componente montado en el DOM
- Constructor por defecto
constructor(...args) {
super(...args)
- Ejemplo de constructor que inicializa estado y enlaza un método con el contexto. Esto último no es necesario si se declara el metodo con arrow function.
constructor(props) {
super(props)
this.state = { mensajeInicial: 'mensaje inicial' }
//this.handleClick = this.handleClick.bind(this)
}
- Arrow function:
handleClick = () => {
this.setState({mensajeInicial: 'nuevo mensaje'})
}
- Se ejecuta una vez
- Se invoca antes de montar el componente y antes del render
- Todavía no tenemos el componente disponible en el DOM
- Se recomienda usar el constructor en su lugar
- Se puede usar setState y no provoca otro render
- El único método obligatorio en nuestro componente
- Retorna los elementos que queremos mostrar en la interfaz
- No se debe llamar al setState, provocaría un loop infinito
- Se debe encargar de transformar los states y las props en una representacion visual en la aplicación
- Evitar operaciones y tranformaciones ya que penaliza el rendimiento de la aplicación
- Cuando devuelve null no reneriza nada
- Renderaizado condicional renderiza dependiendo del valor de alguna propiedad
- Fragmentos se puede devolver una lista, y se van a renderizar todos los elementos, hay que añadir una KEY
- Se ejecuta tras renderizar el componente
- Ya tendremos una representación en el DOM
- Aquí podemos añadir las llamadas para recuperar datos del servidor y escuchar eventos
- se puede usar el setState
componentDidMount(){
console.log('componentDidMount');
document.addEventListener('scroll', () => {
console.log(window.scrollY)
this.setState({scroll: window.scrollY})
})
}
Ejemplo que muestra la cotizacion del Bitcon en dolares, libras y euros. https://www.coindesk.com/api
class FetchExample extends Component {
state = { bpi: {} }
componentDidMount() {
fetch('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(res => res.json())
.then(data => {
const { bpi } = data
this.setState({bpi})
})
}
_renderCurrencies() {
const { bpi } = this.state
const currencies = Object.keys(bpi)
return currencies.map (currency =>
<div key={currency}>
1 BTC is {bpi[currency].rate}
<span>{currency}</span>
</div>
)
}
render() {
return (
<div>
<h4>Bitcoins Price Index</h4>
{this._renderCurrencies()}
</div>
)
}
}
-
https://github.com/AntonioDiaz/aprende_react/tree/master/06_ciclo_actualizacion
-
Está siempre en ejecución desde que se monta el componente y hasta que se desmonta.
-
Se ejecuta sólo cuando el componente va a recibir nuevas props (no cuando cambia el estado).
-
Útil cuando se usan las props para formar el state del componente.
-
Se puede usar el setState y a veces provoca otro render.
-
Se ejecuta cuando nuestro componente:
- Va a recibir nuevas props.
- Va a actualizar su state.
-
Determina si debe ejecutar el render
-
Actualiza el contenido del componente.
-
Se pueden hacer llamadas a servicios externos.
-
Se ejecuta cuando su componente padre le envia nuevas props, aunque sean iguales que el anterion.
import React, { Component } from 'react'
import PropTypes from 'prop-types'
const ANIMAL_IMAGES = {
dolphin: 'https://raw.githubusercontent.com/AntonioDiaz/aprende_react/master/docs/dolphin.jpg',
shark: 'https://raw.githubusercontent.com/AntonioDiaz/aprende_react/master/docs/shark.jpg',
whale: 'https://raw.githubusercontent.com/AntonioDiaz/aprende_react/master/docs/whale.jpg'
}
class AnimalImage extends Component {
state = { src: ANIMAL_IMAGES[this.props.animal] }
//se ejecuta siempre que llegan nuevas props (que pueden ser igual que las propiedades que teniamos).
componentWillReceiveProps(nextProps) {
this.setState({src: ANIMAL_IMAGES[nextProps.animal]})
}
render() {
return (
<div>
<p>Selected {this.props.animal}</p>
<img
alt={this.props.animal}
src={this.state.src}
width='250'/>
</div>
)
}
}
AnimalImage.prototypes = {
animal: PropTypes.oneOf(['dolphin', 'shark', 'whale'])
}
class UpdateLifeCycleExample extends Component {
state = {animal: 'dolphin', index: 3}
indexToAnimal (index) {
switch (index % 3) {
case 0: return 'dolphin'
case 1: return 'shark'
case 2: return 'whale'
default: return 'dolphin'
}
}
constructor() {
super()
setInterval(() => {
let newIndex = this.state.index + 1;
let newAnimal = this.indexToAnimal(newIndex)
this.setState({animal: newAnimal, index: this.state.index + 1})
}, 3000)
}
render() {
return (
<div>
<h4>Update cycle, ComponentWillReceiveProps</h4>
<AnimalImage animal={this.state.animal}></AnimalImage>
</div>
)
}
}
export default UpdateLifeCycleExample
import React, { Component } from 'react'
import PropTypes from 'prop-types'
const ANIMAL_IMAGES = {
dolphin: 'https://raw.githubusercontent.com/AntonioDiaz/aprende_react/master/docs/dolphin.jpg',
shark: 'https://raw.githubusercontent.com/AntonioDiaz/aprende_react/master/docs/shark.jpg',
whale: 'https://raw.githubusercontent.com/AntonioDiaz/aprende_react/master/docs/whale.jpg'
}
const ANIMAL_KEYS = Object.keys(ANIMAL_IMAGES)
class AnimalImage extends Component {
state = {
animalIndex: this.props.animalIndex
}
//se ejecuta siempre que llegan nuevas props (que pueden ser igual que las propiedades que teniamos).
componentWillReceiveProps(nextProps) {
this.setState({
animalIndex: nextProps.animalIndex
})
}
render() {
let animalName = ANIMAL_KEYS[this.state.animalIndex]
let animalUrl = ANIMAL_IMAGES[animalName]
console.log('render....' + this.props.animalIndex);
return (
<div>
<p>Selected {animalName}</p>
<img
alt={animalName}
src={animalUrl}
width='250'/>
</div>
)
}
}
AnimalImage.prototypes = {
animal: PropTypes.oneOf(ANIMAL_KEYS)
}
class UpdateLifeCycleExample extends Component {
state = {index: 0}
indexToAnimal = (myIndex) => {
return ANIMAL_KEYS[myIndex % ANIMAL_KEYS.length]
}
renderAnimalButton = (newAnimal) => {
let newAnimalIndex = ANIMAL_KEYS.indexOf(newAnimal);
let colorButton = '';
if (newAnimalIndex === this.state.index) {
colorButton = 'yellow'
}
return(
<button
key={newAnimalIndex}
onClick={()=> this.setState({index: newAnimalIndex})}
style={{background: colorButton}} >
{newAnimal}
</button>
)
}
constructor() {
super()
setInterval(() => {
let newIndex = (this.state.index + 1) % ANIMAL_KEYS.length;
this.setState({index: newIndex})
}, 10000)
}
render() {
return (
<div>
<h4>Update cycle, ComponentWillReceiveProps</h4>
{ANIMAL_KEYS.map(this.renderAnimalButton)}
<AnimalImage animalIndex={this.state.index}></AnimalImage>
</div>
)
}
}
- Se ejecuta antes de actualiza el componente.
- Determina si el componente se debe actualizar.
- Debe devolver un booleano (true por defecto), false no renderiza.
- No se debe llamar al setState.
shouldComponentUpdate(nextProps) {
return nextProps.animalIndex !== this.state.animalIndex
}
- Realiza una comparación superficial de las props o del state.
- Superficial: solo se puede usar cuando las props y el state sean simples.
- Importarlo de la libreria de react.
import React, { Component, PureComponent } from 'react'
- Extender de PureComponent en vez de Component
- Se puede borrar el metodo ShouldComponentUpdate.
- Se invoca antes del render, en caso que
- Hay que evitar actualizar el state.
componentWillUpdate(nextProps, nextState) {
const img = document.querySelector('img')
img.animate(
[{filter: 'blur(0px)'},{filter: 'blur(2px)'}],
{duration: 500, easing: 'ease'})
}
- Última fase del ciclo de actualización.
- Se ejecuta tras actualizar el componente.
- Permite ejecutar funciones de librerias externas, user el nuevo DOM o hacer llamadas externas.
componentDidUpdate(prevProps, prevState) {
const img = document.querySelector('img')
img.animate(
[{filter: 'blur(2px)'},{filter: 'blur(1px)'}],
{duration: 1500, easing: 'ease'})
}
- Se ejecuta sólo si el componente deja de renderizarse en la aplicación.
- Solo tiene una fase.
- Se utiliza para liberar recursos. Por ejemplo posibles suscripciones a eventos.
- No tiene sentido llamar al setState.
class ComponentToUnmount extends Component {
state = { windowWidth: 0 }
_updateStateWithWindowWidth = () => {
this.setState({windowWidth: document.body.clientWidth})
}
componentDidMount() {
this._updateStateWithWindowWidth()
window.addEventListener('resize', this._updateStateWithWindowWidth)
}
componentWillUnmount() {
window.removeEventListener('resize', this._updateStateWithWindowWidth)
}
render() {
return (
<div>
<p>Window width: {this.state.windowWidth}</p>
</div>
)
}
}
- Solo de ejecuta el método ComponentDidCatch
- Captura los posibles errores de los componentes que utilicemos en nuestro componente.
- No caputará los errores en los métodos disparados por eventos ni en el código asíncrono.
- Si un componente tiene un error no controlado se desmontará totalmente todo el componente de la aplicación.
- Recive dos parámetros:
componentDidCatch(error, info)
- Example:
import React, { Component } from 'react'
class ButtonThrowsException extends Component {
state = {throwError: false}
render() {
if (this.state.throwError) {
throw new Error('Error thrown by the button')
}
return (
<button onClick={()=>this.setState({throwError: true})}>throws exception</button>
)
}
}
class ComponentDidCatchExample extends Component {
state = {hasError: false, errorMsg: ''}
componentDidCatch(error, errorInfo) {
this.setState({hasError: true, errorMsg: error.toString()})
}
render() {
if (this.state.hasError) {
return (
<div>
ERRORACO: {this.state.errorMsg}
<br/>
<button onClick={()=>this.setState({hasError: false})}>change state so render again!!</button>
</div>
)
}
return (
<div>
ComponentDidCatchExample
<br/>
<ButtonThrowsException></ButtonThrowsException>
</div>
)
}
}
export default ComponentDidCatchExample
class MyButton extends Component {
constructor(props) {
super(props)
this.borderColor='blue'
}
render(){
return (
<button style={{borderColor: this.borderColor, display:'block'}}>
{this.props.label}
</button>
)
}
}
class MyButtonDanger extends MyButton {
constructor(props) {
super(props)
this.borderColor='red'
}
render(){
return (
<button style={{borderColor: this.borderColor, display:'block'}}>
{this.props.label}
</button>
)
}
}
class MyButtonWithLegend extends MyButton {
constructor(props) {
super(props)
}
render(){
return (
<div>
{super.render()}
<small>{this.props.legend}</small>
</div>
)
}
}
class App extends Component {
render() {
return (
<div className="App">
<h1>Composición VS Herencia</h1>
<MyButton label='father button'></MyButton>
<br/>
<MyButtonDanger label='danger button'></MyButtonDanger>
<br/>
<MyButtonWithLegend label='legend button' legend='Clicka el boton...'></MyButtonWithLegend>
</div>
);
}
}
class MyButton extends Component {
render(){
return (
<button style={{borderColor: this.props.borderColor, display:'block'}}>
{this.props.label}
</button>
)
}
}
MyButton.defaultProps = { borderColor: 'blue'}
class MyButtonDanger extends Component {
render(){
return (
<MyButton borderColor='red' label={this.props.label} />
)
}
}
class MyButtonWithLegend extends Component {
render(){
return (
<div>
<MyButton label={this.props.label} borderColor={this.props.borderColor}/>
<small>{this.props.legend}</small>
</div>
)
}
}
class App extends Component {
render() {
return (
<div className="App">
<h1>Composición VS Herencia</h1>
<MyButton borderColor='#335EFF' label='father button'></MyButton>
<MyButtonDanger label='danger button'></MyButtonDanger>
<MyButtonWithLegend borderColor='green' label='legend button' legend='do it now' ></MyButtonWithLegend>
</div>
);
}
}
- Si un componente no tiene state (stateless) y no usa ningún método del ciclo de vida se puede convertir en una función.
- Ejemplo con Article, las props ser reciben como parámetro
function Article(props) {
const {author, children, date, title} = props
return (
<section>
<h2>{title}</h2>
{author && <p><em>Escrito por {author}</em></p>}
<Box>{date}</Box>
<article>
{children}
</article>
</section>
)
}
- Ejemplo de Button
const Button = ({borderColor, label}) => (
<button style={{borderColor, display: 'block'}}>
{label}
</button>
)
- Proptypes
Article.propTypes = {
author: PropTypes.string.isRequired
}
- Default values
const Button = ({borderColor: 'red', label}) => (
<button style={{borderColor, display: 'block'}}>
{label}
</button>
)
-
https://github.com/AntonioDiaz/aprende_react/tree/master/08_patron_contenedor_contenido
-
Dividir los componentes en 2 categorias
- Tiene lógica, un state.
- Recupera los datos del servidor y hace las tranformaciones necesarias.
- Para adecuar los datos recibidos a las props del contenido.
-
Sólo se ocupa de representar los datos en un layout.
-
No se pueden tener llamadas externas.
-
Componentes puros.
-
index.js
import BitCoinPriceContainer from './container-component/container'
function App() {
return (
<div className="App">
<header className="App-header">
<BitCoinPriceContainer/>
</header>
</div>
);
}
- container.js
class BitCoinPriceContainer extends Component {
state = { bpi: {}}
componentDidMount() {
fetch('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(res => res.json())
.then(data => {
const { bpi } = data
this.setState({bpi})
})
}
render() {
return (
<BitCoinPrice bpi={this.state.bpi}/>
)
}
}
export default BitCoinPriceContainer
- presentational.js
import React from 'react';
const _renderCurrencies = (bpi) => {
return Object.keys(bpi).map (currency =>
<li key={currency}>
1 BTC is {bpi[currency].rate} _ <span>{currency}</span>
</li>
)
}
const BitCoinPrice = ({bpi}) => {
return (
<div>
<h4>Bitcoins Price Index</h4>
<ul>{_renderCurrencies(bpi)}</ul>
</div>
)
}
export default BitCoinPrice
- Componente Strict Mode sirve para estar seguro de que seguimos las mejores prácticas en React y que podemos actualizar a nuevas versiones de la librería.
- Disponible desde la versión 16.3
- Para utilizarlo hay que ir al fichero index.js de la aplicación, importarlo import React y envolver la aplicación dentro del tag <React.StrictMode>
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
- Create Project
$>npx create-react-app 09_buscador_peliculas
- Add Bulma dependences
$>npm install bulma --save --save-exact
- Start project
$>npm start
- Clean project and import Bulma:
import React from 'react';
import './App.css';
import 'bulma/css/bulma.css'
function App() {
return (
<div className="App">
Search Movies
</div>
);
}
export default App;
- Create Title component on path ./component/Title.js
import React from 'react'
export const Title = ({children}) => (
<h1 className="title">{children}</h1>
)
- Import and use the component
import {Title} from './components/Title'
- Add controller with 2 events.
import React, {Component} from 'react'
export default class SearchFrom extends Component {
state = {
inputMovie: ''
}
_handleChange = (e) => {
this.setState({inputMovie: e.target.value})
}
_handleSubmit = (e) => {
e.preventDefault()
alert (this.state.inputMovie)
}
render() {
return (
<form onSubmit={this._handleSubmit}>
<div className="field has-addons">
<div className="control">
<input
className="input"
onChange={this._handleChange}
type="text"
placeholder="Search Movies"></input>
</div>
<div className="control">
<button className="button is-info">Search</button>
</div>
</div>
</form>
);
}
}
- Add class to center the component on App.css
.App {
padding-top: 36px;
text-align: center;
}
.SearchForm-wrapper {
display: flex;
justify-content: center;
}
- Wrapp the component with a div and add the new class to the component.
import React from 'react';
import {Title} from './components/Title'
import SearchForm from './components/SearchForm.js'
import './App.css';
import 'bulma/css/bulma.css'
function App() {
return (
<div className="App">
<Title>Buscador de Pelis</Title>
<div className='SearchForm-wrapper'>
<SearchForm></SearchForm>
</div>
</div>
);
}
export default App;
OMDb API: http://omdbapi.com/ Pedir API key Add constant: API_KEY = 'ee453XXX'
- Component to access search the films:
import React, {Component} from 'react'
const API_KEY = 'ee4531xx'
export default class SearchFrom extends Component {
state = {
inputMovie: ''
}
_handleChange = (e) => {
this.setState({inputMovie: e.target.value})
}
_handleSubmit = (e) => {
e.preventDefault()
const {inputMovie} = this.state
const url = `http://www.omdbapi.com/?s=${inputMovie}&apikey=${API_KEY}`
fetch(url)
.then(res => res.json())
.then(results => {
const {Search, totalResults} = results
this.props.onResults(Search)
})
}
render() {
return (
<form onSubmit={this._handleSubmit}>
<div className="field has-addons">
<div className="control">
<input
className="input"
onChange={this._handleChange}
type="text"
placeholder="Search Movies"></input>
</div>
<div className="control">
<button className="button is-info">Search</button>
</div>
</div>
</form>
);
}
}
- Showing the results:
import React, {Component} from 'react';
import {Title} from './components/Title'
import SearchForm from './components/SearchForm.js'
import './App.css';
import 'bulma/css/bulma.css'
class App extends Component {
state = { results: [] }
_renderResults() {
const {results} = this.state
return results.map(
(movie, index)=> { return <div key={index}>{movie.Title}</div>})
}
_handleResults = (results) => {
this.setState({results})
}
render() {
return (
<div className="App">
<Title>Buscador de Pelis</Title>
<div className='SearchForm-wrapper'>
<SearchForm onResults={this._handleResults}></SearchForm>
</div>
<span>
{this.state.results.length === 0 ? "sin resultados" : this._renderResults()}
</span>
</div>
)
}
}
export default App;
- Movie
export class Movie extends Component {
static propTypes = {
title: PropTypes.string,
year: PropTypes.string,
poster: PropTypes.string
}
render() {
const {poster, title, year} = this.props
return (
<div className="card">
<div className="card-image">
<figure className="image">
<img src={poster} alt={title}></img>
</figure>
</div>
<div className="card-content">
<div className="media">
<div className="media-content">
<p className="title is-4">{title}</p>
<p className="subtitle is-6">{year}</p>
</div>
</div>
</div>
</div>
)}
}
- Movies List
export class MoviesList extends Component {
static propTypes = {
movies: PropTypes.array
}
render() {
const {movies} = this.props
return (
<div className='MoviesList'>
{
movies.map(movie => {
return (
<div key={movie.imdbID} className='MoviesList-item'>
<Movie
key={movie.imdbID}
title={movie.Title}
year={movie.Year}
poster={movie.Poster}/>
</div>
)
})
}
</div>
)
}
}
- Evitar mostrar "sin resultados" al cargar la página.
- Añadir un atributo al state y usarlo en el render para mostar o no el mensaje al inicio.
state = {usedSearch: false, results: [] }
- User search se pone a true cuando se realiza una busqueda.
_handleResults = (results) => {
this.setState({usedSearch:true, results})
}
- Evitar el error cuando la búsqueda no devuelve resultados.
- Hay que asignar un valor por defecto en el deconstructor del método _handleSubmit de SearchForm.
const {Search = [], totalResults = "0"} = results
- En Movie.js, cambiar el div por un href, añadir el id al propTypes
export class Movie extends Component {
static propTypes = {
id: PropTypes.string,
title: PropTypes.string,
year: PropTypes.string,
poster: PropTypes.string
}
render() {
const {id, poster, title, year} = this.props
return (
<a href={`?id=${id}`} className="card">
...
</a>
)}
}
- En MoviesList.js, para el atributo id a la etiqueta Movie
render() {
const {movies} = this.props
return (
<div className='MoviesList'>
{
movies.map(movie => {
return (
<div key={movie.imdbID} className='MoviesList-item'>
<Movie
id={movie.imdbID}
title={movie.Title}
year={movie.Year}
poster={movie.Poster}/>
</div>
)
})
}
</div>
)
}
}
- En App.js, si viene un parámetro "id" pintar la etiqueta Detail
render() {
const url = new URL(document.location)
const hasId = url.searchParams.has('id')
if (hasId) {
return <Detail id={url.searchParams.get('id')} ></Detail>
}
}
- Etiqueta Detail
import React, { Component } from 'react'
import PropTypes from 'prop-types'
const API_KEY = 'ee453171'
export class Detail extends Component {
static propTypes = {
id: PropTypes.string
}
state = {movie: {}}
_fetchMovie({id}) {
const url = `http://www.omdbapi.com/?i=${id}&apikey=${API_KEY}`
fetch(url)
.then(res => res.json())
.then(movie => {
console.log(movie)
this.setState({movie})
})
}
componentDidMount() {
const {id} = this.props
this._fetchMovie({id})
}
render() {
const {Title, Poster, Actors, Metascore, Plot} = this.state.movie
return (
<div>
<h1>{Title}</h1>
<img src={Poster} alt={Title}/>
<h3>{Actors}</h3>
<span>{Metascore}</span>
<p>{Plot}</p>
<button onClick={this._goBack}>Back</button>
</div>
)
}
}
- En App.js, checkear si hay que pintar el formulario o el detalle.
class App extends Component {
render() {
const url = new URL(document.location)
const page = url.searchParams.has('id')
? <Detail id={url.searchParams.get('id')} ></Detail>
: <HomePage></HomePage>
return page
}
}
- Crear el fichero Home.js, que renderiza el formulario y los resultados de las busquedas.
export class HomePage extends Component {
state = {usedSearch: false, results: [] }
_handleResults = (results) => {
this.setState({results, usedSearch:true})
}
_renderResults = () => {
return this.state.results.length === 0
? <p>No results 😢</p>
: <MoviesList movies={this.state.results}> </MoviesList>
}
render() {
return(
<div className="App">
<Title>Buscador de Pelis</Title>
<div className='SearchForm-wrapper'>
<SearchForm onResults={this._handleResults}></SearchForm>
</div>
<span>
{ this.state.usedSearch ? this._renderResults() : <small>Use the form to search movies</small> }
</span>
</div>
)
}
}
- Importar la libreria react-router: https://reactrouter.com/
- En index.js importar el component BrowserRouter y recubrir con él la etiqueta
import {BrowserRouter} from 'react-router-dom'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
- En App.js definir las rutas
import {Switch, Route} from 'react-router-dom';
class App extends Component {
render() {
const url = new URL(document.location)
const hasId = url.searchParams.has('id')
return (
<div className="App">
<Switch>
<Route exact path='/' component={HomePage} ></Route>
<Route path='/detail/:id' component={Detail}></Route>
</Switch>
</div>
)
}
}
- En Detail.js se obtiene el id
static propTypes = {
match: PropTypes.shape({
params: PropTypes.object,
isExact: PropTypes.bool,
path: PropTypes.string,
url: PropTypes.string
})
}
componentDidMount() {
console.log(this.props)
const {id} = this.props.match.params
this._fetchMovie({id})
}
- En Movie.js hay que cambiar el enlace por
<Link to={`/detail/${id}`} className="card"></Link>
- Crea la pagina NotFound.js
import React from 'react'
import ButtonBackToHome from '../components/ButtonBackToHome'
export const NotFound = () => (
<div>
<h2 className='title'>404 - No existe la página</h2>
<ButtonBackToHome></ButtonBackToHome>
</div>
)
- Crear el componente ButtonBackToHome.js
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
export default class ButtonBackToHome extends Component {
render() {
return (
<Link
className='button is-info'
to="/">
volver a la portada
</Link>
)
}
}
- Añadir el redireccionamiento por defecto a la pagina NotFound.js
render() {
const url = new URL(document.location)
const hasId = url.searchParams.has('id')
return (
<div className="App">
<Switch>
<Route exact path='/' component={HomePage} ></Route>
<Route path='/detail/:id' component={Detail}></Route>
<Route component={NotFound}></Route>
</Switch>
</div>
)
}
- Redux es un contenedor predecible del estado global de una aplicación en javascript.
- Basada en la arquitectura Flux de Facebook, pero más sencilla.
- Redux es una libreria externa.
- Conceptos básicos:
- El estado de tu aplicación se describe como un simple objeto.
- El estado es global y se puede recuperar desde cualquier parte de la vista.
- El estado no se puede modificar, hay que crear uno nuevo a partir del anterior.
- Los 3 principios de Redux.
- Única fuente de verdad: todo el estado de tu aplicación esta contenido en un único store.
- El estado es de solo lectura (inmutable): la única forma de modificar el estado es emitir una action que indique qué cambió.
- Lo cambios se realizan con funciones puras: para controlar como el store es modificado por las acciones se usan reducers puros.
- Diagrama
- Actions: objetos planos de javascript, tienen una propiedad "type" que inica el tipo de acción. Pueden contener otras propiedades que sirven como "payload" para poder realizar la acción.
{
type: 'ADD_TODO',
text: 'Aprender Redux'
}
- Actions Creators: funciones puras que devuelven Actions. Son útiles para evitar inconsitencias en el código y tener que escribir los objetos a mano.
function addTodo(text) {
return {
type: 'ADD_TODO',
payload: {text}
}
}
- Reducers funcion pura que recibe el estado anterior y la acción y, a partir de ellas, crea un nuevo estado de la aplicación.
function todoApp(state = initialState, action) {
switch (action.type)
case ADD_TODO:
return Object.assing({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
})
default:
return state
}
- Store almacena el estado global de la aplicación. Permite que el estado sea leído, que ten puedas suscribir a sus cambios y, lo más importante, que envíes acciones para crear un nuevo estado.
//cargamos la función para crear un store
import {createStore} from 'redux'
// cargamos nuestros reducers
import reducers from './reducers'
// creamos el store
const store = createStore(reducers)
- Obteniendo el state actual de la store
import store from './store.js'
console.log(store.getState())
- Suscribiéndote a los cambios del store
import store from './store.js'
// al suscribirte a la store, te devuelve una función para ejecutarla y desuscribirte más tarde.
const unsubscribe = store.subscribe (() => {
//esta funcion se ejecuta cuando haya una acutalizacion del state
console.log(store.getState())
//podríamos aquí actualizar nuestra aplicación con la nueva info
const {usuario} = store.getState()
document.getElementById('usuario').innerHTML = usuario
})
//podríamos usar el metodo unsubscribe mas tarde para elminiar la suscripción a la store
document.getElementById('cerrar').addEventListener('click', () => unsubscribe())
- Enviando actions para actualizar la store
Projecto: https://stackblitz.com/edit/js-us1wzf
Ejemplo
import store from './store.js';
//esto envia una accion a la store y actualizara el estado usando el reducer que hayas programado
store.dispatch({
type: 'ADD_TODO',
text: 'Aprender React con @midudev'
})
- index.html
<div> Contador: <span id='contador'>0</div>
<button id='incrementar'>+</button>
<button id='decrementar'>-</button>
- index.js
import {createStore} from 'redux'
const contador = document.getElementById('contador')
const decrementar = document.getElementById('decrementar')
const incrementar = document.getElementById('incrementar')
const INITIAL_STATE = { counter: 0 }
//reducer
function counterApp (state= INITIAL_STATE, action) {
switch (action.type) {
case 'INCREMENT':
return { counter: state.counter + 1 }
case 'DECREMENT':
return { counter: state.counter - 1 }
default:
return state;
}
}
const store = createStore(counterApp)
store.subscribe(() => {
const state = store.getState()
contador.innerText = state.counter
})
incrementar.addEventListener('click', () => {
store.dispatch({ type: 'INCREMENT' })
})
decrementar.addEventListener('click', () => {
store.dispatch({ type: 'DECREMENT' })
})
-
Componentes presentacionales: como ser ven los componentes
-
Componentes contenedores: lógica de los componentes
-
react-redux: libreria que facilita la conexión de React con Redux gracias a algunas utilidades que ofrece.
npm install --save react-redux
- Beneficios:
- Evitar la gestion manual de tener que pasar la store a todos nuestro componentes
- Leer el estado global desde cualquier logar de nuestro árbol de elementos
- Llamar a acciones desde cualquier componente