Skip to content

AntonioDiaz/aprende_react

Repository files navigation

Aprender React JS

Introducción del curso

https://www.udemy.com/course/aprendiendo-react/ Creado por: Miguel Duran

Configuracion del entorno

$>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;

Linter

  • Instalar plugin eslint
  • Crear el fichero .eslintrc
{
    "extends": "react-app"
}

Conceptos básicos

Introducción sobre los conceptos básicos de React

  • 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.

Introdución a JSX

Es una sintaxis para generar componentes en React https://babeljs.io/repl compiler

Ejemplo 01

const element = <h1>Hello, world</h1>

Se convierte en:

var element = React.createElement("h1", null, "Hello, world");

Ejemplo 02: expresión

const element = <h1>{2 + 2}</h1>

Se convierte en:

var element = React.createElement("h1", null, 2 + 2);

Ejemplo 03: funciones

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");

Ejemplo 04: atributos

const image = <img src="http://aaa/image.jpg"/>

Se convierte en:

var image = React.createElement("img", { src: "http://aaa/image.jpg" });

Ejemplo 05: two elements

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"));

Componentes en ReactJS

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;

Entendiendo las props

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> &nbsp;
        <span>{this.props.number}</span> &nbsp;
        <span>{isActivated}</span> &nbsp;
        <span>{arrayTranform.join(', ')} </span><br></br>
        <span>nombre: {this.props.person.name} tiene {this.props.person.age}</span>
      </div>
    )
  }
}

Funciones y elementos como props

Funcion para multiplicar

<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>
    )
  }
}

Patrón deconstruccion

    const {isActivated, arrayOfProps, person, multiply} = this.props

Renderizar components dentro de componentes

class MyComponent extends Component {
  render() {
    const {title} = this.props
    return (
      <div>
        {title}
      </div>
    )
  }
}
<MyComponent 
  title={<h1>vamos ahi!</h1>}></MyComponent>

Inmutabilidad de las props

  • No se pueden modificar las propiedades.
  • Excepcion cuando se intenta modificar una property

TypeError: Assignment to constant variable.

Props por defecto

class Title extends Component {
  render() {
    return <h3>{this.props.text}</h3>;
  }
}

Title.defaultProps = {
  text: 'titulo x defecto'
}

Gestion del estado en ReactJS

  • Ejemplo de componente con estado:
class Contador extends Component {
  constructor() {
    super()
    this.state = { contador: 1}
  }

  render() {
    return <span>{this.state.contador}</span>
  }
}

Actualizar estado mediante setState()

  • 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>
  }
}

Propagación del estado

  • Cada cambio en el state padre provoca el renderiazado de todos los hijos.
  • El flujo es uniderccional.

Inicialización del estado mediante Props

class Contador extends Component {
  constructor(props) {
    super(props)
    this.state = { contador: this.props.contadorInitial}
    setInterval(() => {
      this.setState({contador: this.state.contador + 1})
    }, 1000)
  }

Renderizado condicional y listas

Condicionales en el método Render

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>
        )
    }
}

Utilizando ternarias

const conditionalComponent = this.state.mostrarA ? <ComponenteA/> : <ComponenteB/>

Trabajando con listas

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>
    )
  }
}

Listas de objetos

  • 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>
    )
  }
}

React Developer Tools

Eventos

El evento onClick

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 sintéticos

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>
      );
    }
}

Eventos sportados

  • 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>
    );
  }
}

Formularios

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>
        )
    }
}

Particularidades del atributo For

  • For the html pasa a ser htmlFor en react
<label htmlFor='name' >Nombre</label> 

Entendiendo las Refs

  • 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>
      ....

El evento onSubmit

handleChange = (e) => {
    console.log("handleChange")
    console.log(e.target.checked)
}

Componentes controlados

Steps:

  1. Crear las referencias.
  2. 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>
        )
    }
}

Children y PropTypes

La prop especial Children

  • 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>
  );
}

Children Layout

  • 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>  

Desarrollando con PropTypes

  • 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 de los Componentes

Fases

  • 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

El Constructor

  • 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'})
}

ComponentWillMount

  • 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

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

ComponentDidMount

  • 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})
    })       
}

Fetch API

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>
        )
    }
}

Ciclo de actualización

ComponentWillReceiveProps

  • 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

Refactor de ComponentWillReceiveProps

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>
        )
    }
}

ShouldComponentUpdate

  • 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
}

ShouldComponentUpdate con PureComponent

  • 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.

ComponentWillUpdate

  • 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'})
}

ComponentDidUpdate

  • Ú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'})
}

Ciclo de desmontaje

  • Se ejecuta sólo si el componente deja de renderizarse en la aplicación.
  • Solo tiene una fase.

ComponentDidUnmount

  • 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>
        )
    }
}

Ciclo de Error

  • 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)

ComponentDidCatch

  • 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

Buenas Prácticas

Composición vs herencia

Ejemplo herencia

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>
    );
  } 
}

Ejemplo composición

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>
    );
  } 
}

Componentes funcionales puros (Stateless components)

  • 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 en stateles components

  • Proptypes
Article.propTypes = {
  author: PropTypes.string.isRequired
}
  • Default values
const Button = ({borderColor: 'red', label}) => (
  <button style={{borderColor, display: 'block'}}>
    {label}
  </button>
)

Patrón contenedor contenido

Contenedor o listos o lógicos

  • 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.

Contenido o tontos o presentacionales

  • Sólo se ocupa de representar los datos en un layout.

  • No se pueden tener llamadas externas.

  • Componentes puros.

  • Diagrama
    compiler

  • Diagrama Bitcoin
    compiler

  • 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

  • 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')
);

PROYECTO - Buscador de peliculas online

Preparando el entorno de nuestra aplicación

  • 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'

Creando el componente SearchFrom

  • 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;

Usando Fetch para obtener resultados de busqueda desde una API

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;

Creando componentes reutilizables y mejorando el layout

  • 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>
        )
    }
}

Mejoras en la implementación de búsqueda

  • 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

Enrutado básico

  • 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>
          )
    }
}

Separando la página Home

  • 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>
        )
    }
}

Creando una SPA con React Router

  • 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>

Página 404

  • 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>
    )
  }

Publicando con Surge

https://surge.sh/

Redux, gestionando el estado global de tu aplicación

¿Qué es Redux?

  • 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.
    1. Única fuente de verdad: todo el estado de tu aplicación esta contenido en un único store.
    2. El estado es de solo lectura (inmutable): la única forma de modificar el estado es emitir una action que indique qué cambió.
    3. Lo cambios se realizan con funciones puras: para controlar como el store es modificado por las acciones se usan reducers puros.
  • Diagrama redux_diagram

Conceptos de Redux

  • 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())
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'
})

Contador en Redux

  • 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' })
})

API de React Redux

  • 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

Ejercicio práctico de React Redux

Proyectos de los estudiantes

About

curso Udemy Aprende React

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published