React Hooks Tutorial en español desde cero

React Hooks Tutorial en español desde cero
Guía paso a paso para aprender los React Hooks

¿Qué son los Hooks en React?

Los React Hooks fueron incorporados a partir de la versión 16.8 de React js y son funciones de JavaScript que nos permiten utilizar las características de esta biblioteca en componentes funcionales.

Para dar solo un ejemplo: a partir de ahora puedes declarar variables de estado en componentes funcionales, algo que antes era posible sólo en componentes de tipo clase.

import { useState } from 'react'

const Count = () => {
  const [count, setCount] = React.useState(0)

  const handleClick = () => {
    setCount(count + 1)
  }

  return (
    <>
      <p>Clicks: {count}</p>
      <button onClick={handleClick}>Clickeame</button>
    </>
  )
}

Si el código anterior te parece extraño, no te preocupes, ¡Así nos pasa a todos la primera vez! más adelante te voy a hablar más a detalle sobre este hook :D

En este mega post vamos a cubrir los aspectos esenciales y ejemplos de hooks para que puedas dominarlos rápidamente.

Contenidos


Como te dije al inicio, los hooks no son más que funciones de JavaScript que React detecta y las hace "especiales" porque permiten acceder a lo que nos encanta de esta biblioteca: el uso de estados, los re renders, manejo de ciclo de vida y más.

Manejo de estados con useState

El hook useState te permite poder usar variables de estado dentro de un componente funcional. Este es el hook que probablemente más usaras por lo que comprenderlo bien va a ser muy importante.

Hook useState vs classes

A continuación vamos a hacer un componente de tipo clase que sirva para contar los clicks que hace el usuario a un botón.

class Count extends React.Component {
  state = {
    count: 0,
  }

  handleClick = () => this.setState({ count: this.state.count + 1 })

  render() {
    const { count } = this.state
    return (
      <>
        <p>Clicks: {count}</p>
        <button onClick={this.handleClick}>Clickeame</button>
      </>
    )
  }
}

Nota que no usamos el constructor para iniciar el valor del state.

Lo definimos por fuera del constructor basados en la sintaxis alternativa, es decir, declarar el estado como una propiedad de la clase desde fuera del constructor.

Ahora vamos a hacer el ejemplo equivalente en componente funcional utilizando el hook useState.

const Count = () => {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setCount(count + 1)
  }

  return (
    <>
      <p>Clicks: {count}</p>
      <button onClick={handleClick}>Clickeame</button>
    </>
  )
}

Mucho mejor! el componente es más simple, más limpio y con el mismo poder del manejo de estado de React 😄.

Nos retorna siempre un array con dos elementos.

const [count, setCount] = useState(0)

La sintaxis que vemos con los corchetes no es de React sino de ES6 y es llamada destructuración.

En la posición cero del array nos retorna la variable de estado y la posición uno nos retorna una función que nos sirve para actualizar la variable de estado.

Por lo tanto, lo siguiente es igualmente válido para Javascript.

const MyComponent = () => {
  const state = React.useState(null)
  const myState = state[0]
  const setMyState = state[1]
}

Como puedes ver, es más limpio usar la destructuración por lo que siempre veras ejemplos de ese modo.

Y al ser destructurado, podemos nombrar las dos variables de la manera que convenga mejor a nuestros intereses.

Lo recomendado es que sean nombres con significado, que indiquen cuál es su propósito.

Ya que la segunda variable es una función que actualiza el estado, en todos los ejemplos vas a encontrar que inicia con “set” y termina con el nombre de la variable de estado.

Recibe por parámetro el valor inicial de la variable de estado

A diferencia del estado en un componente de tipo clase, la variable de estado usando este hook no tiene que ser necesariamente un objeto.

Nuestro estado puede ser una variable de los siguientes tipos:

  • String.
  • Boolean.
  • Number.
  • Float.
  • Null.
  • Undefined.
  • Object.
  • Array.
const MyComponent = () => {
  const [stateVariable, setStateVariable] = React.useState(false) // <<< pasamos un booleano
}

Para valores de estado iniciales que sean resultado de un calculo costoso, puedes pasar como valor inicial una función que retorne dicho valor calculado.

const MyComponent = () => {
  const [
     stateVariable,
     setStateVariable
   ] = React.useState(() => someExpensiveCalc(); // aqui
 };

Ten en cuenta que los valores iniciales del estado se ejecutan solo una vez y es cuando se monta el componente.

Puedes declarar mas de un estado local

Va a ser común que necesites más de una variable de estado en un componente.

En lugar de tener una sola variable de estado como un objeto que contenga múltiples valores, probablemente sea mejor separar por “preocupaciones” (separation of concerns principle).

// dentro de un componente funcional

const [username, setUsername] = useState('')
const [isAdult, setIsAdult] = useState(false)
const [age, setAge] = useState(0)
const [userTags, setUserTags] = useState([])

Te recomiendo que trates de mantener tus variables de estado lo mas simples posibles y separarlas en diferentes variables cada que puedas.

Sin embargo, a veces vas a necesitar usar arrays u objetos como variables de estado, en esos casos te recomiendo que trates de mantenerlos lo mas planos posibles.

Para estructuras más complejas (un objeto de objetos, un array de objetos que tienen objetos, etc); lo recomendable es usar el hook useRedux como veremos en su post correspondiente.

Actualizando Arrays

Es importante que tengas en cuenta la manera correcta de actualizar el estado cuando usas este tipo de datos ya que puedes caer en comportamientos inesperados.

A continuación te comparto un vídeo corto donde explico paso a paso cómo hacer un CRUD de un array con useState. Da click a la siguiente imagen.

Ciclo de vida de un componente con useEffect

EL hook useEffect es el segundo hook que más vas a usar debido a que nos ayuda en todo lo relativo a ciclos de vida de un componente y efectos secundarios.

Puedes estar pensando: "¿Y qué rayos es un efecto secundario, Juanito?"

Tal como dice la documentación de React, los siguientes son ejemplos de efectos secundarios:

  • Peticiones de datos.
  • Establecimiento de suscripciones.
  • Actualizaciones manuales del DOM.

Esto ya se viene haciendo en React en los componentes de tipo clase por medio de los ciclos de vida.

Pues bien, estos efectos secundarios ahora también los podemos hacer en componentes funcionales con este react hook.

La manera en que luce el hook useEffect es la siguiente:

const MyComponent = () => {
  useEffect(() => {
    console.log('useEffect ejecutado')
  })

  return 'componente con useEffect :o'
}

Características

  • Recibe dos parámetros: el primero una función y el segundo un array cuyos valores serán variables de las que depende nuestro efecto (este array es opcional).
  • Se ejecuta en cada renderizado incluyendo el primero.
  • Puedes usar más de un useEffect dentro de un componente.
  • Está diseñado para que si la función que pasamos por parámetro retorna a su vez otra función, React va a ejecutar dicha función cuando se desmonte el componente.

Ejemplo:

const MyComponent = ({ someProp }) => {
  useEffect(() => {
    console.log('se ejecuta cada render')
    console.log('incluso el inicial')
  })

  useEffect(() => {
    console.log('se ejecuta cada vez que se actualiza el valor de someProp', someProp)
  }, [someProp])

  useEffect(() => {
    // code
    return () => console.log('componente desmontado')
  })

  return ':)'
}

Esto es lo mínimo necesario que necesitas saber para comenzar a usar useEffect.

Pero para ponerla más fácil, vamos a ver ejemplos de casos prácticos del mundo real en el uso de este hook.

¿Cómo hacer un fetch (API Call)? ciclo de vida componentDidMount con useEffect

El siguiente ejemplo va a ser sólo para fines educativos y es simular una llamada a una API.

Lo importante de este ejemplo es comparar la llamada a una api cuando se monta un componente en un componente de tipo clase y cómo es su equivalente en un componente funcional con useEffect.

Componente de tipo clase:

class PostData extends Component {
  state = {
    data: [],
  }

  componentDidMount() {
    const data = myApi.fakeFetch()
    this.setState({ data })
  }

  render() {
    return this.state.data.map(({ label }) => <p>{label}</p>)
  }
}

Nota que estamos usando el ciclo de vida componentDidMount para llamar a una api (falsa en este ejemplo) y actualizamos el estado al tener la respuesta.

Ahora vamos a hacer su equivalente con useEffect.

const SecondPostData = () => {
  const [data, setData] = useState([])

  useEffect(() => {
    const data = myApi.fakeFetch()
    setData(data)
  }, [])

  return data.map(({ label }) => <p>{label}</p>)
}

El código es menos y más simple 😃.

Este efecto se va a ejecutar sólo cuando el componente se monte la primera vez y no volverá a ejecutarse.

Recuerda que ese array de segundo parámetro sirve para indicarle a React las dependencias de ese efecto.

Si el array de dependencias está vacío, no habrá nada que lo vuelva a ejecutar, por lo que hacerlo de este modo equivale al ciclo de vida componentDidMount.

Funciones con async await en useEffect

Quizá notaste que en el ejemplo anterior no estamos manejando la api como si fuera asíncrona.

EL hook useEffect recibe una función como primer parámetro, ¿Cierto? Esa función si le colocamos async nos va a arrojar un error en consola como:

Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect.

useEffect(async () => {}) // error en consola

¿Por qué nos arroja un error? porque React espera que las funciones de useEffect sean normales para garantizar su ejecución de manera correcta entre los renderizados.

Vamos a complementar el ejemplo colocando la sintaxis async await de manera correcta como se muestra a continuación:

useEffect(() => {
  const fetchData = async () => {
    const data = await myApi.fakeFetch()
    setData(data)
  }
  fetchData()
}, [])

Como puedes ver, hemos seguido la sugerencia que nos da el mensaje de error anterior y hemos colocado una función dentro del useEffect con la sintaxis async.

Moraleja: Hay que leer los mensajes de error 🤓.

Ciclo de vida componentDidUpdate con Hooks

Ahora vamos a extender el ejemplo en el escenario en el que se usa el ciclo componentDidUpdate para hacer una segunda llamada a una api cuando depende de un dato como el ID de un usuario.

Primero veamos el ejemplo de cómo sería en una componente de tipo clase usando el ciclo de vida componentDidUpdate:

class PostData extends Component {
  state = {
    data: [],
  }

  componentDidMount() {
    const { userId } = this.props
    const data = myApi.fakeFetch(userId)
    this.setState({
      data,
    })
  }

  // nuevo
  componentDidUpdate(prevProps) {
    const { userId } = this.props

    if (prevProps.userId !== userId) {
      const data = myApi.fakeFetch(userId)
      this.setState({
        data,
      })
    }
  }

  render() {
    return this.state.data.map(({ label }) => <p>{label}</p>)
  }
}

Nada fuera de lo común. Estamos haciendo una llamada a la api cada vez que el user id cambie.

Ahora veamos su equivalente en un componente funcional.

const SecondPostData = ({ userId }) => {
  const [data, setData] = useState([])

  useEffect(() => {
    const data = myApi.fakeFetch(userId)
    setData(data)
  }, [userId]) // <<< nuevo

  return data.map(({ label }) => <p>{label}</p>)
}

Sólo hemos pasado la variable userIdcomo dependencia dentro del array del useEffect.

Esto significa que nuestro efecto se va a ejecutar al montarse el componente y cada vez que la variable userIdcambie logrando así nuestro objetivo.

El código anterior lo puedes ejecutar y editar a continuación en este enlace.

Hook useEffect y componentWillUnmount

Vamos a usar de ejemplo la suscripción del evento de cuando se presiona la tecla Enter del teclado.

Ya que es un efecto secundario, iniciamos creando la suscripción dentro de un useEffect.

Cuando realizamos una suscripción por lo común vamos a necesitar “sanarla” o removerla cuando se desmonta el componente como en este ejemplo.

Para ello en nuestro useEffect donde inicializamos la suscripción vamos a retornar una función que contenga el código necesario para removerla.

const MyComponent = ({ someProp }) => {
  useEffect(() => {
    window.addEventListener('keydown', handleKeydown)

    return () => window.removeEventListener('keydown', handleKeydown)
  })

  const handleKeydown = ({ keyCode }) => {
    const enterKeyCode = 13

    if (keyCode === enterKeyCode) {
      alert('tecla Enter presionada')
    }
  }

  return 'Presiona Enter :)'
}

El mismo principio aplica para cualquier tipo de suscripciones que realices, ya sea a una API como firebase, otros eventos del DOM, etc.

Recapitulacion

En esta sección hemos visto lo siguiente:

  • El hook useEffect recibe dos parámetros: una función que React ejecutará en cada renderizado y un array de dependencias como opcional.
  • Para emular componentDidMount pasa un array vacío.
  • Para emular componentWillUnmount retorna una función con la lógica que quieres correr cuando se desmonte el componente, usado comúnmente en las suscripciones.
  • Para emular componentDidUpdate pasa las variables dependientes del estado en el array de dependencias del useEffect.
  • Puedes usar más de un useEffect en el mismo componente para separar responsabilidades en tu código.

¿Por qué deberías usar los Hooks en vez de componentes clase?

Los hooks vienen para resolver los siguientes problemas:

Rehusar lógica entre componentes: La manera de rehusar lógica antes de los hooks era por medio de patrones o técnicas como los render props y los componentes de orden superior (HOC por sus siglas en inglés).

El problema de esto es que la jerarquía de componentes tiende a terminar siendo un árbol complejo de envolvedores (wrapper hell).

Veremos en un apartado posterior cómo puedes crear tus propios hooks, de modo que ahora podrás rehusar lógica entre componentes de una manera más limpia y sin necesidad de modificar la jerarquía de los mismos.

Componentes complejos: En este contexto, un componente complejo es aquel que para funcionar termina ejecutando varias sentencias de código que pueden estar o no relacionadas en los mismos métodos de ciclos de vida de un componente.

Por ejemplo: puede que cuando se monte el componente, en el método componentDidMount, consumas una api y en ese mismo método inicialices una o varias subscripciones de eventos del navegador (o de algo diferente a la api).

Lo anterior ocasiona que tengamos lógica diferente mezclada en el mismo lugar.

El inconveniente de esto es que el código se hace difícil de leer y por lo tanto da pie a los errores humanos.

Otros Hooks

Hasta ahora hemos visto muy a detalle los ejemplos de useState y useEffect que serán los hoooks que más intensamente usarás en tu día a día.

Sin embargo, es bueno que conozcas el resto de los hooks para que los puedas tener en consideración en tus soluciones.

Listado de los hooks

Hooks de la comunidad recomendados: useForm, useSelector de redux y otros

Una estrategia que te recomiendo para aprender sobre mejores prácticas de desarrollo con los hooks, es leyendo el código fuente de los hooks más populares de la comunidad, ¡Es gratis! :D

Por ejemplo, yo mismo publiqué una biblioteca de NPM para manejar formularios en React JS hace unos años.

Así como yo, hay una gran cantidad de personas creadoras que comparten su trabajo por medio de código open source. Por ejemplo:

También te recomiendo estos repositorios que contienen una gran cantidad de ejemplos de custom hooks:

Manejo de formularios con Hooks

Manejar formularios es una de las característica que casi todas las apliciones tienen, y por lo tanto, una de las principales actividades de nuestro día a día.

En la sección anterior te mencioné que había publicado una biblioteca de NPM para manejar formularios en React, pues bien, en este post te comparto cómo fue mi proceso mental al mismo tiempo que te explico cómo manejar formularios desde cero hasta de manera avanzada con HOC y con un custom hook.

React Hooks Testing Library

Errores comunes y cómo solucionarlos

react hooks must be called in a react function component or a custom react hook function

must be called in the exact same order in every component render

react hooks useeffect has a missing dependency

Siguientes pasos para subir al siguiente nivel de seniority

Te dejo links de mis cursos premium de Udemy donde te ayudo a subir tu nivel de seniority 🤓 ☕️.