React — Hooks [useState, useEffect, useContext](Parte I)

Mauricio Garcia
13 min readAug 24, 2020

--

Temario

  • Introducción
  • ¿Qué es un Hook?
  • Hooks incorporados (useState, useEffect, useContext)

i. Introducción

React ha evolucionando a lo largo del tiempo, con el fin de darnos a los desarrolladores una librería que sea fácil de escribir código, mantenible, simple, pero sobre todo escalable.

En los inicios de React, para crear un componente, se necesitaba la APIReact.createClass, donde se le pasa un objeto con propiedades, que le indican cómo crear el componente.

Después llegó la edición ECMA2015 y con ello las clases, así que React se actualizó con la API React.Component, para bien o para mal, esta última llegó con algunos problemas:

  • Recordar agregar bind a tus manejadores de eventos.
  • La forma de estructurar los componentes termina acoplándose al ciclo de vida.
  • Una aplicación va más allá de la interfaz de usuario, por lo que luego tenemos la necesidad de reutilizar código no visual (lógica) y es complicado.

Después React tuvo la fabulosa idea de crear componentes funcionales, pero (si, hay un GRAN pero), si en un componente necesitamos usar un estado o el ciclo de vida, teníamos que regresar a los componentes de clase, entonces con las funcionales solo podíamos crear componentes básicos.

Entonces React al ser una librería hecha por y para desarrolladores, buscaron la manera de resolver los problemas, pero manteniendo todas sus bondades

Y así es como nacieron los Hooks!!..

ii. ¿Que es un Hook?

El patrón Hook[ref], es una API de React que nos van a permitir usar el estado y otras características en un componente funcional.

A partir de la versión 16.8 de React, podemos crear componentes funcionales y tener el mismo (quizás mejor) potencial de los componentes de clase (sí, incluyendo los ciclos de vida).

:: Tipos de Hooks

Tenemos tres tipos:

— Incorporados: useState, useEffect, useContext

— Adicionales: useReducer, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect, useDebugValue

— Otros: Hooks personalizados

:: Reglas básicas

Siempre que vayas a utilizar Hooks no olvides lo siguiente:

  • Los Hooks no funcionan con componentes de clase.
  • Nunca llamar un Hook dentro de un loop, condición, función anidada o funciones fuera del componente, con esta regla nos aseguramos de que los Hooks siempre se llamen en el mismo orden cada vez que un componente se renderiza.
  • Los Hooks, deben estar ubicados dentro del componente en el nivel superior.
  • Solo usar cuando sea un componente funcional.
  • Un Hook puede mandar a llamar a otro Hook.

En esta storie, vamos a ver algunos, los otros los iremos viendo más adelante, ya que para entenderlos necesitamos comprender algunos conceptos.

iii. Hooks incorporados

Antes de comenzar, vamos a crear un nuevo proyecto en codesandbox.io (ver la story de como crrear un proyecto básico en codesandbox.io[ref]) o con create-react-app (En la siguiente story vemos cómo generar uno).

La idea será crear un componente así:

Entonces, debemos crear la carpeta components, y dentro de esta crear el archivo ClickCounter.jsx, donde agregaremos el siguiente código:

Lo importamos en la App.js y lo agregamos al JSX:

Hasta aquí, no tenemos ningún problema, es un simple componente.

— useState

La primera pregunta que viene a la mente (y quizás la que más miedo da)…¿Cómo le hacemos para administrar el estado local, sin usar clases?

Para poder administrar el estado local en los componentes funcionales, ocuparemos el Hook useState

:: ¿Cómo se importa?

Importamos la función en la parte superior del componente de la siguiente manera:

Al ser un paquete de React (y no ser el de default) se va a agregar usando las llaves {}, por lo que le estaremos diciendo al componente funcional que va a manejar un estado local.

:: ¿Cuál es su sintaxis?

:: Ejemplo

Continuando con el ejemplo, vamos a cambiar la constante counter por el Hook; quedaría de la siguiente manera:

Donde:

  • Hemos declarado un estado useState, que se va a inicializar en 0 useState(0)
  • Donde vamos a desestructurar un arreglo [stateName, setStateName]
  • El primero, es el nombre del estado counter
  • El segundo, es el nombre de la función que va a actualizar el estado setCounter

El siguiente paso es agregar un manejador de evento al botón, con la idea de que cada vez que se le de click, aumente uno el contador.

Lo que estamos haciendo, es que cuando se le de click al botón, ejecute el método handleCLick, donde, internamente va a ejecutar la función setCounter, que es el encargado de actualizar el estado.

Si hemos hecho bien los pasos, debemos de ver lo siguiente:

:: Ejemplo completo

:: ¿Cómo funciona?

Ya entendimos para que sirve, cual es su sintaxis, e inclusive ya hasta vimos un ejemplo, pero… realmente ¿cómo funciona?, veamos la siguiente imagen (basada en el ejemplo):

:: Ejemplo 2

¿Se acuerdan de este ejemplo que hicimos con el componente de clase?

La idea es que cada vez que se escriba en el input, se refleje en el elemento <div>de abajo.

Vamos a hacerlo, pero ahora con componentes funcionales.

  • Primero vamos a crear un nuevo proyecto en codesandbox
  • Después crearemos el componente Input, debe quedar así:

Lo único que estamos haciendo es recibiendo dos propiedades defaultValue, onChange(el callback cuando cambia el valor)

  • Ahora vamos a crear el componente funcional Accordion
  • El siguiente paso es usar el Hook de useState, así que reemplacemos value
  • Por último paso, lo importamos en la App.js y lo insertamos en el JSX

:: Ejemplo completo

— useEffect

La primera vez que probé los componentes funcionales, me di cuenta que eran un poco limitados, ya que no tenía el control sobre el ciclo de vida del componente (muchas veces dependía de llamadas API, o modificaciones directas al DOM), así que siempre analizaba el componente y después decidía cuál era la mejor opción (componente de clase o funcional).

En la documentación oficial dice así:

El Hook useEffect, agrega la capacidad de realizar efectos secundarios desde un componente funcional.

Tiene el mismo propósito que componentDidMount, componentDidUpdate y componentWillUnmount en las clases React, pero unificadas en una sola API. [ref]

Esto quiere decir que con los componentes funcionales debes olvidarte de los métodos de ciclo de vida, así como la forma de pensar de “cuando está montado…”, “cuando se desmonta…”; de ahora en adelante debemos pensar como “que pasa después del renderizado del componente”.

En un principio, usábamos el ciclo de vida para establecer un estado inicial en el componente, obtener datos, actualizar el DOM, etc. Realmente siempre fue para modificar el componente (renderizarlo).

Entiendo que al principio cuesta trabajo acostumbrarse, pero una vez que lo comprendes, no vas a tener problema.

Resumiendo useEffect, nos va a permitir ejecutar código adicional después que se haya renderizado el componente y se define dentro del componente para que pueda acceder directamente a las variables y funciones definidas.

:: ¿Cómo se importa?

Importamos la función en la parte superior del componente de la siguiente manera:

Al ser un paquete de React (y no ser el de default) se va a agregar usando las llaves {}, por lo que le estaremos diciendo al componente funcional que vamos a ejecutar código adicional después que se haya renderizado el componente.

:: ¿Cuál es su sintaxis?

:: Ejemplo

¿Recuerdan este ejemplo?, si, estoy tomando ejemplos que ya hicimos, para que ustedes puedan ver como es el cambio de un componente de clase a uno funcional y de paso refactorizar.

La idea es que el componente va a consumir una API[ref], y dependiendo del resultado, vamos a cambiar el color; vamos a hacer paso a paso el componente:

  • Lo primero es crear un componente funcional Welcome, donde va recibir dos propiedades color, name.
  • Ahora vamos a generar el componente funcional Container, que es donde vamos a importar al componente Welcome
  • El siguiente paso es crear el estado local para name, color.

Creo que hasta aquí no tenemos ningún problema, son cosas que ya hemos hecho.

Hablando en términos de clase, la idea es de que cuando se monte el componente, se mande a llamar la API

Donde:

  • (A) — Generamos el Hook useEffect
  • (B) — “Ejecutamos la API
  • (C) — Mandamos los datos al método que va a actualizar el estado.
  • (D) — Actualizamos el estado.
  • (E) — Como no queremos observar ninguna propiedad o estado para que se ejecute, mandamos un arreglo vacío.

¿Por qué generé un método para cambiar los estados, y no lo hice directamente en la “API”?, Recuerda la regla de no poner estados dentro de funciones anidadas.

Si hemos hecho correctamente los cambios, debemos ver lo siguiente:

:: Ejemplo completo

:: Ejemplo 2:

En el ejemplo anterior el Hook useEffect siempre se ejecuta cuando el componente estaba renderizado; ahora, con el siguiente ejemplo, vamos a hacer que se ejecute solamente cuando un estado cambie.

Vamos a aprovechar la API de themoviedb[ref], en un campo de texto el usuario va a poder escribir un id de imdb, en caso de que exista, nos va a regresar la película, caso contrario saldrá un mensaje, algo así:

Nota: La idea del ejemplo es utilizar el Hook useEffect, solo me he enfocado en hacerlo lo más sencillo posible, así que quizás pueda tener muchas mejoras el código, quizás agregarle mas validaciones, separar en más componentes, mejorar estilos, etc.

Entonces, tenemos:

MovieCard: Va a recibir 4 propiedades title, poster, overview, imdb_id

Container: Es quien va a tener la lógica para ejecutar la API y mandar los datos al componente funcional MovieCard.

App: Solo va a tener en el JSX del componente funcional Container.

Teniendo en cuenta lo de arriba, comencemos con el ejemplo…

Agregar el siguiente CSS al archivo src/styles.css

El siguiente paso es hacer el componente MovieCard:

Los Fragmentos (Fragment) nos van a permitir agrupar una lista de hijos sin agregar nodos extra al DOM, esto quiere decir que podemos evitar tener nodos extras (es muy usando en los loops de tablas y listas)

Ahora crearemos el componente funcional Container, lo primero que necesitamos es agregar el formulario y el componente funcional MovieCard:

  • (A) — Cuando le damos click al botón, se va a ejecutar el método handleSearchMovie.
  • (B) — Al estar utilizando un componente no controlado, debemos acceder al valor.

Lo siguiente es agregar el estado para el id:

  • (A) — Agregamos el Hook useState.
  • (B) — Cuando obtenemos el valor del cambio, lo actualizamos con el método setSearchMovie(es el que declaramos en el Hook)

A continuación viene lo interesante, lo que vamos a hacer es crear un Hook useEffect, donde se deberá ejecutar cuando el estado searchMovie cambie.

  • (A) — Creamos el Hook useEffect
  • (B) — Le indicamos al Hook, que se va a ejecutar cuando el estado searchMovie cambie

Si hemos hecho bien los pasos hasta aquí, debemos de ver lo siguiente:

Cada vez que cambia el estado, se ejecuta el Hook useEffect y se imprime en consola, al final no cambia el estado, por lo que no se ejecuta y por lo tanto no imprime en consola.

Por último vamos a utilizar la API fetch[ref], para invocar el servicio, y posteriormente actualizar los datos en un nuevo Hook useState, para enviarselo al componente funcional MovieCard

  • (A) — Creamos un Hook useState, para la película.
  • (B) — Utilizamos la API fetch, para invocar el servicio
  • (C) — Mandamos al método handleData los datos .
  • (D) — Método que hemos creado para actualizar el estado.
  • (E) — Mandamos los datos al componente funcional MovieCard.

:: Ejemplo completo

Descargar el ejemplo completo de GitHub[ref]

:: Ejemplo 3:

La ventaja de usar el Hook useEffect, es que podemos tener cuantos sean necesarios con la ventaja de poder separar la lógica dependiendo la necesidad de cada uno.

  • (A) — Hook useEffect, que solo se va a ejecutar la primera vez que se renderiza el componente.
  • (B) — Hook useEffect, que se va a ejecutar cuando el estado userCount cambie de valor
  • (C) — Hook useEffect, que se va a ejecutar cuando el estado simpleCount cambie de valor

:: ¿Cómo funciona?

Ya entendimos para que sirve, cual es su sintaxis, e inclusive ya hasta vimos un ejemplo, pero… realmente ¿cómo funciona?, veamos la siguiente imagen (basada en el ejemplo)

:: Ejemplo 4

En el siguiente ejemplo, vamos a ver cómo podemos saber cuando el componente ha sido desmontado:

En el App.js, vamos a hacer los siguientes cambios:

  • (A) — Creamos un estado local, para mostrar/ocultar el componente
  • (B) — Cuando se le da click al botón, si está montado el componente, lo va a desmontar o viceversa.

Ahora en el componente Welcome,agregamos lo siguiente:

  • (A) — Cuando el componente se ha montado
  • (B) — Cuando queremos hacer algo, cuando el componente se haya desmontado, solo es necesario regresar una arrow function y dentro de ella la lógica.

Bastante sencillo ¿no?.

:: Ejemplo completo

useContext

Context provee una forma de pasar datos a través del árbol de componentes sin tener que pasar props manualmente en cada nivel.[ref]

En vez de estar pasando datos a través de cada uno de los hijos, lo que hace Context, es crear un estado global, lo cual nos va a ser útil para los datos que deban compartirse entre componentes, usualmente es usado en temas (theme CSS), autentificación, idioma.

Para poder administrar el “estado global” en los componentes funcionales, ocuparemos el Hook useContext.

:: ¿Cómo se importa?

Importamos la función en la parte superior del componente de la siguiente manera:

Al ser un paquete de React (y no ser el de default) se va a agregar usando las llaves {}, por lo que le estaremos diciendo a al componente funcional que vamos a utilizar un estado global.

:: ¿Cuál es su sintaxis?

:: Ejemplo

Poder utilizar useContext, realmente va más allá de solo utilizarlo en el componente, por lo que vamos a realizar el ejemplo paso a paso:

Lo primero que tenemos que hacer es crear un proyecto en codesandbox, después debemos de crear:

Lo primero que vamos a crear es el Context, donde vamos a crear un archivo JavaScript llamado theme.js.

  • (A) — Es el tema que nosotros vamos a utilizar en los componentes
  • (B) — El contexto se inicializa con la API createContext, lo que estamos haciendo es compartir un contexto ThemeContext, para que pueda ser utilizado a través de los componentes.
  • (C) — Exportamos la constante que tiene los temas, así como el contexto que hemos creado.

Ahora vamos a agregar el contexto, donde se puede englobar en cualquier componente, en el caso del ejemplo o en la App.

  • (A) — Lo que hacemos es importar el contexto y los temas que tenemos.
  • (B) — El contexto ThemeContext va a proveer a todos los componentes dentro de App el tema, sin importar cuántos hijos tiene cada uno, inicializando con el tema themes.dark.

El siguiente paso será crear lo 3 componentes restantes Home, Container, Button:

Ahora, para cargar el tema, en el componente Button, vamos a hacer los siguientes cambios:

  • (A) — Importamos el contexto, con la finalidad de poder ocupar el tema.
  • (B) — Aquí es donde usamos el Hook useContext, lo que va hacer aquí es tomar los valores que tiene el contexto ThemeContext y los va a asignar a la constante theme.
  • (C) — En este caso agregamos la constante a los estilos.

Si hemos hecho bien todos los pasos, debemos ver lo siguiente (depende el tema que le asignemos):

:: Ejemplo completo

:: ¿Cómo funciona?

Ya entendimos para que sirve, cual es su sintaxis, e inclusive ya hasta vimos un ejemplo, pero… realmente ¿cómo funciona?, veamos la siguiente imagen (basada en el ejemplo)

:: Ejemplo 2

Vamos a hacer que cuando entre, tenga unos datos por default el login, y cuando “acceda”, en este caso por medio de un botón, se van a actualizar los datos en algún hijo, algo así:

A simple vista no se nota, pero los componentes están así:

Las carpetas, quedan así:

Entonces el código completo, antes de actualizar el contexto, es el siguiente:

userInfo.js

  • (A) — El objeto por default
  • (B) — El contexto se inicializa con la API createContext, lo que estamos haciendo es compartir un contexto UserInfoContext, para que pueda ser utilizado a través de los componentes.
  • (C) — Exportamos la constante que tiene el perfil del usuario, para setear la propiedad, así como el contexto que hemos creado.

App.js

Home.jsx

Container.jsx

Login.jsx

Hasta aquí no deberíamos de tener problema alguno (es casi una copia del Ejemplo 1). Ahora la idea es que cuando le den click, le mandemos un nuevo objeto al contexto, y se vea reflejado en el componente funcional Login.

  • (A) — Vamos a crear un estado, para que cuando entre, se actualice
  • (B) — Seteamos el valor de user.
  • (C) — Al actualizar el parámetro, la aplicación vuelve a mandar el objeto user actualizado.

:: Ejemplo completo

--

--

No responses yet