Desarrollo Web

React Hooks: Qué son y qué problemas solucionan

En este artículo aprenderás qué son los Hooks en React, qué problemas solucionan, los tipos que existen, cómo usarlos y algunos ejemplos prácticos.

Publicado el 14 de Octubre de 2020
Compartir

Trabajando con React, y antes de la aparición de Hooks en la versión 16.8, se disponía siempre de la posibilidad de crear componentes de tres formas distintas basándose en una serie de suspuestos y necesidades:

  • Componentes elementales - Estos componentes son los más sencillos, se caracterizan por ser meras variables que almacenan una expresión JSX concreta, de forma que no reciben propiedades y no poseen estado, aunque pueden seguir usando cualquier operador de forma habitual, por ejemplo:

    const componente = list.length > 1 ?
          [
              <li className="mi-clase">{list[0]}</li>,
              <li className="mi-clase">{list[1]}</li>
          ]
        :
        null;
    

    Este array inyectado en un tag JSX <ul> nos renderizará esa lista en nuestro DOM.

  • Componentes funcionales - En la era pre-Hooks, estos componentes se usaban principalmente para aislar características potencialmente reutilizables pero con una lógica adicional no dependiente de su estado, ya que los componentes funcionales recibían propiedades pero no poseían estado. Un ejemplo de componente funcional sería:

    function MiComponente(props) {
        return <h1>Este componente imprime: {props.value}</h1>
    }
    // Podemos renderizarlo directamente
    const miComponente = <MiComponente value="Esto mismo"/>
    ReactDOM.render(
        miComponente,
        document.getElementById('root')
    );
    
  • Componentes de clase - Estos han sido siempre los componentes más habituales en el desarrollo con React, por el simple hecho de que eran los únicos que poseían propiedades, estado y ciclo de vida, haciéndolos esenciales para la gestión de la lógica principal y el ciclo de la aplicación. El ejemplo más sencillo de componente de clase con estado y con algún ciclo de vida sería:

    class MiComponente extends React.Component {
        constructor(props) {
           super(props);
           this.state = {
               value: ''
           }
        }
        componentDidMount() {
            this.setState({ value: 'Esto mismo' })
        }
        render() {
            const { value } = this.state;
            return <h1>Este componente imprime: {value}</h1>
        }
    }
    

Sin embargo estos supuestos quedaron obsoletos cuando React presentó los hooks en el 2018, prometiendo así una nueva forma de trabajar en base a componentes funcionales con acceso a estado y ciclo de vida.

Imagen 0 en React Hooks: Qué son y qué problemas solucionan

Qué es un Hook

Un Hook es una función de javascript que permite crear/acceder al estado y a los ciclos de vida de React y que, para asegurar la estabilidad de la aplicación, debe de utilizarse siguiendo dos reglas básicas:

  • Debe de ser llamado en el nivel superior de la aplicación - Un hook nunca debe de llamarse dentro de ciclos, condicionales o funciones anidadas, ya que el orden de llamada de los hooks debe de ser siempre el mismo para asegurar que el resultado sea predecible durante la renderización. Este uso únicamente en el nivel superior es lo que asegura que el estado interno de React se preserve correctamente entre diferentes llamadas del mismo hook.
  • Debe de llamarse en funciones o en otros hooks personalizados de React - Un hook nunca debe de ser llamado fuera de una función de React o de otro hook personalizado, de forma que la lógica de estado del componente sea cláramente visible desde el resto del código para el scope establecido por React.
Aprende a desarrollar webs optimizadas
Comienza 15 días gratis en OpenWebinars y accede cursos, talleres y laboratorios prácticos de JavaScript, React, Angular, HTML, CSS y más.
Registrarme ahora

Como vimos en el anterior apartado, un componente funcional antes de React 16.8 no podía poseer estado ni ciclo de vida, de forma que la única forma de crear, por ejemplo, un acumulador simple sería:

class Acumulador extends React.Component {
    constructor(props) {
       super(props);
       this.state = {
           count: 0
       }
    }
    render() {
        const { count } = this.state;
        return (
            <h1>El botón ha sido pulsado { count } veces</h1>
            <button onClick={() => this.setState({count: count + 1})}>Púlsame</button>
           )
    }
}

Sin embargo gracias a los hooks ahora podemos replicar este componente añadiéndole un estado con el uso de useState, de forma que el acumulador anterior de forma funcional sería:

function Acumulador(props) {
    const [count, setCount] = useState(0);
    return (
        <h1>El botón ha sido pulsado { count } veces</h1>
        <button onClick={() => setCount(count => count + 1)}>Púlsame</button>
    );
}

Hooks en React

Como la finalidad última de los hooks es simplificar la lógica actual, React proporciona únicamente un set reducido, con la flexibilidad para responder ante diversas situaciones del ciclo de vida de una aplicación y la posibilidad de construir también los nuestros propios.

Hooks básicos

React proporciona tres hooks básicos que responden a las necesidades habituales para implementar un ciclo de vida en un componente de clase:

Hook de estado useState

Este hook nos devuelve un valor con un estado mantenido y una función que es necesaria para actualizarlo:

  const [count, setCount] = useState(0)

El estado inicial es el parámetro pasado a useState, en este caso 0, y será el estado del que dispondrá durante al menos el render inicial y hasta que sea llamada la función setCount con un nuevo valor, de forma que en este caso, por ejemplo, setCount(count + 1) incrementará el valor de count en uno, pasando a valer 1 en la siguiente renderización. También es posible utilizar el valor del estado anterior dentro de la propia función de establecimiento del estado, de forma que lo anterior es posible también escribirlo como setCount(count => count + 1).

Hook de efecto useEffect

Este hook nos permite agregar efectos secundarios a un componente funcional dado, es decir, nos permite realizar modificaciones en nuestra lógica una vez se haya realizado el renderizado, de la misma forma que los métodos de ciclo de vida componentDidMount,componentDidUpdate y componentWillUnmount en los componentes de clase.

Una de las grandes ventajas de este hook es que simplifica el ciclo de vida, de forma que los tres métodos de clase disponibles pueden ser expresados utilizando únicamente este hook. Por ejemplo, en el caso de un componente de clase que carga una serie de datos de una cookie al montar, y los escribe al desmontar, podríamos escribirlo así:

  class LoadUnload extends React.Component {
      constructor(props) {
          super(props);
          this.state = {
              data: null
          }
      }
      componentDidMount() {
          // función teórica que devuelve los datos de una cookie como objeto js
          const loadedData = getCookie('data');
          this.setState({data: loadedData})
      }
      componentWillUnmount() {
          const { data } = this.state;
          // Función teórica que guarda el objeto data en formato JSON
          setCookie(data);
      }
  }

Esto podríamos realizarlo en el componente funcional usando un único componente funcional:

  function LoadUnload(props) {
      const [data, setData] = useState(null);
      useEffect(() => {
          const loadedData = getCookie('data');
          setData(loadedData);
          return () => setCookie(data);
      }, []);
  }

Donde, al usar un array vacío de dependencias, le decimos al efecto que sólo se ejecute al montar (Como componentDidMount) y el retorno de la función es una nueva función que se llama únicamente al desmontar (Como componentWillUnmount).

Hook de contexto useContext

Si has usado el contexto de React en alguna ocasión, este es el hook que necesitas. Un contexto en React es una forma de pasar datos entre diferentes componentes sin necesidad de realizar una cascada manual de props. Algo útil, por ejemplo, cuando queremos crear temas o localizaciones, que deben de ser globales para todo el árbol de componentes y puede ser engorroso tener que propagarlos para cada componente añadido.

En el caso de componentes de clase, el contexto se pasa a través de un provider que englobe al árbol de componentes que debe de disponer de dicho contexto, de forma que un componente que tenga localización y utilice un contexto LocalContext se puede escribir así:

import {LocalContext} from './local-context';

class LocalizedText extends React.Component {
  render() {
    let local = this.context;
    return (
      <div
        {...this.props}
      >
            <h1>{local.title}</h1>
            <p>{local.paragraph}</p>
      </div>
    );
  }
}
LocalizedText.contextType = LocalContext;

export default LocalizedText;

En el caso de un componente funcional, y con el uso de hooks, podemos utilizar useContext que nos permite acceder al contexto creado, por ejemplo para una pequeña aplicación que pase la localización a un componente como el anterior pero de forma funcional:

const local = {
  es: {
    title: 'Hola',
    paragraph: 'Esto es una prueba'
  },
  en: {
    title: 'Hello',
    paragraph: 'This is a test'
  }
};

const LocalContext = React.createContext(local.es);

function App() {
  return (
    <ThemeContext.Provider value={local.en}>
      <Container />
    </ThemeContext.Provider>
  );
}

function Container(props) {
  return (
    <div>
      <LocalizedText />
    </div>
  );
}

function LocalizedText(props) {
  const local = useContext(LocalContext);
  return (
    <div {...props}>
        <h1>{local.title}</h1>
        <p>{local.paragraph}</p>
      </div>
  );
}

Hooks adicionales

Además de estos Hooks, existen una serie de hooks orientados a la optimización de nuestro flujo de renderizado, para evitar perder ciclos, como son useCallback y useMemo, cuya finalidad es memorizar funciones y componentes funcionales para no renderizarlos inútilmente si ninguna de sus dependencias ha cambiado (Al igual que cuando implementamos el ciclo de vida shouldComponentUpdate en componentes de clase).

Sin embargo, sobre estos hooks y otros veremos más información de su aplicación en la siguiente parte donde veremos un ejemplo práctico en el que aplicar todos estos conocimientos.

Y si aun así te has quedado con ganas de saber más sobre estos y otros hooks siempre puedes ampliar tus conocimientos con nuestro completo taller práctico sobre React Hooks.

Por qué usar Hooks

El uso de los hooks en componentes funcionales parece una mera adición que, de hecho, no reemplaza ni tiene previsto reemplazar los actuales componentes de clase, entonces podríamos preguntarnos: ¿Cual es el sentido del uso de hooks y de cambiar nuestra forma de desarrollar con React?.

En primer lugar, como Dan Abramov adelantó en la presentación de esta característica, el uso de hooks reduce el número de conceptos necesarios en el desarrollo de aplicaciones React, de forma que no se nos hace necesario cambiar continuamente entre funciones, clases, HOCs o elementos para realizar tareas semejantes; Los hooks nos ofrecen homogeneidad en el ecosistema.

En segundo lugar el ciclo de vida de React se ha simplificado en gran manera con el uso de los hooks , de modo que, como vimos anteriormente, los métodos de ciclo de vida de clases componentDidMount, componentDidUpdate y componentWillUnmount se resumen en un único hook useEffect que actúa como los tres. Así un temporizador que añade una cierta cantidad por segundo, usando un componente de clase primero:

class Timer extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            seconds: 0
        }
    }
    componentDidMount() {
        const {seconds, quantityToAdd} = this.state;
        this.interval = setInterval(() => {
            this.setState({seconds: seconds + 1})
        })
    }
    componentWillUnmount() {
        clearInterval(this.interval);
    }
    render() {
        return (
            <div>Han pasado {this.state.seconds} segundos</div>
        )
    }
}

Puede expresarse como un componente funcional con el uso de hooks:

function Timer(props) {
    const [seconds, setSeconds] = useState(0);
    useEffect(() => {
        const interval = setInterval(() => {
            setSeconds(seconds => seconds + 1);
        }, 1000);
        return () => clearInterval(interval);
    }, []);
    return <div>Han pasado {seconds} segundos</div>;
}

Que sin duda queda mucho más compacto, tanto en calidad de código como en uso de funciones, y el funcionamiento es similar.

En tercer lugar utilizar componentes funcionales refuerza el principio base de React de evitar las mutaciones, ya que cambiar el estado de un componente de clase es lo mismo que mutar su propiedad state, al igual que es necesario realizar un “binding” para las funciones que gestionan eventos, aproximaciones que incrementan notablemente la complejidad y reducen la predictibilidad del componente.

Y finalmente encontramos que la introducción del hook useReducer introduce en el núcleo de React la posibilidad de trabajar con un patrón Redux sin necesidad de dependencias adicionales. Este hook, catalogado en la categoría de “hooks adicionales”, es siempre recomendable cuando el estado de la aplicación sea demasiado complejo y con una gran cantidad de anidación, ya que el reducer acepta una función del tipo (state, action) => newState devolviendo el estado actual y una función de “dispatch”, lo cual nos permite emular las funcionalidades disponibles actualmente en las librerías redux y react-redux tan utilizadas para solucionar el problema de gestión del estado o de cascada de propiedades.

Construye interfaces de usuarios personalizadas y atractivas
Lleva la formación de tu equipo al siguiente nivel con cursos, talleres y laboratorios prácticos de JavaScript, React, Angular, HTML, CSS y más.
Solicitar más información

Para continuar

React Hooks nos ofrece un nuevo paradigma, y una nueva forma de pensar, que está muy relacionada con la programación funcional, donde las funcionalidades son bloques predecibles de entrada-salida y son consideran ciudadanas de primera clase y los efectos secundarios se aíslan de una forma altamente controlable.

Sin embargo, poseer el conocimiento teórico no es poseer el dominio de su aplicación, por tanto en la siguiente parte sobre Hooks desarrollaremos un pequeño juego incremental con el uso de hooks, haciendo un especial hincapié en el flujo de la aplicación, la traducción en componentes y hooks para el ciclo de vida y adicionalmente el uso de hooks de optimización para mejorar el rendimiento (Como los ya mencionados useCallback y useMemo).

Si te ha gustado esta primera parte te invitamos a seguir atento a la publicación de la siguiente y sobre todo a que eches un vistazo a nuestro detallado curso de React para principiantes, donde desarrollarás paso por paso las habilidades necesarias para encauzar cualquier proyecto al que te enfrentes.


Compartir este post

También te puede interesar...

Tecnología

React vs Vue

17 Septiembre 2020 Pablo Huet
React Hooks

React Hooks

59 minutos y 10 segundos · Taller

En este taller aprenderás la nueva forma para controlar el estado en React sin necesidad de tener que definir una clase. Además, veremos cuál es …

  • Desarrollo Web
Artículos
Ver todos