Haciendo un fetching de datos y creando un custom hook. 🪝
Tags: react, typescript, webdev, spanish
Published: 2022-06-18 00:35:30

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.🤗

&nbsp;

## Tabla de contenido
> 📌 [Tecnologías a utilizar](#2)
>   
> 📌 [Creando el proyecto](#3)
> 
> 📌 [Primeros pasos](#4)
> 
> 📌 [Haciendo nuestro primer fetch](#5)
> 
> 📌 [Mostrando los datos de la API en pantalla](#6)
> 
> 📌 [Creando un custom hook](#7)
> 
> 📌 [Mejorando el hook `useFetch`](#8)
> 
> 📌 [Agregando más componentes y refactorizando](#9)
>> 📍 [Header.tsx](#10)
>>
>> 📍 [Loading.tsx](#11)
>>
>> 📍 [ErrorMessage.tsx](#12)
>>
>> 📍 [Card.tsx](#13)
>>
>> 📍 [LayoutCards.tsx](#14)

---

## 🚨 Tecnologías a utilizar.<a id="2"></a>

▶️ React JS (version 18)

▶️ Vite JS

▶️ TypeScript 

▶️ [Rick and Morty API](https://rickandmortyapi.com)

▶️ CSS vanilla (**Los estilos los encuentras en el repositorio al final de este post**)
&nbsp;
## 〽️ Creando el proyecto.<a id="3"></a>

```bash
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.

```bash
cd fetching-data-custom-hook
```

Luego instalamos las dependencias:

```bash
npm install
```

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

```bash
code .
```
&nbsp;
## 〽️ Primeros pasos.<a id="4"></a>

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.

```jsx
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;
```

![Mostrando titulo y subtitulo](https://res.cloudinary.com/dnxchppfm/image/upload/v1655258479/01_vfqfvg.png)

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](https://rickandmortyapi.com/documentation/#character-schema)), seleccione un ID, el nombre y la imagen del personaje.

```jsx
interface Response {
  results: Result[]
}

interface Result {
  id: number;
  name: string;
  image: string;
}
```
&nbsp;
## 〽️ Haciendo nuestro primer Fetch.<a id="5"></a>

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.

```jsx
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).

```jsx
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. 

```jsx
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).

```jsx
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)   
},[])
```
&nbsp;
## 〽️ Mostrando los datos de la API en pantalla.<a id="6"></a>

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.

```jsx
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.

```jsx
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. 😎

![Mostrando cards](https://res.cloudinary.com/dnxchppfm/image/upload/v1655258695/02_ggf975.png)
&nbsp;
## 〽️ Creando un custom hook.<a id="7"></a>

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

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

```jsx
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`.”

```jsx
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. 🤓

```jsx
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. 🤯
&nbsp;
## 〽️ Mejorando el hook `useFetch`.<a id="8"></a>

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`

```jsx
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**

```jsx
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. 

```jsx
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).

```jsx
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.

```jsx
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.

```jsx
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.

```jsx
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**. 

```jsx
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. 😳
&nbsp;
## 〽️ Agregando más componentes y refactorizando.<a id="9"></a>

Lo primero es crear una carpeta **components** dentro de **src.**

Dentro de la carpeta componentes creamos los siguientes archivos.
&nbsp;
### 🟡 Header.tsx<a id="10"></a>

Dentro de este componente solo estarán el titulo y el subtitulo anteriormente creados. 😉

```jsx
export const Header = () => {
    return (
        <>
            <h1 className="title">Fetching data and create custom Hook</h1>
            <span className="subtitle">using Rick and Morty API</span>
        </>
    )
}
```
&nbsp;
### 🟡 Loading.tsx<a id="11"></a>

Este componente solo se mostrará si la propiedad loading del hook esta en true. ⏳

```jsx
export const Loading = () => {
  return (
    <p className='loading'>Loading...</p>
  )
}
```

![Mostrando el loading](https://res.cloudinary.com/dnxchppfm/image/upload/v1655258693/03_dkowhz.png)
&nbsp;
### 🟡 ErrorMessage.tsx<a id="12"></a>

Este componente solo se mostrará si la propiedad error del hook contiene un valor string. 🚨 

```jsx
export const ErrorMessage = ({msg}:{msg:string}) => {
  return (
    <div className="error-msg">{msg.toUpperCase()}</div>
  )
}
```

![Mostrando el error](https://res.cloudinary.com/dnxchppfm/image/upload/v1655258693/04_oqlca7.png)
&nbsp;
### 🟡 Card.tsx<a id="13"></a>

Muestra los datos de la API, o sea la imagen y su texto. 🖼️

```jsx
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>
    )
}
```
&nbsp;
### 🟡 LayoutCards.tsx<a id="14"></a>

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.

```jsx
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 <Header/>, abrimos paréntesis y hacemos la llamada a la función **showData**.

```jsx
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. ⬇️

{% embed https://github.com/Franklin361/fetching-data-custom-hook %}