React Hooks: la guia definitiva! 🚀

Santiago Quinteros - CEO & CTO - Software on the road
By:
Santiago Quinteros

A partir de React 16.8.0 hay nuevas formas de llamar al código de asíncrono de forma elegante, reutilizar la lógica entre componentes mucho más fácilmente.

Como desarrollador de React, es tu deber estar al día con sus nuevas características.

No para complacer a tu jefe, sino para mantenerte relevante en el campo y en el mercado.

Todavía recuerdo los viejos tiempos cuando nadie hablaba del patrón de redux y mis aplicaciones react eran un desastre de estado (mediados de 2014).

Cuando se introdujo el patrón de flujo al principio era difícil de entender y parece muy complicado de implementar, pero ahora unos años después es el estándar en todos los proyectos basados en el de react.

Con los hooks de react ocurrirá lo mismo, es el reemplazo de los componentes de clase y el futuro de react.js

Muy bien, este va a ser un largo post, así que he añadido una tabla de contenido para que podáis leer un poco, luego seguir trabajando en vuestro proyecto, y volver más tarde cuando necesitéis un descanso.

Soy el único que lee artículos técnicos para limpiar mi mente y liberar el estrés de mi trabajo diario?

Tabla de contenidos

Qué son los React hooks de todos modos? 🤔

Cuando trabajas con los componentes de la clase Reactjs puedes usar el estado, es por eso que estos componentes también se llaman stateful, también cada componente de la clase tiene métodos de ciclo de vida como: ComponentDidMount(), ComponentDidUpdate(), y así sucesivamente.

No puedes usar nada de esto en los componentes funcionales. Los componentes funcionales no pueden usar su propio estado y no tienen métodos de ciclo de vida.

Ahora con React hooks puedes.

Los React hooks nos permiten tomar un componente funcional de React.js y agregarle métodos de estado y ciclo de vida.

En palabras simples, los React hooks son funciones especiales para extender las capacidades de los componentes funcionales y darles la posibilidad de tener eventos de ciclo de vida y manejar el estado.

Comparemos cómo una clase difiere de un componente funcional cuando se usan React hooks

La buena y vieja manera de la clase

import React from 'react';
class ClickCounter extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      count: 0 // Valor inicial para nuestro contador
    };
  }

  setCount(numb) {
    this.setState({
      count: numb
    })
  }

  render() {
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={() => this.setCount(this.state.count + 1).bind(this)}>
          Click me
        </button>
      </div>
    );
  }
}

Con React hooks

import React, { useState } from 'react';
function ClickCounter() {
  /** 
    useState crea una variable "count" que almacenará el estado y una función "setCount" que silenciará el estado de la variable "count"
  **/
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Usando el ejemplo hooks "useState" para almacenar el estado en un componente de la función.

Menos líneas de código para hacer lo mismo!

Pero no es sólo eso, con los React hooks, ahora puedes reutilizar la lógica del estado y tener una mejor separación de las preocupaciones.

Al principio, esta nueva API puede parecerte extraña, pero quédate conmigo, aprenderás a sacarle el máximo provecho.

La existencia de React hooks 🍱

La nueva API viene con dos hooks preexistentes principales y algunos otros para otros casos de uso.

Lo esencial de React hooks

La base de todos los React hooks, todos los demás hooks que verán es una variación de estos tres o los están usando como primitivos.

  • El "useState" es el hook de State que se usa para declarar el estado en sus componentes

  • El "useEffect" es el hook de efectos laterales que se usa para obtener datos, cambiar manualmente el DOM, etc.

  • El "useContext" lo usa en conjunto con el API de Reactjs Context. Cuando el proveedor de React Context se actualice, este hook disparará el render con el último valor de contexto.

Los hooks de Advance React

Estos son los más importantes de los otros hooks incorporados de React que vienen con la biblioteca.

  • El "useReducer" es una alternativa al "useState", deberías usarlo cuando tengas lógica de estado compleja, si estás familiarizado con Redux te gustará.

  • El "useRef" lo usa para acceder a un elemento DOM con un objeto ref mutable. Es más útil que el atributo `ref

Esos peculiares paréntesis

Podrías preguntarte qué significa la sintaxis const [age, setAge] = useState(24), pero es sólo la nueva forma de desestructurar una matriz, déjame mostrarte otra forma de hacerlo.

const ageStateVariable = useState(24); // Devuelve una tupla o una matriz de longitud 2
const age = ageStateVariable[0]; // Primer elemento
const setAge = ageStateVariable[1]; // Segundo elemento

// La forma en que el ES6 hace esto
const [age, setAge] = useState(24);

Me encantan las líneas simples y elegantes, no tanto como la gente pyton, y definitivamente NO me gustan las líneas locas como la gente pyton.

Reglas

  • Nunca llame a Hooks desde el interior de un bucle, condición o función anidada

  • Nunca llames a un Garfio desde una función regular

  • Sólo los llaman componentes de funciones internas o hooks personalizados

  • Los hooks deben estar en el nivel superior de su componente

  • Los hooks pueden llamar a otros hooks

El hook useState 🎲

El más fácil de usar y entender todos los hooks. Su propósito es almacenar el estado en un componente funcional.

Bueno, técnicamente no estamos almacenando el estado dentro de él, sino enganchando en el diccionario (llave-valor) de estados que son manejados por la biblioteca de reacciones bajo el capó. Pero no vamos a profundizar en esos detalles por ahora.

import React, { useState } from 'react';

function myAwesomeComponent () {
  const [name, setName] = useState('John');
  ...
}

El useState devuelve una tupla con una propiedad state holder y un método setter.

Se invoca a useState con el valor inicial de su estado.

Para actualizar el estado llamas a la función "setName"

El hook useEffect 🍯

En una clase de React, normalmente se configura una suscripción en "ComponentDidMount", y se limpia en "ComponentWillUnmount".

Con el hook react "useEffect" lo realizamos devolviendo una función para limpiar o "desuscribir" el efecto.

Si has trabajado con mobx este patrón puede resultarte familiar, es una analogía a una reacción.

  useEffect(() => {
    PlacesAPI.subscribeToPlaceNews(props.place.id, handlePlacesNews);
    return () => {
      PlacesAPI.unsubscribeFromPlaceNews(props.place.id, handlePlacesNews);
    };
  });

Why did we return a function from our effect?

This is the optional cleanup mechanism for effects. Every effect may return a function that cleans up after it.

This lets us keep the logic for adding and removing subscriptions close to each other.

El hook useReducer 🎣

Cuando tienes una lógica de estado compleja, es una buena idea usar un "reductor". Si estás familiarizado con librerías como "Redux" o el "flux pattern" lo entenderás a primera vista.

Redux pattern architecture

Básicamente con un reductor que "despacha" o desencadena algunas acciones en su vista, esos eventos son escuchados por un reductor que tiene la lógica dentro para actualizar la tienda que es donde vive su estado. Ahora, cuando el almacén se actualiza, tu componente se volverá a renderizar.


import React, { useReducer, useState } from 'react';
import produce from 'immer';

function reducer(state, action) {
  switch (action.type) {
    case 'toggle':
      return produce(state, (draftState) => {
        draftState[action.payload].isCompleted = !draftState[action.payload].isCompleted;
      });
    case 'add':
      return produce(state, (draftState) => {
        draftState.push({ label: action.payload });
      });
    default:
      return state;
  }
}

function Todo({ isCompleted, label, onChange }) {
  return <p>
    <label style={{
      textDecoration: isCompleted && 'line-through'
    }}>
      <input
        type="checkbox"
        checked={isCompleted || false}
        onChange={onChange}
      />
      <span>{label}</span>
    </label>
  </p>
}

function TodoList() {
  const todos = [
    { label: 'Do something' },
    { label: 'Buy dinner' }
  ];

  const [state, dispatch] = useReducer(reducer, todos);
  const [newTodo, setNewTodo] = useState('');

  return <>
    {state.map((todo, i) => (
      <Todo
        key={i}
        {...todo}
        onChange={() => dispatch({ type: 'toggle', payload: i })}
      />
    ))}
    <input
      type="text"
      value={newTodo}
      onChange={(e) => setNewTodo(e.target.value)}
    />
    <button onClick={() => {
      dispatch({ type: 'add', payload: newTodo });
      setNewTodo('');
    }}>
      Add
    </button>
  </>;
}

export default TodoList;

El hook useRef 🔮

Refs se utilizan para acceder a los elementos de React o a los elementos DOM renderizados en la función render. El hook useRef devuelve un objeto ref mutable cuya propiedad .current se inicializa con el argumento pasado initialValue. Es muy simple de usar

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

Separación de intereses 🎎

Mantén tu código organizado

Con Hooks, puedes extraer la lógica del estado de un componente para que pueda ser probado independientemente y reutilizado.

Los hooks permiten reutilizar la lógica del estado sin cambiar la jerarquía del componente.

Por ejemplo, los componentes pueden realizar algunas búsquedas de datos en "ComponentDidMount" y "ComponentDidUpdate".

Sin embargo, el mismo método de "ComponentDidMount" también puede contener lógica no relacionada que configura los escuchas de eventos, con la limpieza realizada en "ComponentWillUnmount".

El código mutuamente relacionado que cambia junto se divide, pero el código completamente no relacionado termina combinado en un solo método.

  import React from 'react';
  import PlacesAPI from '../services/place';
  class PlaceNewsWithCounter extends React.Component {
    constructor(props) {
      super(props);
      this.handlePlacesNews = this.handlePlacesNews.bind(this);
      this.state = { count: 0, currentEvent: null };
    }

    // Lógica estatal no relacionada
    componentDidMount() {
      document.title = `You clicked ${this.state.count} times`;
      PlacesAPI.subscribeToPlaceNews(
        this.props.place.id,
        this.handlePlacesNews
      );
    }

    componentDidUpdate() {
      document.title = `You clicked ${this.state.count} times`;
    }

    componentWillUnmount() {
      PlacesAPI.unsubscribeFromPlaceNews(
        this.props.place.id,
        this.handlePlacesNews
      );
    }

    handlePlacesNews(place) {
      this.setState({
        currentEvent: place.currentEvent
      });
    }
    ...
  }

Un mejor enfoque usando los React hooks

  import React, { useState, useEffect } from 'react';
  import PlacesAPI from '../services/place';
  function PlaceNewsWithCounter() {

    // Lógica para el contador aquí...
    const [count, setCount] = useState(0);
    useEffect(() => {
      document.title = `You clicked ${count} times`;
    });


    // Lógica para colocar la API aquí...
    const [currentEvent, setCurrentEvent] = useState(null);

    function handlePlacesNews(place) {
      setCurrentEvent(place.currentEvent);
    }

    useEffect(() => {
      PlacesAPI.subscribeToPlaceNews(props.place.id, handlePlacesNews);

      return () => {
        PlacesAPI.unsubscribeFromPlaceNews(props.place.id, handlePlacesNews);
      };
    });


    return ...;
  }

Advance use cases 🤵

Como un jefe

Usando "useEffect" para la obtención de datos

Con la combinación de "useEffect" y "useState", puedes hacer llamadas a la API usando "useEffect" y pasando un array u objeto vacío como segundo argumento para tener el mismo comportamiento que componentDidMount.

La clave aquí es el segundo argumento. Si no proporcionas un array u objeto vacío como segundo argumento, la llamada a la API será llamada en cada render, y efectivamente se convierte en lo mismo que un componentDidUpdate

  const [todo, setTodo] = useState(null);
  const [id, setId] = useState(1);
  
  useEffect(() => {
    if (!id) {
      return;
    }
    
    fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
      .then(results => results.json())
      .then(data => {
        setTodo(data);
      });
  }, [id]);  // No te olvides de añadir esto!

Al pasar un segundo parámetro para usar el efecto, estamos estableciendo una suscripción cada vez que la propiedad cambia, el efecto se vuelve a activar.

Si en cambio, nos gustaría hacer una llamada a la API Sólo cuando el componente esté montado

const [fullName, setFullName] = useState(null);

useEffect(() => {
  fetch('https://randomuser.me/api/')
    .then(results => results.json())
    .then(data => {
      const {name} = data.results[0];
      setFullName(`${name.first} ${name.last}`);
    });
}, []); // <-- Tienes que pasar por [] aquí!

Ejemplos del mundo real

Mostrar estado en línea

Detectar el estado de conexión del dispositivo del usuario. (https://github.com/rehooks/online-status)_

Hook implementation

import { useEffect, useState } from "react";

function getOnlineStatus() {
  return typeof navigator !== "undefined" &&
    typeof navigator.onLine === "boolean"
    ? navigator.onLine
    : true;
}

export const useOnlineStatus = () => {
  let [onlineStatus, setOnlineStatus] = useState(getOnlineStatus());
  const goOnline = () => setOnlineStatus(true);
  const goOffline = () => setOnlineStatus(false);

  useEffect(() => {
    window.addEventListener("online", goOnline);
    window.addEventListener("offline", goOffline);
    return () => {
      window.removeEventListener("online", goOnline);
      window.removeEventListener("offline", goOffline);
    };
  }, []);

  return onlineStatus;
}

Uso de Hook

const App = () => {
  let onlineStatus = useOnlineStatus();
  return (
    <div>
      <h1>You are {onlineStatus ? "Online" : "Offline"}</h1>
    </div>
  );
}

Detectar cambios de geolocalización

Rastrea el estado de geolocalización del dispositivo del usuario. (https://github.com/streamich/react-use)_

Implementación de Hook

import {useState, useEffect} from 'react';
const useGeolocation = () => {
const [state, setState] = useState({
  loading: true,
  accuracy: null,
  altitude: null,
  altitudeAccuracy: null,
  heading: null,
  latitude: null,
  longitude: null,
  speed: null,
  timestamp: Date.now(),
});
let mounted = true;
let watchId: any;

const onEvent = (event: any) => {
  if (mounted) {
    setState({
      loading: false,
      accuracy: event.coords.accuracy,
      altitude: event.coords.altitude,
      altitudeAccuracy: event.coords.altitudeAccuracy,
      heading: event.coords.heading,
      latitude: event.coords.latitude,
      longitude: event.coords.longitude,
      speed: event.coords.speed,
      timestamp: event.timestamp,
    });
  }
};
const onEventError = (error: any) =>
  mounted && setState(oldState => ({...oldState, loading: false, error}));

useEffect(() => {
  navigator.geolocation.getCurrentPosition(onEvent, onEventError);
  watchId = navigator.geolocation.watchPosition(onEvent, onEventError);

  return () => {
    mounted = false;
    navigator.geolocation.clearWatch(watchId);
  };
}, [0]);

return state;
};

Hook Usage

const Demo = () => {
  const state = useGeolocation();

  return (
    <pre>
      {JSON.stringify(state, null, 2)}
    </pre>
  );
};

Proyectos impresionantes ✨

Conclusión 🎉

La nueva API de React hook es un cambio de juego, ahora podemos usar el estado en los componentes de función, reutilizar la lógica del estado.

Aprendemos sobre "usar el estado" y "usar el efecto", esos son los primitivos para cada gancho que verás.

**Recuerda, cada nuevo gancho es una derivación de uno de esos dos.

Hablamos de que otros reaccionan con hooks incorporados como "useReducer" y "useRef".

Creamos nuestros propios hooks personalizados para manejar la obtención de datos e implementamos nuestra propia versión de useReducer para demostrar la magia que hay detrás.

Mantengan la calma y continúen aprendiendo

Recursos

Get the latest articles in your inbox.

Join the other 2000+ savvy node.js developers who get article updates. You will receive only high-quality articles about Node.js, Cloud Computing and Javascript front-end frameworks.


santypk4

CEO at Softwareontheroad.com