简体   繁体   中英

Custom hook to execute an axios request [React, Typescript]

I am trying to create a reusable custom hook (useRequest) where I can fetch data with axios, display it and have a loading state. In case of an error I want it to be caught by useRequest. I'm having trouble catching eventual errors and passing the axios request to useRequest. Currently I'm only getting null for the error message.

EDIT: I use generated api which uses axios. So to make my fetch request it would look something like this:

import {GeneratedApi} from '/generatedApi'

const generatedApi = new GeneratedApi(configuration) //configuration is for editing the headers etc.
const response = await generatedApi.getData();
setData(response.data);

My code:

import axios, { AxiosResponse } from "axios";
import { useEffect, useState } from "react";

const useRequest = (promise: Promise<AxiosResponse<any>>) => {
  const [loading, setLoading] = useState<boolean>(true);

  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setError(null);
        await promise;
        setLoading(false);
        setError(null);
      } catch (error) {
        setLoading(false);
        setError("Error: " + JSON.stringify(error));
      }
    };
    fetchData();
  }, [promise]);

  return [loading, error];
};

export default function App() {
  const [data, setData] = useState<any | null>(null);

  const [loading, error] = useRequest(async () => {

    const response = await axios.get("https://jsonplaceholder.typicode.com/todos");
    setData(response.data);
    return response;
  });

  if (loading) {
    return <p>Loading ...</p>;
  } else if (data) {
    return <p>{data}</p>;
  } else {
    return <p>Error: {error}</p>;
  }
}

You can pass a function, wrapped in useCallback hook, which would invoke your api call:

import axios, { AxiosResponse } from "axios";
import { useCallback, useEffect, useState } from "react";

const url = "https://jsonplaceholder.typicode.com/todos"

const useRequest = (apiCall: () => Promise<AxiosResponse<any>>, setData: (data: any) => void) => {
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setError(null);
        const response = await apiCall()
        setData(response.data)
        setLoading(false);
        setError(null);
      } catch (error) {
        setLoading(false);
        setData(null)
        setError("Error: " + JSON.stringify(error));
      }
    };
    fetchData();
  }, [apiCall, setData]);

  return [loading, error];
};

export default function App() {
  const [data, setData] = useState<any | null>(null);
  const fun = useCallback(() => axios.get(url), [])

  const [loading, error] = useRequest(fun, setData);

  if (loading) {
    return <p>Loading ...</p>;
  } else if (data) {
    return <p>{'data'}</p>;
  } else {
    return <p>Error: {error}</p>;
  }
}

Konstantin Samarin's answer helped to point me in the right direction.

My current solution has a missing dependency(callback) and might not be ideal. Adding the dependency causes infinite rerenders.

import axios, { AxiosResponse } from "axios";
import { useCallback, useEffect, useState } from "react";

interface RequestReponse {
    loading: boolean,
    error: string | null
}

function useRequest(callback: any, dependencies: any[]): [boolean, (string | null)] {
    const [loading, setLoading] = useState<boolean>(true);

    const [error, setError] = useState<string | null>(null);

    const navigate = useNavigate();

    useEffect(() => {
        async function doRequest() {
            try {
                setError(null);
                await callback();
                setLoading(false);
            } catch (error) {
                setLoading(false);
                setError(error)
            }
        }

        doRequest();
    }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps

    return [loading, error];
}

export default function App() {

  const generatedApi = new GeneratedApi();

  const [data, setData] = useState<any | null>(null);

  const {loading, error} = useRequest(async () => {
    const response = await generatedApi.getData();
    setData(() => (response.data);
}, [])

  if (loading) {
    return <p>Loading ...</p>;
  } else if (data) {
    return <p>{'data'}</p>;
  } else {
    return <p>Error: {error}</p>;
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM