DEV Community

Cover image for Haciendo un fetching de datos y creando un custom hook. 馃獫
Franklin Martinez
Franklin Martinez

Posted on • Updated on

Haciendo un fetching de datos y creando un custom hook. 馃獫

El prop贸sito de este post es ense帽ar una manera de como realizar peticiones HTTP de tipo GET mediante el uso de React y un custom hook.

馃毃 Nota: Este post requiere que sepas las bases de React (hooks b谩sicos y peticiones con fetch).

Cualquier tipo de Feedback es bienvenido, gracias y espero disfrutes el articulo.馃

Tabla de contenido

馃搶 Tecnolog铆as a utilizar

馃搶 Creando el proyecto

馃搶 Primeros pasos

馃搶 Haciendo nuestro primer fetch

馃搶 Mostrando los datos de la API en pantalla

馃搶 Creando un custom hook

馃搶 Mejorando el hook useFetch

馃搶 Agregando m谩s componentes y refactorizando

馃搷 Header.tsx

馃搷 Loading.tsx

馃搷 ErrorMessage.tsx

馃搷 Card.tsx

馃搷 LayoutCards.tsx


馃毃 Tecnolog铆as a utilizar.

鈻讹笍 React JS (version 18)

鈻讹笍 Vite JS

鈻讹笍 TypeScript

鈻讹笍 Rick and Morty API

鈻讹笍 CSS vanilla (Los estilos los encuentras en el repositorio al final de este post)

銆斤笍 Creando el proyecto.

npm init vite@latest
Enter fullscreen mode Exit fullscreen mode

En este caso le colocaremos de nombre: fetching-data-custom-hook (opcional).

Seleccionaremos React y luego TypeScript.

Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.

cd fetching-data-custom-hook
Enter fullscreen mode Exit fullscreen mode

Luego instalamos las dependencias:

npm install
Enter fullscreen mode Exit fullscreen mode

Despu茅s abrimos el proyecto en un editor de c贸digo (en mi caso VS code)

code .
Enter fullscreen mode Exit fullscreen mode

銆斤笍 Primeros pasos.

Dentro de la carpeta src/App.tsx borramos todo el contenido del archivo y colocamos un componente funcional que muestre un titulo y un subtitulo.

const App = () => {
  return (
            <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Mostrando titulo y subtitulo

Antes que nada, crearemos un par de interfaces que nos ayudaran al auto completado de las propiedades que viene en la respuesta JSON que nos proporciona la API.

  • La primera interfaz Response contiene la propiedad results que es un arreglo de Results.
  • La segunda interfaz Result, solo contiene 3 propiedades (aunque hay m谩s, puedes revisar la documentaci贸n de la API), seleccione un ID, el nombre y la imagen del personaje.
interface Response {
  results: Result[]
}

interface Result {
  id: number;
  name: string;
  image: string;
}
Enter fullscreen mode Exit fullscreen mode

銆斤笍 Haciendo nuestro primer Fetch.

  1. Primero agregamos un estado que sea de tipo Result[] y el valor por defecto sera un arreglo vac铆o ya que aun no hacemos la llamada a la API. Esto nos servir谩 para almacenar los datos de la API y poder mostrarlos.
const App = () => {

  const [data, setData] = useState<Result[]>([]);

  return (
        <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode
  1. Para poder realizar un fetch de datos, tenemos que hacerlo en un useEffect, ya que necesitamos ejecutar el fetch cuando nuestro componente se renderice por primera vez.

Como necesitamos que solo se ejecuta una vez, colocamos un arreglo vaci贸 (o sea, sin dependencia alguna).

const App = () => {
  const [data, setData] = useState<Result[]>([]);

    useEffect(()=> {

    },[]) // arreglo vaci贸

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
    </div>
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode
  1. Dentro del cuerpo de la funci贸n del useEffect, se har谩 la llamada a la API, y como el useEffect no nos permite usar c贸digo as铆ncrono directamente, haremos la llamada mediante promesas por mientras.
const [data, setData] = useState<Result[]>([]);

useEffect(()=> {
   fetch('https://rickandmortyapi.com/api/character/?page=8')
   .then( res => res.json())
   .then( (res: Response) => {})
   .catch(console.log)   
},[])
Enter fullscreen mode Exit fullscreen mode
  1. Una vez resuelta las promesas, obtendremos la data correspondiente a la API, la cual la colocaremos al estado mediante la funci贸n setData

Con esto ya podr铆amos mostrar los datos en pantalla. 馃槍

馃毃 Si algo sale mal con la API, el catch se encargara de atrapar el error y mostrarlo por consola y el valor del estado 鈥data鈥 se queda como arreglo vaci贸 (y al final no se mostrara nada mas que el titulo y subtitulo de la app).

const [data, setData] = useState<Result[]>([]);

useEffect(()=> {
   fetch('https://rickandmortyapi.com/api/character/?page=8')
   .then( res => res.json())
   .then( (res: Response) =>  {
      setData(res.results);
   })
   .catch(console.log)   
},[])
Enter fullscreen mode Exit fullscreen mode

銆斤笍 Mostrando los datos de la API en pantalla.

Antes de mostrar los datos de la API, debemos hacer una evaluaci贸n. 馃

馃數 Solo si la longitud del valor del estado 鈥data鈥 es mayor a 0, mostramos los datos de la API en pantalla

馃數 Si la longitud de del valor del estado 鈥data鈥 es menor o igual a 0, no se mostrara ning煤n dato en pantalla, solamente el titulo y subtitulo.

const App = () => {
    const [data, setData] = useState<Result[]>([]);

    useEffect(()=> {
       fetch('https://rickandmortyapi.com/api/character/?page=8')
       .then( res => res.json())
       .then( (res: Response) =>  {
          setData(res.results);
       })
       .catch(console.log)   
    },[])

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
      {
        (data.length > 0) && <p>data</p>
      }
    </div>
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Ahora, una vez confirmado que si tenemos datos en el valor del estado 鈥data鈥, proseguiremos a mostrar y moldear los datos.

Mediante la funci贸n map que se usa en arreglos. Recorremos el arreglo del valor del estado 鈥data鈥 y retornaremos un nuevo componente JSX que en este caso solo sera una imagen y un texto.

馃敶 NOTA: la propiedad key dentro del div, es un identificador que usa React en las listas, para renderizar los componentes de forma m谩s eficiente. Es importante colocarla.

const App = () => {
    const [data, setData] = useState<Result[]>([]);

    useEffect(()=> {
       fetch('https://rickandmortyapi.com/api/character/?page=8')
       .then( res => res.json())
       .then( (res: Response) =>  {
          setData(res.results);
       })
       .catch(console.log)   
    },[])

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
      {
        (data.length > 0) && data.map( ({ id, image, name }) => (
          <div key={id}> 
            <img src={image} alt={image} /> 
            <p>{name}</p> 
          </div>
        ))
      }
    </div>
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode

De esta forma hemos acabado de realizar un fetching de datos y mostrarlos en pantalla correctamente. Pero aun podemos mejorarlo. 馃槑

Mostrando cards

銆斤笍 Creando un custom hook.

Dentro de la carpeta src/hook creamos un archivo llamado useFetch.

Creamos la funci贸n y cortamos la l贸gica del componente App.tsx

const App = () => {

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
      {
        (data.length > 0) && data.map( ({ id, image, name }) => (
          <div key={id}> 
            <img src={image} alt={image} /> 
            <p>{name}</p> 
          </div>
        ))
      }
    </div>
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Pegamos la l贸gica dentro de esta funci贸n, y al final retornamos el valor del estado 鈥data.鈥

export const useFetch = () => {
  const [data, setData] = useState<Result[]>([]);

  useEffect(()=> {
     fetch('https://rickandmortyapi.com/api/character/?page=8')
     .then( res => res.json())
     .then( (res: Response) =>  {
        setData(res.results);
     })
     .catch(console.log)   
  },[]);

  return {
    data
  }
}
Enter fullscreen mode Exit fullscreen mode

Por ultimo, hacemos la llamada al hook useFetch extrayendo la data.

Y listo, nuestro componente queda aun m谩s limpio y f谩cil de leer. 馃

const App = () => {

  const { data } = useFetch();

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
      {
        (data.length > 0) && data.map( ({ id, image, name }) => (
          <div key={id}> 
            <img src={image} alt={image} /> 
            <p>{name}</p> 
          </div>
        ))
      }
    </div>
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Pero espera, aun podemos mejorar este hook. 馃く

銆斤笍 Mejorando el hook useFetch.

Ahora lo que haremos sera mejorar el hook, agregando m谩s propiedades.

Al estado existente le agregaremos otras propiedades y este nuevo estado sera del tipo DataState

interface DataState {
    loading: boolean;
    data: Result[];
    error: string | null;
}
Enter fullscreen mode Exit fullscreen mode

馃數 loading, valor booleano, nos permitir谩 saber cuando se este haciendo la llamada a la API. Por defecto el valor estar谩 en true.

馃數 error, valor string o nulo, nos mostrar谩 un mensaje de error. Por defecto el valor estar谩 en null.

馃數 data, valor de tipo Result[] , nos mostrara la data de la API. Por defecto el valor ser谩 un arreglo vaci贸.

馃敶 NOTA: se acaba de renombrar las propiedades del estate

馃數 data 鉃★笍 dataState

馃數 setData 鉃★笍 setDataState

export const useFetch = () => {
    const [dataState, setDataState] = useState<DataState>({
      data: [],
      loading: true,
      error: null
  });

  useEffect(()=> {
     fetch('https://rickandmortyapi.com/api/character/?page=8')
     .then( res => res.json())
     .then( (res: Response) =>  {
        setData(res.results);
     })
     .catch(console.log)   
  },[]);

  return {
    data
  }
}
Enter fullscreen mode Exit fullscreen mode

Ahora sacaremos la l贸gica del useEffect en una funci贸n aparte. dicha funci贸n tendr谩 el nombre de handleFetch.

Usaremos useCallback, para memorizar esta funci贸n y evitar que se vuelva a crear cuando el estado cambie.

El useCallback tambi茅n recibe un arreglo de dependencias, en este caso lo dejaremos vaci贸 ya que solo queremos que se genere una vez.

const handleFetch = useCallback(
    () => {},
    [],
)
Enter fullscreen mode Exit fullscreen mode

La funci贸n que recibe en useCallback, puede ser as铆ncrona por lo cual podemos usar async/await.

  1. Primero colocamos un try/catch para manejar los errores.
  2. Luego creamos una constante con el valor de la URL para hacer la llamada a la API.
  3. Hacemos la llamada a la API usando fetch y le mandamos la URL (el await nos permitir谩 esperar una respuesta ya sea correcta o err贸nea, en caso de error, se ir铆a directo a la funci贸n catch).
const handleFetch = useCallback(
      async () => {
          try {
                            const url = 'https://rickandmortyapi.com/api/character/?page=18';
              const response = await fetch(url);
          } catch (error) {}
        },
        [],
    )
Enter fullscreen mode Exit fullscreen mode
  1. Despu茅s evaluamos la respuesta, si hay un error entonces activamos el catch y mandamos el error que nos da la API.
  2. En el catch, vamos a settear el estado. Llamamos al setDataState, le pasamos una funci贸n para obtener los valores anteriores (prev). Retornamos lo siguiente.
    1. Esparcimos las propiedades anteriores (鈥rev), que en este caso solo sera el valor de la propiedad data, que terminara siendo un arreglo vaci贸.
    2. loading, lo colocamos en false.
    3. error, casteamos el valor del par谩metro error que recibe el catch para poder obtener el mensaje y colocarlo en esta propiedad.
const handleFetch = useCallback(
      async () => {
          try {
                            const url = 'https://rickandmortyapi.com/api/character/?page=18';
              const response = await fetch(url);

              if(!response.ok) throw new Error(response.statusText);

          } catch (error) {

              setDataState( prev => ({
                  ...prev,
                  loading: false,
                  error: (error as Error).message
              }));
          }
        },
        [],
    )
Enter fullscreen mode Exit fullscreen mode
  1. Si no hay error por parte de la API, obtenemos la informaci贸n, y setteamos el estado de una manera similar a como lo hicimos en el catch.
  2. Llamamos al setDataState, le pasamos una funci贸n para obtener los valores anteriores (prev). Retornamos lo siguiente.
    1. Esparcimos las propiedades anteriores (鈥rev), que en este caso solo sera el valor de la propiedad error que terminara siendo un nulo.
    2. loading, lo colocamos en false.
    3. data, sera el valor de la contante dataApi accediendo a su propiedad results.
const handleFetch = useCallback(
      async () => {
          try {
                            const url = 'https://rickandmortyapi.com/api/character/?page=18';
              const response = await fetch(url);

              if(!response.ok) throw new Error(response.statusText);

              const dataApi: Response = await response.json();

              setDataState( prev => ({
                  ...prev,
                  loading: false,
                  data: dataApi.results
              }));

          } catch (error) {

              setDataState( prev => ({
                  ...prev,
                  loading: false,
                  error: (error as Error).message
              }));
          }
        },
        [],
    )
Enter fullscreen mode Exit fullscreen mode

Despu茅s de crear la funci贸n handleFetch, nos regresamos al useEffect al cual le quitamos la l贸gica y agregamos lo siguiente.

Evaluamos si el valor del estado 鈥dataState鈥 accediendo a la propiedad data, contiene una longitud igual a 0, entonces queremos que se ejecute la funci贸n . Esto es para evitar que se llame m谩s de una vez dicha funci贸n.

useEffect(() => {
    if (dataState.data.length === 0) handleFetch();
}, []);
Enter fullscreen mode Exit fullscreen mode

Y el hook quedar铆a de esta manera:

馃敶 NOTA: al final del hook, retornamos, mediante el operador spread, el valor del estado 鈥dataState鈥.

馃敶 NOTA: se movieron las interfaces a su carpeta respectiva, dentro de src/interfaces.

import { useState, useEffect, useCallback } from 'react';
import { DataState, Response } from '../interface';

const url = 'https://rickandmortyapi.com/api/character/?page=18';

export const useFetch = () => {

    const [dataState, setDataState] = useState<DataState>({
        data: [],
        loading: true,
        error: null
    });

    const handleFetch = useCallback(
        async () => {
            try {
                const response = await fetch(url);

                if(!response.ok) throw new Error(response.statusText);

                const dataApi: Response = await response.json();

                setDataState( prev => ({
                    ...prev,
                    loading: false,
                    data: dataApi.results
                }));

            } catch (error) {

                setDataState( prev => ({
                    ...prev,
                    loading: false,
                    error: (error as Error).message
                }));
            }
        },
        [],
    )

    useEffect(() => {
        if (dataState.data.length === 0) handleFetch();
    }, []);

    return {
        ...dataState
    }
}
Enter fullscreen mode Exit fullscreen mode

Antes de usar las nuevas propiedades de este hook, haremos una refactorizaci贸n y crearemos m谩s componentes. 馃槼

銆斤笍 Agregando m谩s componentes y refactorizando.

Lo primero es crear una carpeta components dentro de src.

Dentro de la carpeta componentes creamos los siguientes archivos.

馃煛 Header.tsx

Dentro de este componente solo estar谩n el titulo y el subtitulo anteriormente creados. 馃槈

export const Header = () => {
    return (
        <>
            <h1 className="title">Fetching data and create custom Hook</h1>
            <span className="subtitle">using Rick and Morty API</span>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

馃煛 Loading.tsx

Este componente solo se mostrar谩 si la propiedad loading del hook esta en true. 鈴

export const Loading = () => {
  return (
    <p className='loading'>Loading...</p>
  )
}
Enter fullscreen mode Exit fullscreen mode

Mostrando el loading

馃煛 ErrorMessage.tsx

Este componente solo se mostrar谩 si la propiedad error del hook contiene un valor string. 馃毃

export const ErrorMessage = ({msg}:{msg:string}) => {
  return (
    <div className="error-msg">{msg.toUpperCase()}</div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Mostrando el error

馃煛 Card.tsx

Muestra los datos de la API, o sea la imagen y su texto. 馃柤锔

import { Result } from '../interface';

export const Card = ({ image, name }:Result) => {

    return (
        <div className='card'>
            <img src={image} alt={image} width={100} />
            <p>{name}</p>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

馃煛 LayoutCards.tsx

Este componente sirve como contenedor para hacer el recorrido de la propiedad data y mostrar las cartas con su informaci贸n. 馃敵

馃敶 NOTA: usamos memo, encerrando nuestro componente, con el prop贸sito de evitar re-renders, que probablemente no se noten en esta aplicaci贸n pero solo es un tip. Dicha funci贸n memo, solo se vuelve a renderizar si la propiedad 鈥渄ata鈥 cambia sus valores.

import { memo } from "react"
import { Result } from "../interface"
import { Card } from "./"

interface Props { data: Result[] }

export const LayoutCards = memo(({data}:Props) => {
    return (
        <div className="container-cards">
            {
                (data.length > 0) && data.map( character => (
                    <Card {...character} key={character.id}/>
                ))
            }
        </div>
    )
})
Enter fullscreen mode Exit fullscreen mode

As铆 quedar铆a nuestro componente App.tsx

Creamos una funci贸n showData, y evaluamos:

  • Si la propiedad loading es true, retornamos el componente <Loading/>
  • Si la propiedad error es true, retornamos el componente <ErrorMessage/> , mandando el error al componente.
  • Si ninguna de las condiciones se cumple, significa que los datos de la API ya est谩n listos y se retornamos el componente <LayoutCards/> y le mandamos la data para que la muestre.

Finalmente, debajo del componente , abrimos par茅ntesis y hacemos la llamada a la funci贸n showData.

import { ErrorMessage, Header, Loading, LayoutCards } from './components'
import { useFetch } from './hook';

const App = () => {

  const { data, loading, error } = useFetch();

  const showData =  () => {
    if (loading) return <Loading/>
    if (error) return <ErrorMessage msg={error}/>
    return <LayoutCards data={data} />
  }

  return (
    <>
      <Header/>
      { showData() }
    </>
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode

馃敶 NOTA: Tambi茅n puedes mover la funci贸n showData al hook, y cambiar la extension del archivo del hook a .tsx, esto es porque se esta utilizando JSX al retornar diversos componentes.


Gracias por llegar hasta aqu铆. 馃檶

Te dejo el repositorio para que le eches un vistazo si quieres. 猬囷笍

GitHub logo Franklin361 / fetching-data-custom-hook

Tutorial on how to fetching data and creating a custom hook

Fetching data and create custom Hook

Tutorial on how to fetching data and creating a custom hook

demo

Link to tutorial post 鉃★笍

Top comments (1)

Collapse
 
flash010603 profile image
Usuario163

Me gusto la parte del custom hook!:)