Haciendo un fetching de datos y creando un custom hook. 🪝

HTTP 팁 GET 중간에 사용자 정의 훅을 사용할 수 있는 방법이 있습니다.

🚨 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 바닐라(Los estilos los encuentras en el repositorio al final de este post)

〽️ Creando el proyecto.

npm init vite@latest

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

Luego instalamos las dependencias:

npm install

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

code .

 

〽️ 프리메로스 파소.

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;


Antes que nada, crearemos un par de interface que nos ayudaran al auto completado de las propiedades que viene en la respuesta JSON que nos proporciona la API.
  • 라 프리메라 인터파즈 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 ), selectione un ID, el nombre y la imagen del personaje.

  • interface Response {
      results: Result[]
    }
    
    interface Result {
      id: number;
      name: string;
      image: string;
    }
    


    〽️ 하시엔도 누에스트로 프라이머 페치.

    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;
    
    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;
    
    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)   
    },[])
    
    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)   
    },[])
    

     

    〽️ 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;
    

    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;
    

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



    〽️ 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;
    

    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
      }
    }
    

    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;
    

    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;
    }
    

    🔵 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
      }
    }
    

    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(
        () => {},
        [],
    )
    

    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) {}
            },
            [],
        )
    
    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 (…prev), 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
                  }));
              }
            },
            [],
        )
    
    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 (…prev), 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
                  }));
              }
            },
            [],
        )
    

    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();
    }, []);
    

    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
        }
    }
    

    Antes de usar las nuevas propiedades de este hook, haremos una refactorización y crearemos más componentes. 😳
     

    〽️ 구성 요소와 리팩터링이 모두 모여 있습니다.

    Lo primero es crear una carpeta components dentro de src.

    Dentro de la carpeta componentes creamos los siguientes archivos.
     

    🟡 헤더.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>
            </>
        )
    }
    

     

    🟡 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>
      )
    }
    


    🟡 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>
      )
    }
    


    🟡 카드.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>
        )
    }
    

     

    🟡 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 “data” 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>
        )
    })
    

    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;
    

    🔴 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. ⬇️

    프랭클린361 / 가져오기-데이터-사용자 지정-후크


    데이터를 가져오고 사용자 지정 후크를 만드는 방법에 대한 자습서





    데이터 가져오기 및 사용자 지정 후크 만들기


    데이터를 가져오고 사용자 지정 후크를 만드는 방법에 대한 자습서
    demo
    튜토리얼 게시물 링크 ➡️


    View on GitHub

    좋은 웹페이지 즐겨찾기