lista de etiquetas

React - Custom Hook para carga de datos separada de la vista

Aquí el repositorio con el código completo.

El problema a resolver es el siguiente. Supongamos que creamos un componente que necesita cargar datos de una API y mostrarlos en pantalla. Queremos separar la lógica de carga de datos de la vista del componente para mantenerlo más limpio y reutilizable. Para ello, podemos crear un custom hook que se encargue de la carga de datos y devuelva los resultados al componente.

Si hiciera falta, el código que está en el componente App podría pasarse a un componente contenedor (Ej. CountriesContainer), e importarlo donde queramos usarlo.

import { useState, useEffect } from "react";

import "./App.css";

interface Country {
  name: {
    common: string;
    official: string;
    nativeName: {
      [key: string]: {
        official: string;
        common: string;
      };
    };
  };
}
export default function App() {
  const { data, isLoading, error } = useFetchCountries();
  return (
    <>
      <div>Countries</div>
      {isLoading && <div>Loading...</div>}
      {error && <div>Error: {error}</div>}
      {data && <CountriesList countries={data} />}
    </>
  );
}

function CountriesList({ countries }: { countries: Country[] }) {
  return (
    <ul>
      {countries &&
        countries.map((country) => (
          <li key={country.name.common}>{country.name.common}</li>
        ))}
    </ul>
  );
}

function useFetchCountries(): {
  data: Country[];
  isLoading: boolean;
  error: string | null;
} {
  const [data, setData] = useState<Country[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    async function fetchData() {
      try {
        setIsLoading(true);
        const response = await fetch(
          "https://restcountries.com/v3.1/all?fields=name"
        );
        if (!response.ok) {
          setError(`HTTP error! status: ${response.status}`);
          return;
        }
        const data = await response.json();
        if (!data) {
          setError("No data received");
          return;
        }
        setData(data);
      } catch (error) {
        if (error instanceof Error) {
          // Log the error to an external service
          console.error("Error caught:", error);
          // Set a user-friendly error message
          setError(`An unexpected error occurred: ${error.message}`);
        } else {
          // Handle non-Error objects
          setError("An unexpected error occurred.");
        }
      } finally {
        setIsLoading(false);
      }
    }
    fetchData();
  }, []);

  return { data, isLoading, error };
}