React

Jesús García

Objetivos del curso

Aprender a usar React

Entender por qué es interesante

- Dan Abramov

Consistency

Responsiveness

Latency

Navigation

Staleness

Entropy

Priority

Accesibility

Internationalization

Delivery

Resilience

Abstraction

React

The V in MVC

 A JavaScript library for building user interfaces 

Declarative

Component-based

Learn Once, Write Anywhere

Contenido

  1. Elementos, DOM Virtual y JSX

  2. Componentes y props

  3. Estado y eventos (hooks y clases)

  4. Estilos

  5. Tipos y testing

Elementos, DOM Virtual y JSX

HTML

const myElement = document.createElement("div");
myElement.textContent = "Hola Kairós reacters";

document.getElementById("app").appendChild(myElement);
const element = React.createElement(
  "div",
  { className: "" },
  "Hola Kairós reacters"
);
ReactDOM.render(element, document.getElementById("app"));
const myElement = <div>Hola Kairós reacters</div>;
ReactDOM.render(element, document.getElementById("app"));

React (sin JSX)

React (con JSX)

Podemos interpolar cualquier expresión de JavaScript en JSX

/* Conditional render */
const element =(
  <div>
    {
      userIsLoggedIn && <div>Welcome, {user.name}<div/>
    }
  </div>
);

/* Conditional render with ternary operator */
const element = (
  <div>
    {
      userIsLoggedIn ? <div>Welcome, {user.name}<div/> : <div>Hello, please log in<div/>
    }
  </div>
);

Booleans

Podemos interpolar cualquier expresión de JavaScript en JSX

/* Conditional render */
const element =(
  <div>
    {
      someArray.length > 0 && <div>Welcome<div/>
    }
  </div>
);

/* MAL - esto puede renderizar '0' */
const element = (
  <div>
    {
      someArray.length && <div>Welcome<div/>
    }
  </div>
);

Numbers

Podemos interpolar cualquier expresión de JavaScript en JSX

/* Un string se puede pasar como hijo. 
React se encarga de escaparlo para evitar ataques de cross-site scripting */

const element =(
  <div>
    Hola
  </div>
);

/* Un string también puede representar un caso de un 'enum' */

const size = 'small' //'medium', 'big'
const style = {
  small: {width: '10px', height: '10px'},
  medium: {width: '20px', height: '20px'}, 
  big: {width: '30px', height: '30px'} 
};

const element = (
  <div style={style[size]}/>
);

Strings

Podemos interpolar cualquier expresión de JavaScript en JSX

/* Un array de datos puede convertirse en un array de elementos de React
mediante la función map */

const people = [
  {id: '1', name: Jesús, age: 27}, 
  {id: '2', name: Marcos, age: 1}, 
  {id:'3', name: Vega, age: 0}
]

const element = (
  <div>
      {people.map(({id, name, age}) => (
        <div key={id}>
          Name: {name}
          Age: {age}
        </div>
      ))};
  </div>
);



Arrays

Podemos interpolar cualquier expresión de JavaScript en JSX

/*JSX*/

const someJsx = <div>Hello!</div>

const element = (
  <div>
      {someJsx};
  </div>
);


/*Componente*/

const MyGreetingComponent = () => <div>Hello!</div>

const element = (
  <div>
      <MyGreetingComponent/>;
  </div>
);


/*Función*/

JSX, componentes, funciones

Más recursos con JSX

/*elegir elemento dinámicamente*/

const Text = ({tag, children}) => {
  const Tag = tag
  return <Tag>{children}</Tag>
}


/*Varios componentes en un objeto*/

const Text = {
  small(){}
  medium(){}
  big(){}
}

const jsx = <Text.small>Hola</Text.small>

/*Props spread*/

const MyComponent = ({color, ...rest}) => <div style={{color}} {...rest}/>

Extra

La propiedad $$typeoff de los elementos de React sirve para prevenir ataques XSS

¿Por qué llama React a nuestros componentes en lugar de nosotros mismos?

Hay ventajas en esta inversión de control

Componentes

const MyComponent = (props) => (
  <div className={props.className}>
    {props.children}
  </div>
);

Expresar UIs con JavaScript en vez de templates

  • Potente, se puede usar lógica arbitraria y podemos reutilizar y componer igual que hacemos normalmente en JS
  • No tenemos que aprender otro lenguaje de marcado
  • Difícil de generar
  • Difícil comprobar si es correcto en tiempo de compilación
  • Más fácil introducir bugs

Principle of least power

1, 2 - Tim Berners-Lee

The choice of language is a common design choice. The low power end of the scale is typically simpler to design, implement and use...

...The reason for this is that the less powerful the language, the more you can do with the data stored in that language.  The Semantic Web is an attempt, largely, to map large quantities of existing data onto a common language so that the data can be analyzed in ways never dreamed of by its creators.

 I chose HTML not to be a programming language because I wanted different programs to do different things with it: present it differently, extract tables of contents, index it, and so on.

I wish we could retire the term “virtual DOM”. It made sense in 2013 because otherwise people assumed React creates DOM nodes on every render. But people rarely assume this today. “Virtual DOM” sounds like a workaround for some DOM issue. But that’s not what React is.

React is “value UI”. Its core principle is that UI is a value, just like a string or an array. You can keep it in a variable, pass it around, use JavaScript control flow with it, and so on. That expressiveness is the point — not some diffing to avoid applying changes to the DOM.

For example, if it was fast enough to always assign properties on every node update, we would just do that. We wouldn’t throw away our React apps because “DOM is fast now”. That’s nonsense. UI is a value. Function is the building block. Lazy calls (elements) are the glue.

Dan Abramov

Estado y eventos

Para añadir estado a un componente usamos clases

class MyComponent extends React.Component{
  constructor(props){
    super(props)
    //state siempre es un objeto
    this.state = {}
  }
  
  //lo mismo que el cuerpo de la función en un componente función
  //tiene acceso a this.state y this.props
  render(){
    /*...*/
  }
}

class MyComponent extends React.Component{
  state = {}
  
  render(){
    /*...*/
  }
}

Para actualizar el estado en respuesta a eventos de la interfaz pasamos handlers como props a nuestros elementos

class MyComponent extends React.Component {
  constructor(){
    super()
    this.state = {/*...*/}
    this.myMethod = this.myMethod.bind(this)
    /*...*/
  }
  myMethod(){/*...*/}
  render(){
    return (
      <button onClick={this.myMethod}>hola</button>
    )
  }
}

React tiene un sistema de eventos sintético con pooling. Los eventos se reutilizan, por lo que no se debe acceder a ellos de forma asíncrona.

Binding para los métodos de clase

  • Binding en el constructor
class MyComponent extends React.Component {
  constructor(){
    super()
    this.state = {/*...*/}
    this.myMethod = this.myMethod.bind(this)
    /*...*/
  }
  myMethod(){/*...*/}
  render(){
    return (
      <button onClick={this.myMethod}>hola</button>
    )
  }
}
  • Binding inline
  •  
class MyComponent extends React.Component {
  constructor(){
    super()
    this.state = {/*...*/}
    /*...*/
  }
  myMethod(){/*...*/}
  render(){
    return (
      <button onClick={this.myMethod.bind(this)}>hola</button>
    )
  }
}

Binding para los métodos de clase

  • arrow function
class MyComponent extends React.Component {
  constructor(){
    super()
    this.state = {/*...*/}
  }
  myMethod(){/*...*/}
  render(){
    return (
      <button onClick={e => this.myMethod(e)}>hola</button>
    )
  }
}
  • class properties FTW
class MyComponent extends React.Component {
  constructor(){
    super()
    this.state = {/*...*/}
  }
  myMethod = () => {/*...*/}
  render(){
    return (
      <button onClick={this.myMethod}>hola</button>
    )
  }
}
  • El estado es inmutable

setState :: (Object | Function, [Function]) -> void

  • Lo actualizamos mediante el método asíncrono setState

setState

class MyComponent extends React.Component {
    state = {thing: 1, anotherThing: 2, yetAnotherThing: 3}
    
    updateThing = () => {
      //shallow merge of object with previous state
      this.setState({
        thing: 4
      })
    }

    updateAnotherThing = () => {
      this.setState(prevState => {
        //return a new object to shallow merge
        return {
          anotherThing: 5
        }
      })
    }

    updateYetAnotherThingBasedOnSomeEvent = (e) => {
      const value = e.target.value
      if (someCondition(value)){
          this.setState(prevState => {
          //WARNING: do not access the event (e) in here
          //The event may already be null - we save the value above
          return {
            ...prevState,
            yetAnotherThing: 5
          }
        }, () => {
            /*Here this.state has been updated*/
        })
      }
    }
}

function CounterWithHook({ initialValue }) {
  const [count, setCount] = React.useState(0)
  function increment() {
    setCount(oldCount => oldCount + 1)
  }
  const double = count * 2
  return (
    <div>
      <div>count: {count}</div>
      <div>double: {double}</div>
      <button onClick={increment}>increment</button>
    </div>
  )
}

useState hook

Formularios

Se puede tener un formulario "normal" en React

const UncontrolledForm = () => (
  <form>
    <label>
      Email
      <input type="text" name="email" />
    </label>
    <label>
      Password
      <input type="text" name="password" />
    </label>
    <input type="submit" value="Submit" />
  </form>
);

Pero lo recomendable es usar "controlled components" para tener el estado en JS

class ControlledForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = { email: '', password: '' };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(e) {
    const { name, value } = e.target;
    this.setState({ [name]: value });
  }

  handleSubmit(e) {
    e.preventDefault(); //evitar el refresco de la página
    //Hacer algo con this.state
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Email
          <input type="text" name="email" value={this.state.email} onChange={this.handleChange}/>
        </label>
        <label>
          Password
          <input type="text" name="password" value={this.state.password} onChange={this.handleChange}/>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

De esta forma, React se convierte en la fuente de la verdad

Tener el estado en JS nos permite aplicar validaciones, formateos, etc

//textarea HTML
<textarea>
  Contenido de la textarea
</textarea>
//textarea React
<textarea value={this.state.value} onChange={this.handleChange} />
//select HTML

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>
//select React

<select value={this.state.value} onChange={this.handleChange}>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

Refs

React nos abstrae de manipular el DOM, pero a veces necesitamos acceder a un elemento para, por ejemplo, darle el foco.

Todos los elementos aceptan la prop reservada "ref"

//1. Forma - React.createRef()

class MyComponent extends React.Component {
  constructor(props) {
    super(props);

    this.inputRef = React.createRef();
  }

  render() {
    return <input type="text" ref={this.inputRef} />;
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }
}
//2. forma (antigua) - callback ref

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return <input type="text" ref={input => {
     input && this.inputRef = input
    }} />;
  }

  componentDidMount() {
    this.inputRef.focus();
  }
}

Estilos

Una breve historia de CSS

  • 1991 - Tim Berners-Lee propone HTML
  • 1991-1996 Surgen diversas propuestas de lenguajes de estilos
  • 1996 - Se publica la primera versión del spec de CSS
  • 1998-99 se publican los specs de CSS 2 y los primeros drafts de CSS3
  • 2000 Los navegadores soportan CSS "completamente"
  • 2009 se publica la primera version del spec de FlexBox

Problemas con CSS

CSS fue ideado para estilar documentos, y da problemas cuando se usa para estilar aplicaciones a gran escala

  • Estilos globales
  • Difícil reusar
  • Se recurre a selectores complejos e !important

Sharing does not imply endorsement :P

Otros: Atomic CSS, OOCSS, SMACSS

CSS-in-JS

Typechecking y Testing

 

Higiene básica

 

Linter - ESLint

Formatter - Prettier

Git Hooks para automatizar

Convención commits

Higiene básica

 

$ npm install --save-dev eslint prettier eslint-config-prettier lint-staged husky

//.eslintrc
{
  "parserOptions": {
    "ecmaVersion": "2018"
  },
  "extends": [
    "eslint:recommended", "eslint-config-prettier"
  ],
  "rules": {
  },
  "env": {
      "browser": true
  }
}

//.prettierrc

{
  "arrowParens": "avoid",
  "bracketSpacing": false,
  "jsxBracketSameLine": false,
  "printWidth": 80,
  "proseWrap": "always",
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "useTabs": false
}

*

* no instalar eslint con Create React App - viene incluida

//.lintstagedrc

{
  "linters": {
    "*.js": [
      "eslint"
    ],
    "*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx|md|graphql|mdx)": [
      "prettier --write",
      "git add"
    ]
  }
}

//package.json

{
  "scripts": {
    "lint": "eslint src",
    "format": "prettier --write \"**/*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx|md|mdx|graphql|vue)\"",
  }
},
{
  "husky": {
    "hooks": {
      "pre-commit": "npm test && lint-staged",
      "pre-push": "npm test"
    }
  }
}

Contratos en runtime - lanzan warnings por consola

//Componente función

import PropTypes from "prop-types" 

function MyComponent(props){
  return (
    <div>
      Hi, my name is {name} 
      and I am {age} years old
    </div>
  )
}

MyComponent.defaultProps = {
  name: "Hinclimer",
  age: 3
}

MyComponent.propTypes = {
  name: PropTypes.string,
  age: PropTypes.number
}
//Componente clase

import PropTypes from "prop-types" 

class MyComponent extends React.Component {
  static defaultProps = {
    name: "Hinclimer",
    age: 3
  }
  static propTypes = {
    name: PropTypes.string,
    age: PropTypes.number
  }
  render(){
    return (
      <div>
        Hi, my name is {name} 
        and I am {age} years old
      </div>
    )
  }
}
//Ejemplos de reutilización de propTypes

import PropTypes from 'prop-types';
import { Image } from 'react-native';

export const childrenType = PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
    PropTypes.func
]);

export const styleType = PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.array,
    PropTypes.number
]);

export const apiDefaultProp = {
    loading : false,
    retry   : () => {},
    error   : null
};

export const imageSourceType = Image.propTypes.source;

//Para componentes que acepten un único hijo
MyComponent.propTypes = {
  children: PropTypes.element.isRequired
};
export interface HelloProps { compiler: string; framework: string; }

export const Hello = (props: HelloProps) => 
  <h1>Hello from {props.compiler} and {props.framework}!</h1>;

TypeScript

export interface HelloProps { compiler: string; framework: string; }

// 'HelloProps' describes the shape of props.
// State is never set so we use the '{}' type.
export class Hello extends React.Component<HelloProps, {}> {
    render() {
        return <h1>Hello from {this.props.compiler} and {this.props.framework}!</h1>;
    }
}

Testing

¿Cómo debemos testear una interfaz?

¿Cómo debemos testear ?

TDD, ATDD, Unit Tests, Integration Tests, End-to-End tests, Visual Regression Tests, Snapshots, ...

  • Librería recomendada oficialmente
  • Sencilla, provee assertions para buscar y hacer comprobaciones en el DOM
  • Enfocada a tests que simulen la interacción de un usuario con la interfaz
it('calls "onClick" prop on button click', () => {
  //usamos un mock de jest para onClick
  const onClick = jest.fn();

  // rtl nos provee la función render, que nos devuelve utilidades
  // para interactuar con el "DOM" del componente renderizado
  const { getByText } = render(<button onClick={onClick}>click me</button>);

  fireEvent.click(getByText(/click me/i));
  expect(onClick).toHaveBeenCalled();
});
  • Un tipo de assertion para los tests
  • Se generan archivos .snap que se deben commitear
  • El test únicamente comprueba que no ha cambiado el archivo: debemos validar cada cambio
  • Típicamente usados para testear el output de los componentes de React, pero tienen más posibilidades
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.facebook.com">Facebook</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});
exports[`renders correctly 1`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Facebook
</a>
`;
Made with Slides.com