/notes-react-basics

Notas sobre conceptos básicos de React

MIT LicenseMIT

React: Conceptos Básicos

Contenido


React Element

Es una representación liviana, a través de un objeto plano de JS, de un nodo real del DOM. Utilizar estos objetos, más livianos que un nodo real, son los que le permiten a React crear lo que se conoce como virtual DOM, resultando más eficiente y minimizando la cantidad de operaciones en el DOM real.

React luego va a utilizar esta descripción para crear un nodo real en el DOM y actualizarlo cuando corresponda, con los atributos que necesite.

const element = React.createElement(
  'div',
  { id: 'login-btn' },
  'Login'
)

// el código de arriba retorna esta representación de un nodo del DOM
{
  type: 'div',
  props: {
    children: 'Login',
    id: 'login-btn'
  }
}

↑ Ir al inicio

React Component

Un componente es un bloque de código reutilizable e independiente, una pieza de UI con contenido, estilos y comportamiento definidos. También podemos definirlo como una función o clase, que, opcionalmente acepta algún input y retorna un React Element.

En React, cada parte de la UI es un componente y cada componente tiene un estado.

Idealmente, cada componente debería encargarse de una sola cosa. Si crece demasiado, puede a su vez dividirse en sub-componentes. Es decir, aplicamos el mismo criterio que utilizamos a la hora de crear funciones u objetos en general.

👉 La UI de nuestra aplicación va a terminar estando definida entonces como un árbol de componentes.

↑ Ir al inicio

Props

Los componentes reciben valores a través de diferentes parámetros a los que llamamos props (por propiedades) y retornan el código necesario (usando JSX) para renderizar los componentes.

Podríamos decir que funcionan de forma similar a los atributos HTML, sólo que, en este caso, escribimos JSX en lugar de HTML y las props pueden ser cualquier expresión válida en JS. Estos valores (props) podrán ser luego utilizados por el componente o pasados a un child component.

👉 Las props son inmutables y siempre se pasan de componentes superiores a componentes inferiores[1], dicho de otra forma, desde un componente padre (parent component) hacia un componente hijo (child component).

↑ Ir al inicio

State

Un componente puede simplemente recibir props y renderizarse.

Por otro lado, los componentes también pueden poseer un state, un objeto de JavaScript que se utiliza para almacenar información (características propias) acerca del componente y que puede cambiar a lo largo del tiempo. Estos cambios suelen ser resultado de diferentes eventos producidos por los usuarios (input en un formulario, click sobre un elemento, etc) o el sistema (obtener el resultado de hacer un request a una API, etc).

El state se crea dentro del componente, usando el constructor, para setear su valor inicial (el cual también puede obtenerse a partir de las props que reciba el componente).

constructor(props) {
  super(props);
  
  this.state = {
    count: 0,
  };
}

El state se va a utilizar entonces para interactividad, es decir, datos que cambian a través del tiempo.

👉 Recordermos que la vista es una función del estado: cuando el estado cambia, la vista se vuelve a renderizar. Por lo tanto, si queremos que la vista (UI) se actualice, tenemos que modificar el estado de alguna forma.

Si necesitamos compartir el state de un componente con otros, lo pasamos por props, siempre de arriba hacia abajo, de un parent a un child component. En la práctica, esto implica que el componente raíz (root, el que se encuentra arriba de todo en el árbol de componentes) de la aplicación necesita tener acceso a este valor en su state.

Resumidamente, podríamos decir que el state sirve para preservar valores entre renders y triggerear re-renders del componente cuando este se actualiza.

↑ Ir al inicio

Props vs State

Tanto las props como el state tienen características comunes:

  • son objetos de JS.
  • ambos contienen información que influye en el resultado del render.
  • pueden tener valores por default.
  • pueden ser accedidos mediante this.props o this.state, pero estas propiedades son de sólo lectura (tenemos que utilizar el método setState para poder modificar el estado).

Entre las diferencias, encontramos

Props:

  • se pasan a un componente, siempre de arriba hacia abajo.
  • son valores de configuración. Podemos pensar en ellos como si se tratase de los argumentos que recibe una función o los atributos de un elemento HTML.
  • son inmutables, no podemos modificar el valor de una prop desde el componente que la recibe.

State:

  • se administra dentro de un componente, similar a como funcionan las variables declaradas dentro de una función.
  • un componente maneja su propio estado internamente, pero no modifica el estado de sus child components. Podríamos decir que el state es privado y el componente mismo se encarga de inicializarlo y modificarlo.
  • siempre debe tener un valor inicial (default).
  • es mutable, pero sólo puede modificarse por el componente que lo contiene, utilizando setState.

Es conveniente, en lo posible trabajar con componentes stateless, ya que manejar el state agrega complejidad a nuestra aplicación.

👉 Ver más detalles en ReactJS: Props vs. State.

↑ Ir al inicio

Debe un componente tener estado?

El state es totalmente opcional. Dado que agregarlo agrega complejidad a la aplicación, es preferente utilizar, siempre que sea posible, componentes sin estado.

👉 Si un componente necesita hacer uso de cierta información que puede cambiar entre renders, información que el componente mismo puede crear y modificar, vamos a utilizar componentes con estado.

↑ Ir al inicio

JSX

JSX nos permite utilizar una sintaxis similar a HTML en aplicaciones React, que pueda convivir con código JavaScript en un mismo archivo .js. Babel luego va a transformar (transpilar) este código y transformarlo en código JavaScript standard que el engine (por ejemplo del browser) puede entender y ejecutar. Por lo tanto, JSX nos permite escribir HTML dentro de JavaScript.

👉 Podemos escribir cualquier expresión válida en JS dentro de llaves ({}) y esta será evaluada.

Por ejemplo

<a className='App-link' href={mdnLink} target='_blank' rel='noopener noreferrer'>
  Learn {jsFirst}
</a>

donde mdnLink y jsFirst son strings que recibimos como props ó a través del state.

Algunos detalles a tener en cuenta:

  • JSX no es un template sino una extensión de la sintaxis de JS.
  • JSX existe de forma separada de React.
  • <foo-bar /> mientras que <foo-bar> no. A diferencia de HTML, en JSX tenemos que cerrar los tags siempre.
  • para los atributos de clase, usamos className en lugar de class, ya que class es una keyword de JS que usamos para construir clases (OOP).

👉 Para más detalles, ver JSX in Depth.

↑ Ir al inicio

Comentarios

No podemos utilizar comentarios de HTML directamente dentro de JSX, porque pueden interpretarse como nodos del DOM.

❌ esto no funciona!

render() {
  return (
    <div>
      <!-- This doesn't work! -->
    </div>
  );
}

Si queremos utilizar comentarios entonces, debemos escribir comentarios de JS de la forma /* */, dentro de llaves { } (los comentarios con // no funcionan, sólo /* */).

✅ esta es la forma correcta.

render() {
  return (
    <div>
      { /* This works! */}
    </div>
  );
}

También podemos escribir comentarios multi-línea, de la forma

render() {
  return (
    <div>
      {/* 
        Multi
        line
        comment
      */}
    </div>
  );
}

👉 Notar que los comentarios deben siempre incluirse dentro de otro elemento. Si queremos escribirlos de forma separada, podemos utilizar Fragments.

Ejemplo con comentario suelto, utilizando Fragments.

render() {
  return (
    <>
      {/* 
        hi
        there
      */}
      <p>Hello, React dev!</p>
    </>
  );
}

↑ Ir al inicio

Tipos de componentes

Functional o Stateless Components

👉 Sólo tienen props, (no state). Toda la lógica de este tipo de componentes depende únicamente de las props que reciben, por lo tanto, son mucho más simples de entender (y testear).

En React, los Stateless Components se definen utilizando funciones.

Estas funcionen reciben props como parámetro y, a diferencia de los class components, no utilizan this para acceder a las mismas

function HelloWorld (props) {
  return <div>Hello {props.name}</div>;
}

Siempre van a renderizar el mismo output dado el mismo input, es por esto que se los conoce como componentes funcionales o puros, ya que vamos a definirlos utilizando funciones puras.

↑ Ir al inicio

Class o Stateful Components

👉 Tienen props y state. También se los conoce a veces como state managers. Se encargan por ejemplo, de la comunicación cliente-servidor (HTTP requests), procesar datos y reaccionar a eventos del usuario. Toda esta lógica va a manejarse dentro de Stateful Components, mientras que lo relacionado a la visualización y formato de la misma debería delegarse a Stateless Components, para reducir la complejidad.

En React, los Stateful Components se definen utilizando clases. Podemos escribir clases que retornen HTML.

Usamos Class Components si necesitamos acceder al state.

Primero necesitamos importar Component,

import React, { Component } from 'react';

el cual vamos a extender para escribir nuestras propias clases.

class App extends Component {
  render() {
    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>
    );
  }
}

↑ Ir al inicio

Accediendo al state

Para acceder al state desde una clase, llamamos a super desde el constructor. Recordemos que App extiende ComponentReact.Component, dependiendo de cómo lo importemos), por lo que al hacer esto, estamos llamando al constructor de Component.

No podemos utilizar this dentro de una clase que extiende a otra sin anter llamar a super(props).

Ahora tenemos acceso a la propiedad state y podemos modificar este objeto, setearle valores, etc.

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      jsFirst: 'JavaScript',
      mdnLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript'
    };
  }
  render() {
    const { jsFirst, mdnLink } = this.state;

    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={mdnLink} target='_blank' rel='noopener noreferrer'>
            Learn {jsFirst}
          </a>
        </header>
      </div>
    );
  }
}

↑ Ir al inicio

Modificando el state

La forma que tenemos de modificar el state en un Class Component es a través del método setState (que proviene de Component).

👉 Por qué utilizamos este método y no podemos reasignar el valor directamente? (this.state = ...)
Esto no funcionaría. Recordemos que en React, la vista es una función del estado. No tenemos que preocuparnos por actualizar el DOM, ya que React lo hará por nosotros cada vez que el estado cambie. Si modificáramos el estado directamente, React no tendría forma de saber que el estado fue modificado y por lo tanto, no podría actualizar la UI.

Dependiendo de si el nuevo estado depende o no del estado previo, setState nos provee de 2 formas de actualizar el mismo.

En la primera, setState recibe un objeto que representa el nuevo estado y se va a mergear con el estado actual.

// 1: sin estado previo, `setState` recibe un objeto
updateLearning() {
  this.setState({
    learnFirst: 'JS'
  });
}

En la segunda forma, si el nuevo estado depende del anterior, vamos a pasarle un callback a setState, que va a recibir como parámetro el valor actual del estado (en el ejemplo debajo es el parámetro setState) y retornar un objeto que representa al nuevo estado y se va a mergear con el anterior.

// 2: con estado previo, `setState` recibe un callback
updateLearning() {
  this.setState(prevState => ({
    learnFirst: prevState.learnFirst === 'JS' ? 'React' : 'JS'
  }));
}

La elección sobre cuál forma usar va a depender de si necesitamos o no el valor del estado previo

Volviendo al ejemplo visto anteriormente, si queremos, por ejemplo, modificar alguna propiedad del state al hacer click en un botón, podríamos utilizar la propiedad onClick, que agrega un listener (para un evento de tipo 'click') en el elemento <button> y recibe un callback como parámetro. Este callback va a ejecutarse cada vez que se clickee el botón.

Combinando lo anterior entonces con el método setState[2], podemos modificar el estado al clickear el botón. setState va a invocar luego al método render (ya que modificamos el estado), para que el componente se vuelva a renderizar y la UI refleje el cambio de estado correctamente.

En este caso, sólo estamos modificando la propiedad jsFirst del state y dejando el resto igual que antes.

constructor(props) {
  super(props);

  this.state = {
    learnFirst: 'JS'
  }
  
  /*
    ⚠️ es importante bindear siempre los métodos que modifiquen el estado, 
    para no perder la referencia al componente original, sobre todo si pasamos el
    método como prop a un child component
  */
  this.updateLearning = this.updateLearning.bind(this);
}

updateLearning() {
  this.setState(prevState => ({
    learnFirst: prevState.learnFirst === 'JS' ? 'React' : 'JS'
  }));
}

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={mdnLink} target='_blank' rel='noopener noreferrer'>
        Learn {this.state.learnFirst}
      </a>
      <button
        onClick={() => this.updateLearning()}
      >
        Change text
      </button>
    </header>
  </div>
);

↑ Ir al inicio

Estilos y CSS

React no es opinionado sobre cómo aplicar estilos a los componentes, de hecho, funciona perfectamente con archivos de CSS plano. Recordemos que React se ocupa sólo de la vista de la aplicación, es decir, renderizar elementos del DOM en el sitio. Estos elementos pueden tener clases de CSS y el browser se encargará de aplicar los estilos correspondientes.

Como vimos antes, usando la prop className, podemos agregarle clases de CSS a un componente, donde el valor de className hará referencia a clases definidas en hojas de estilo CSS externas.

render() {
  return <span className="menu navigation-menu">Menu</span>;
}

Generalmente, las clases dependen de las props o el state del componente

render() {
  let className = 'menu';
  
  if (this.props.isActive) {
    className += ' menu-active';
  }
  
  return <span className={className}>Menu</span>;
}

Este tipo de código puede simplificarse usando el paquete classnames

↑ Ir al inicio

Global CSS

Podemos agregar estilos con CSS a nuestro proyecto con simplemente linkear una hoja de estilos en el index.html que monta nuestros componentes, como lo haríamos con HTML y CSS normalmente y referirnos a las clases definidas en este CSS a través del atributo className.

<link rel="stylesheet" href="styles.css" />

También podemos importar el .css en el index.js. En el caso de que estemos utilizando CRA, Webpack se dará cuenta de que './index.css' es un archivo CSS y va a aplicarlo al componente App, a través de un atributo style.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css'; // importing .css file
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

👉 El problema con aplicar estilos de esta forma, es que son globales (este es el comportamiento por default de CSS), por lo tanto no podríamos scopear estilos a un componente determinado, sino que tendríamos un namespace global, complejizando así la reusabilidad de los estilos.

↑ Ir al inicio

Inline CSS

Al igual que en HTML, React nos permite también aplicar estilos inline, utilizando el atributo style.[3]

Los estilos inline se definen usando objetos de JS en lugar de strings, con propiedades escritas en la versión camelCase del nombre de la propiedad CSS. Podemos pasar los estilos directamente o crear un objeto que los contenga y pasarlo como prop.

Por ejemplo

const divStyle = {
  color: 'blue',
  backgroundImage: 'url(' + imgUrl + ')',
};

function HelloWorldComponent() {
  return <div style={divStyle}>Hello World!</div>;
}

Los estilos inline también nos permiten combinar sintaxis de CSS con JSX.

En React, el atributo style se usa con mayor frecuencia para añadir estilos calculados dinámicamente al momento del renderizado.

↑ Ir al inicio

CSS-in-JS

CSS-in-JS es un término que se utiliza para definir un patrón donde el CSS se escribe usando JavaScript, en lugar de definirlo en hojas de estilo externas. De esta forma, podemos definir estilos dentro del scope de un componente,es decir, dejan de ser globales y pasan a ser locales, modularizando de esta forma el CSS y haciéndolo reutilizable.

Para más detalles, ver slides de la charla React: CSS-in-JS - Christopher "vjeux" Chedeau y esta comparación de librerías CSS-in-JS.

👉 Esta funcionalidad no forma parte de React, sino que es proporcionada por terceros. React no es opinionado respecto de cómo se definen los estilos.

The Road to Styled Components - Max Stoiber (React Conf 2017)

Ver The Road to Styled Components - Max Stoiber (React Conf 2017)

↑ Ir al inicio

CSS Modules

Un módulo CSS es un archivo CSS donde todos los estilos tienen un scope local por default.

En React, cada component tiene su propio archivo VSS, scopeado a ese componente. Para aplicarle estilos a un componente, sólo tenemos que crear un .css que contenga los estilos.

Luego, este CSS es compilado, generando una versión modificada del CSS, con las clases renombradas (para evitar colisiones en el namespace) y un objeto JS que el componente recibirá como prop, con las clases originales mapeadas a las nuevas.

What are CSS Modules?

Ver What are CSS Modules? A visual introduction

↑ Ir al inicio

React Component Lifecycle

Ver notas sobre React Component Lifecycle.

↑ Ir al inicio

React Hooks

Ver notas sobre React Hooks.

↑ Ir al inicio

PropTypes

Una gran cantidad de bugs puede prevenirse si validamos los diferentes tipos de datos que utilizamos y pasamos como parámetros (props) en nuestra aplicación.

En React existen las PropTypes para verificar tipos de las props de cada componente y chequear que estén siendo utilizados con los valores correspondientes, asi como validar que esté recibiendo las props requeridas.

Las propTypes también sirven de documentación, ya que nos permiten saber rápidamente qué tipo de props espera un componente.

👉 Tener en cuenta que los PropTypes sólo se chequean en runtime (tiempo de ejecución de la app) y en modo desarrollo, por cuestiones de performance.

import React from 'react'
import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};

A continuación se mencionan algunas propTypes bastante útiles

isRequired

Aparte de validar el tipo de una prop, podemos definir por ejemplo, si es requerida, agregando isRequired al final

import React from 'react'
import PropTypes from 'prop-types'

export default function Hello ({ name }) {
  return <h1>Hello, {name}</h1>
}

Hello.propTypes = {
  name: PropTypes.string.isRequired
}

propTypes compuestas

También podemos componer propTypes, por ejemplo si tenemos un array de string, utilizando PropTypes.arrayOf(PropTypes.string)

Component.propTypes = {
  languages: PropTypes.arrayOf(PropTypes.string).isRequired,
  selected: PropTypes.string.isRequired,
  onUpdateSelected: PropTypes.func.isRequired
};

PropTypes.exact

La prop que recibe el componente debe ser un objeto con una forma específica (exacta), cualquier propiedad extra genera error. En el caso de que querramos darle cierta flexibilidad y permitir propiedades extra, podemos utilizar PropTypes.shape.

Header.propTypes = {
  user: PropTypes.exact({
    name: PropTypes.string,
    age: PropTypes.number,
    submit: PropTypes.func,
  })
}

<Header
  user={{
    name: 'Jesse',
    age: 28,
    submit: () => ({})
  }}
/>

PropTypes.oneOf

La prop que recibe el componente debe tener como valor alguno de los especificados.

List.propTypes = {
  order: PropTypes.oneOf(['ascending', 'descending'])
  items: PropTypes.array,
}

<List items={users} order='ascending' />

PropTypes.oneOfType

La prop que recibe el componente debe ser de alguno de los tipos especificados.

Post.propTypes = {
  date: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.instanceOf(Date)
  ])
}

<Post date={new Date()} />

↑ Ir al inicio

React Developer Tools

Una extensión que nos va a facilitar mucho la vida a la hora de debuggear y entender nuestras aplicaciones React, son las Developer Tools, que nos permiten inspeccionar los componentes como si de elementos HTML se tratase.

👉 Descargar React Developer Tools para Chrome

👉 Descargar React Developer Tools para Firefox

↑ Ir al inicio

Create React App

(WIP)

↑ Ir al inicio


1 Ver one-way data flow.

2 Tener en cuenta que setState es asincrónico.

3 Por cuestiones de especificidad y mantenimiento del código, se recomienda usar clases antes que estilos inline.