Artículos de Tecnología > Front End

¿Cuál es el tipo de un React Hook?

Mônica Hillman
Mônica Hillman
img-portada-react-hooks alt="Imagen de portada del artículo. Un escritorio con una computadora, papeles y una taza, una ventana donde se puede ver las luces de la cuidad, el color predominante es azul. "

Estás listo para embarcarte en un viaje en la construcción de componentes React con TypeScript? A medida que profundices en el desarrollo front-end con la biblioteca React, pronto te darás cuenta de la necesidad de especificar los tipos de datos que se utilizan en tu código. Es como elegir los Pokémon adecuados para la batalla, asegurándote de que sean compatibles con tus estrategias.

img1-gif-pokemon alt="El personaje Ash del anime Pokémon, un adolescente de cabello oscuro, ojos marrones y una expresión decidida en su rostro. Lleva puesto una gorra de béisbol roja con blanco. También lleva una chaqueta azul y blanca y guantes verdes. Está lanzando una Pokébola roja y blanca."

En este artículo, abordaremos el tipado de los hooks nativos de React. Además, crearemos y aplicaremos tipos en un hook personalizado. También aprenderás a manejar tipos dinámicos e inferencia, del mismo modo que un entrenador experimentado adapta su estrategia durante un combate Pokémon.

[TOC]

Hooks? ¿Quién es ese Pokémon?

Los hooks de React son funciones especiales que permiten "conectar" estados y comportamientos a componentes funcionales de React. Se introdujeron en React 16.8 y proporcionaron una forma más sencilla y eficiente de gestionar estados y efectos secundarios en componentes funcionales, haciéndolos más potentes y flexibles. Algunos de los hooks más comunes incluyen useState, useEffect, useContext, entre otros.

Ayudan a los desarrolladores a organizar el código de manera más modular y reutilizable, facilitando el desarrollo de aplicaciones React más sólidas y escalables. Puedes utilizar los hooks nativos de React o crear tus propios hooks personalizados (hooks personalizados).

Hooks nativos de React

Los hooks nativos de React son aquellos que forman parte de la biblioteca React y se utilizan para crear y manipular el estado, los efectos y el contexto de los componentes. Los principales hooks nativos son:

useState

El hook useState crea un estado local para un componente y devuelve un array con dos posiciones: la primera es el valor actual del estado y la segunda es una función para actualizar ese valor.

Para entenderlo mejor, imaginemos una analogía con Pokémon. Supongamos que tienes una Poké Ball que puede almacenar un Pokémon en su interior. Puedes abrir la Poké Ball para ver qué Pokémon hay dentro o cambiar el Pokémon por otro. Esta Poké Ball es como el hook useState: almacena un valor (el nombre del Pokémon) y te proporciona una forma de cambiar ese valor (cambiar el Pokémon).

Ahora, imagina que quieres crear un componente React que muestre el nombre del Pokémon en la pantalla y un botón para cambiarlo por otro Pokémon aleatorio. Podrías usar un hook llamado useState para crear una Poké Ball que almacene el nombre del Pokémon y una función para cambiarlo. Así es como quedaría el código:

import { useState } from "react";

const pokemons = ["Pikachu", "Charmander", "Bulbasaur", "Squirtle", "Eevee"];

Aquí definimos un arreglo llamado pokemons que contiene una lista de nombres de Pokémon. Cada elemento del arreglo es una cadena de texto.

function getPokemonAleatorio() {
  const index = Math.floor(Math.random() * pokemons.length);
  return pokemons[index];
}

La función getPokemonAleatorio devuelve un Pokémon aleatorio de la lista "pokemons" que creamos anteriormente.

function pokemon() {
    const [pokemon, setPokemon] = useState<string>('Pikachu');

    function cambiarPokemon() {
    const nuevoPokemon = getPokemonAleatorio();
    setPokemon(nuevoPokemon);
}

Aquí estamos creando un componente llamado "pokemon" que utiliza useState de React para gestionar un estado llamado "pokemon". El estado se inicializa con el valor "Pikachu" como una cadena de texto. El componente también tiene una función llamada cambiarPokemon Esta función llama a la función getPokemonAleatorio() para obtener un nombre de Pokémon aleatorio. Luego, actualiza el estado "pokemon" con el nuevo nombre de Pokémon obtenido, utilizando la función setPokemon(nuevoPokemon).

    return (
        <div className="Pokemon">
            <h1>{Pokemon}</h1>
            <button onClick={cambiarPokemon}>Cambiar</button>
        </div>
    );
}

export default Pokemon;

Ahora hemos construido la parte visual del componente, que incluye un container, un título con el nombre del Pokémon y un botón que llama a la función cambiarPokemon cuando se hace clic, lo que resulta en:

img2-gif-pokemon alt="Título que cambia entre los nombres Pikachu, Charmander y Squirtle al hacer clic en el botón con el texto "Cambiar".."

useEffect

El hook useEffect ejecuta una función cada vez que cambia una dependencia y devuelve una función opcional para limpiar los efectos secundarios.

Imagina que quieres ver más información sobre el Pokémon que está dentro de tu Poké Ball, como su imagen y sus tipos. Podrías usar otro hook llamado useEffect para obtener estos datos de una API(Application Programming Interface) externa al cambiar el nombre del Pokémon. La API que utilizaremos es PokeApi, una API pública y gratuita que proporciona información detallada sobre Pokémon, incluyendo datos sobre los propios Pokémon, tipos, movimientos, evoluciones, estadísticas, sprites (imágenes) y mucho más.

Así es cómo se vería el código:

import { useEffect, useState } from "react";

interface pokemon {
    nombre: string;
    image: string;
}

Estamos importando dos funciones proporcionadas por la biblioteca React: useEffect y useState. Además, hemos definido una interfaz de TypeScript llamada "pokemon",en la que hemos establecido que un objeto de tipo "pokemon" debe tener dos propiedades obligatorias: "nombre" e "imagen". Ambas propiedades son de tipo strings, lo que significa que esperamos que el nombre del Pokémon y la ruta de la imagen se representen como como strings.

function pokemon({ nombre }: { nombre: string }) {
    const [pokemon, setPokemon] = useState<Pokemon | null>(null);

Aquí estamos creando una función componente llamada "pokemon". Este componente recibe un solo argumento llamado "nombre", que es un objeto desestructurado con una propiedad "nombre" de tipo cadena de texto. Esto significa que cuando usamos este componente, debemos pasar un valor de cadena de texto a la propiedad "nombre". Dentro del componente, estamos usando el el hook useState para crear un estado local.

Estamos inicializando el estado del Pokémon con null, lo que significa que inicialmente no tenemos información sobre el Pokémon. Además, estamos utilizando la notación <pokemon | null> para tipar el estado. Esto significa que el estado puede ser del tipo "pokemon" (cuando tengamos información del Pokémon) o null (cuando aún no tengamos información).

useEffect(() => {
    async function buscaPokemon() {
        const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${nombre}`);
        const fecha = await response.json();

        const pokemon: pokemon = {
            nombre: fecha.name,
            imagem: fecha.sprites.front_default,
        };

        setPokemon(pokemon);
    }

    if (nombre) {
        buscaPokemon();
    }
}, [nombre]); 

El useEffect es un hook de React que te permite ejecutar código en respuesta a cambios específicos en las dependencias, en este caso, la dependencia es [nombre]. Esto significa que el código dentro de este bloque se activará cada vez que cambie el valor de la variable nombre.

Dentro del useEffect, se define una función asincrónica llamada buscaPokemon(). Esta función será responsable de realizar solicitudes a la API de Pokémon y obtener datos de ese Pokémon. Después de recibir la respuesta de la API, los datos se extraen del formato JSON y se almacenan en la variable fecha.

Se crea un nuevo objeto llamado pokemon, contiene la información del Pokémon que se obtuvo de la API. Este objeto tiene las propiedades nombre e imagen, que representan el nombre del Pokémon y la URL de su imagen. Utilizamos la función setPokemon para actualizar el estado del componente con el objeto Pokemon creado.

Esto permitirá que los datos de Pokémon se muestren en la interfaz de usuario. Antes de llamar a la función buscapokemon(), verificamos que el nombre no esté vacío, asegurando que la búsqueda solo se realice cuando se proporciona un nombre válido, evitando solicitudes API innecesarias.


    return (
        <div className="Pokemon">
            <>
                {Pokemon ? ( 
                    <>
                        <h1>{Pokemon.nombre}</h1>
                        <img src={Pokemon.image} alt={Pokemon.nombre} />
                    </>
                ) : (
                    <p>Carregando...</p> 
                )}
            </>
        </div>
    );
}

export default Pokemon;

Aquí hemos completado la parte de renderizado del componente Pokémon y la visualización de información de Pokémon en la interfaz de usuario utilizando los datos obtenidos de la API. El resultado es el siguiente:

img3-charmander alt="Un título escrito "Charmander" y una imagen de un pequeño lagarto de color anaranjado con una llama en la punta de la cola, que es un personaje que forma parte de la franquicia Pokémon.''

useContext

"useContext" es un hook de React que permite acceder a los valores de un contexto. Vamos a simular un contexto relacionado con el mundo Pokémon, donde tenemos información sobre el entrenador de Pokémon y analizar cómo se utiliza "useContext". Primero, crea un archivo para el contexto, por ejemplo, "pokemonContext.tsx":

import { createContext, useContext, useState, ReactNode } from 'react';

// Interfaz para representar el contexto
interface pokemonContextType {
  entrenador: string;
  setEntrenador: React.Dispatch<React.SetStateAction<string>>;
}

// Crear el contexto
const pokemonContext = createContext<pokemonContextType | undefined>(undefined);

// Proveedor del contexto
interface pokemonProviderProps {
  children: ReactNode;
}

export function pokemonProvider({ children }: pokemonProviderProps) {
  const [entrenador, setEntrenador] = useState('Ash Ketchum');

  return (
    <pokemonContext.Provider value={{ entrenador, setEntrenador }}>
      {children}
    </pokemonContext.Provider>
  );
}

// Hook personalizado para usar el contexto
export function usePokemon() {
  const context = useContext(pokemonContext);
  if (!context) {
    throw an Error('usePokemon debe ser utilizado dentro de un pokemonProvider');
  }
  return context;
}

En este ejemplo de TypeScript, creamos un contexto utilizando createContext. Este contexto es donde se almacenarán y compartirán los datos entre los componentes. En este caso, el contexto se llama "pokemonContext". "pokemonContext" se crea con el tipo "pokemonContextType | undefined". También hemos definido una interfaz llamada "pokemonContextType" para representar el formato de datos. Si quieres más información sobre cómo crear contextos, consulta el curso "React: Hooks, contextos y buenas prácticas".

Ahora, vamos a crear un componente que consuma esta información en TypeScript. Por ejemplo, en un archivo llamado "entrenadorPokemon.tsx":

import React from 'react';
import { usePokemon } from './PokemonContext';

function EntrenadorPokemon() {
  const { entrenador, setEntrenador } = usePokemon();

  return (
    <div>
      <h2>Entrenador Pokémon:</h2>
      <p>{entrenador}</p>
      <button onClick={() => setEntrenador('Misty')}>Cambiar Entrenador</button>
    </div>
  );
}

export default EntrenadorPokemon;

Aquí hemos construido la parte visual de este componente que consta de un contenedor con el título "Entrenador Pokémon", su nombre y un botón que utiliza setEntrenador para actualizar el nombre en todos los componentes que utilizan este contexto.

Asegúrate de envolver tu componente principal con pokemonProvider para que el contexto esté disponible para los componentes que lo consumen:

<pokemonProvider>
  <EntrenadorPokemon />
</pokemonProvider>

El resultado en tu pantalla será el siguiente:

img4-gif-pokemon alt="Gif con un título que dice "Entrenador Pokémon" y el nombre "Ash Ketchum" se cambian por "Misty" al hacer clic en el botón "Cambiar entrenador"."

Observa que podemos acceder a entrenador y setEntrenador directamente en EntrenadorPokemon. Cuando haces clic en el botón "Cambiar Entrenador", se llama a la función setEntrenador y se actualiza el valor del contexto, lo que, a su vez, actualiza todos los componentes que consumen el contexto.

En resumen, "useContext" permite que accedas a los valores de un contexto en componentes específicos, sin la necesidad de pasar datos manualmente a través de props. Esto simplifica la comunicación entre componentes y es particularmente útil cuando necesitas compartir datos o estados en varios niveles del árbol de componentes.

Custom Hooks

Los hooks personalizados son funciones que siguen la convención de comenzar con "use" y pueden utilizar otros hooks dentro de ellas. Permiten extraer la lógica del estado de un componente y reutilizarla en otros componentes. Un ejemplo de hook personalizado es "usePokemon", que se creó en el ejemplo anterior.

Para profundizar aún más en el tema, crearemos un nuevo hook personalizado llamado "usePokeball", en un nuevo archivo llamado "usePokeball.tsx":

import { useState } from 'react';
type PokeballState = 'abierta' | 'cerrada';

En primer lugar, definimos el tipo para representar el estado de la Poké Ball, que son las cadenas "abiertas" o "cerradas". Esto significa que una variable o parámetro del tipo PokeballState solo puede tener uno de estos dos valores como valor posible. Es decir, una variable de tipo PokeballState puede ser igual a abierta o cerrada, y no se permite ningún otro valor.

function useallallPokeball(): [PokeballState, () => void, () => void] {
    // Utiliza useState para gestionar el estado de la Poké Ball
    const [estadoPokeball, setEstadoPokeball] = useState<PokeballState>('cerrada');

    // Función para abrir la Poké Ball
    const abrirPokeball = () => {
        setEstadoPokeball('abierta');
    };

    // Función para cerrar la Poké Ball
    const cerrarPokeball = () => {
        setEstadoPokeball('cerrada');
    };

    return [estadoallPokeball, abrirPokeball, cerrarPokeball];
}

export default usePokeball;

Ahora, hablando sobre el hook personalizado "usePokeball" en sí, es una función que no recibe argumentos y especifica que devuelve una tupla que contiene tres elementos. La primera posición de la tupla es de tipo "PokeballState", que representa el estado de la Poké Ball (abierta o cerrada). Las dos siguientes posiciones son funciones que no reciben argumentos y no devuelven nada.

Dentro de este hook, utilizamos el hook "useState" para crear una variable de estado llamada estadoPokeball que se inicializa con el valor cerrada. El tipo del estado se especifica como "PokeballState", asegurando que solo pueda contener los valores 'abierta' o 'cerrada'.

A continuación, definimos dos funciones, abrirPokeball y cerrarPokeball, las cuales sirven para cambiar el estado de la Poké Ball. Finalmente, la función usePokeball devuelve una tupla que contiene el estado actual de la Poké Ball (estadoPokeball) y las funciones para abrir (abrirPokeball) y cerrar (cerrarPokeball) la Poké Ball.

Utilizando este hook personalizado en un componente React, puedes controlar fácilmente el estado de la Pokébola en tu aplicación, alternando entre los estados 'abierta' y 'cerrada' según sea necesario. ¿Vamos a utilizar este hook personalizado en un componente React?

import usePokeball from '../usePokeball';
import PokeballAbierta from "./pokeballabierta.png";
import PokeballCerrada from "./pokeballcerrada.png";

function pokeball() {
    // Utiliza el hook usePokeball
    const [estadoPokeball, abrirPokeball, cerrarPokeball] = usePokeball();

    return (
        <div>
            <img src={estadoPokeball === "abierta"
                ? PokeballAbierta
                : PokeballCerrada} alt={`Pokeball ${estadoPokeball}`} />
            <div>
                <button onClick={abrirPokeball}>Abrir Pokéball</button>
                <button onClick={cerrarPokeball}>Cerrar Pokéball</button>
            </div>
        </div>
    );
}

export default Pokeball;

Finalmente, hemos construido la parte visual de este componente que consta de un container con una imagen que, según el estado de la Poké Ball, renderiza la imagen de una Poké Ball abierta o cerrada. También hay botones para abrir o cerrar la Poké Ball, lo que resulta en:

img5-gif-pokemon alt="Imágenes de una bola roja y blanca, llamada Pokébola, alternando entre abierta y cerrada, según qué botón se esté haciendo clic: abrir la Pokébola o cerrarla."

Puedes crear tus propios hooks personalizados para reutilizar la lógica de estado entre los componentes que la necesiten. También puedes usar hooks personalizados creados por otras personas, como los disponibles en la biblioteca useHooks.

Hooks con tipos dinámicos

Los tipos dinámicos en los hooks son tipos que no están definidos explícitamente, pero React los infiere a partir de los valores que pasan o regresan a los hooks.

Por ejemplo, si utilizas el hook useState para crear un estado que almacena un número, React inferirá que el tipo de ese estado es number, incluso si no lo has especificado.

De manera similar, si utilizas el hook useContext para acceder a un contexto que proporciona un objeto con propiedades como name y age, React inferirá que el tipo de ese contexto es { name: string, age: number }, aunque no lo hayas definido explícitamente.

Aquí tienes un ejemplo de código que utiliza tipos dinámicos en los hooks:

import { useState } from 'react';

function pokemonLevel() {
    const [pokemonLevel, setPokemonLevel] = useState(21);
    // ...
}

El componente utiliza el hook "useState "para crear un estado llamado pokemonLevel y una función setPokemonLevel para actualizar ese estado. El estado se inicializa con el valor 21, que es el nivel inicial de Pokémon Ninetales.

Este estado es un ejemplo de tipo dinámico, ya que no especificamos su tipo explícitamente. En su lugar, TypeScript infiere el tipo basándose en el valor inicial que es 21. Esto significa que TypeScript determina que pokemonLevel es un número (number) basado en el valor asignado inicialmente.

const aumentarNivel = () => {
    setPokemonLevel(pokemonLevel + 1);
};

const disminuirNivel = () => {
    if (pokemonLevel > 0) {
        setPokemonLevel(pokemonLevel - 1);
    }
};

El componente tiene dos funciones:

aumentarNivel: Utiliza la función setPokemonLevel para aumentar el nivel del Pokémon en 1 unidad. El nivel solo puede aumentarse si ya es mayor que 0.

disminuirNivel: Utiliza la función setPokemonLevel para disminuir el nivel del Pokémon en 1 unidad, siempre cuando el nivel actual sea mayor que 0.

return (
    <div>
        <h1>Ninetales</h1>
        <img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/38.png" alt="Imagen del Pokémon Ninetales" />
        <p>Nivel actual del Pokémon: {pokemonLevel}</p>
        <button onClick={aumentarNivel}>Aumentar Nivel</button>
        <button onClick={disminuirNivel}>Disminuir Nivel</button>
    </div>
);

En la parte de renderización del componente, mostramos un título con el nombre "Ninetales" y una imagen de ese Pokémon. También tenemos un párrafo que muestra el nivel actual basado en el valor del estado pokemonLevel, y dos botones <button>, uno para aumentar el nivel y otro para disminuir el nivel del Pokémon. Están asociados a las funciones aumentarNivel y disminuirNivel, respectivamente, lo que resulta en el siguiente componente:

img6-ninetales alt="Título con el nombre "Ninetales" y luego su representación gráfica, que es un zorro de nueve colas."

Justo debajo de la imagen, puedes encuentrar el indicador "Nivel Actual del Pokémon", que muestra el valor 21. Este número representa el nivel actual de poder y experiencia de Ninetales. Abajo, están dos botones ubicados junto al indicador de nivel: el botón "Aumentar Nivel" y el botón "Disminuir Nivel"."

Sin embargo, el uso de tipos dinámicos puede causar varios problemas, como:

Para evitar estos problemas, se recomienda tipar los hooks de manera explícita. Esto te ayudará a evitar errores de tipo y mejorar la legibilidad del código.

Conclusión

Al igual que los entrenadores dedican tiempo a entrenar y fortalecer a sus Pokémon, tú, como desarrollador, has mejorado tus habilidades al comprender cómo especificar tipos para tus hooks, protegiendo tu código contra errores y facilitando la colaboración con otros miembros del equipo.

Así como los Pokémon evolucionan y se vuelven más poderosos, tu conocimiento en TypeScript y React se ha expandido, permitiéndote crear aplicaciones más robustas y efectivas.

img7-gif-pokemon alt="Pokémon Pikachu saludando al atardecer."

Recuerda que, al igual que cada Pokémon tiene sus propias características y fortalezas, cada proyecto de React puede tener necesidades de tipado únicas. Para embarcarte en este viaje de mejora en React con TypeScript, puedes confiar en el Tech Guide como tu guía, al igual que un Entrenador Pokémon confía en su Pokédex.

¡Con dedicación y práctica continua, estarás listo para enfrentar todas las batallas y desafíos de desarrollo que se crucen en tu camino! ¡Nos vemos por ahí! :)

img-autora

Mónica Mazzochi Hillman Licenciada en Tecnologías Digitales, especialista en Experiencia de Usuario y estudiante de posgrado en Docencia y Desempeño en Educación a Distancia con experiencia en soporte técnico de T.I y tecnologías front-end. Durante la licenciatura, fue vicepresidenta del directorio académico y monitora de LIBRAS. Actualmente es instructora en Alura. En su tiempo libre, le gusta ver animes y producciones de Marvel, escuchar K-pop y post-hardcore, jugar a Valorant y Genshin Impact y practicar la cocina.

Artículos de Tecnología > Front End

En Alura encontrarás variados cursos sobre Front End. ¡Comienza ahora!

Precios en:
USD
  • USD
  • BOB
  • CLP
  • COP
  • USD
  • PEN
  • MXN
  • UYU

Semestral

  • 273 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana

  • Acceso a todo el contenido de la plataforma por 6 meses
US$ 65.90
un solo pago de US$ 65.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Anual

  • 273 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana

  • Acceso a todo el contenido de la plataforma por 12 meses
US$ 99.90
un solo pago de US$ 99.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Acceso a todos
los cursos

Estudia las 24 horas,
dónde y cuándo quieras

Nuevos cursos
cada semana