Home > Back-end >  Custom useAxios hook in react
Custom useAxios hook in react

Time:01-11

I am using axios with react, so I thought to write a custom hook for this which I did and it is working fine like below

  const useAxios = () => {
  const [response, setResponse] = useState([]);
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(true); //different!
  const [controller, setController] = useState();

  const axiosFetch = async (configObj) => {
    const { axiosInstance, method, url, requestConfig = {} } = configObj;

    try {
      const ctrl = new AbortController();
      setController(ctrl);
      const res = await axiosInstance[method.toLowerCase()](url, {
        ...requestConfig,
      });

      setResponse(res.data);
    } catch (err) {
      console.log(err.message);
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    console.log(controller);

    // useEffect cleanup function
    return () => controller && controller.abort();
  }, [controller]);

  return [response, error, loading, axiosFetch];
};

I have also created one axiosInstance to pass BASE_URL and headers. Now calling useAxios to fetch data from api like below

  const [data, error, loading, axiosFetch] = useAxios();
  const getData = () => {
    axiosFetch({
      axiosInstance: axios,
      method: "GET",
      url: "/url",
    });
  };
  useEffect(() => {
    getData();
  }, []);

My Question is

  • When I need to call one api I am doing above.
  • But what if I have to call three or four APIs in a single page.
  • Shall I replicate the code like this const [data1, error1, loading1, axiosFetch]=useAxios();
  • Or is there any other way to minimize the code.

Edit / Update

I ran above code to get data from /url, what if I want to hit different route to get one more data from server for other work, the base url remains the same

So if the second route is /users

  const [data, error, loading, axiosFetch] = useAxios();
  const getUsers = () => {
    axiosFetch({
      axiosInstance: axios,
      method: "GET",
      url: "/users",
    });
  };
  useEffect(() => {
    getUsers();
  }, [on_BTN_Click]);

THe above codeI want to run in same file, one to get data and one to get users, how should I write my axios, as I think this const [data, error, loading, axiosFetch] = useAxios(); should gets called only once, Don't know how to do this or what is the correct way, shall I need to change my useAxios hook?

CodePudding user response:

What you could do is pass the endpoint to the hook or properly call the axiosFetch callback with the different endpoints. But I have another opinion about what you are trying to do and here are my 5 cents on why this "axios hook" might not be a good idea.

A good rule of thumb on React Hooks is to use a custom hook if you need to encapsulate component logic that uses React Hooks.

Another important thing that is described in the React Hooks docs is:

Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.

So, eventually, if 2 different components call the fetch for the same endpoint, they both are going to execute the call to the Backend. How to prevent that? Well, you could use a lib such as React Query, that creates some kind of "cache" for you (and a bunch of other nice features!)

And last but not least: API calls are much more related to a Service/Module than a React Hook (isolate component logic), as a concept. I hardly advise you to create a service for making API calls and using that service inside your hook instead of coupling that logic to your hook and having to handle all kinds of issues such as Caching and multiple instances of the same hook or even multiple instances of this hook calling multiple different endpoints that eventually could or could not be dependant of themselves.

CodePudding user response:

How about a generic useAsync hook that accepts any asynchronous call? This decouples axios specifics from the the hook.

function useAsync(func, deps = []) {
  const [state, setState] = useState({ loading: true, error: null, data: null })
  useEffect(
    () => {
      func()
        .then(data => setState({ loading: false, error: null, data }))
        .catch(error => setState({ loading: false, error, data: null }))
    },
    deps,
  )
  return state
}

Here's a basic example of its usage -

function UserProfile({ userId }) {
  const user = useAsync(
    () => axios.get(`/users/${userId}`),  // async call
    [userId],                             // dependencies
  })
  if (user.loading)
    return <Loading />
  if (user.error)
    return <Error message={user.error.message} />
  return <User user={user.data} />
}

The idea is any asynchronous operation can be performed. A more sophisticated example might look like this -

function UserProfile({ userId }) {
  const profile = useAsync(
    async () => {
      const user = await axios.get(`/users/${userId}`)
      const friends = await axios.get(`/users/${userId}/friends`)
      const notifications = await axios.get(`/users/${userId}/notifications`)
      return {user, friends, notifications}
    },
    [userId],
  )
  if (profile.loading) return <Loading />
  if (profile.error) return <Error message={profile.error.message} />
  return <>
    <User user={profile.data.user} />
    <Friends friends={profile.data.friends} />
    <Notifications notifications={profile.data.notifications} />
  </>
}

In the last example, all fetches need to complete before the data can begin rendering. You could use the useAsync hook multiple times to get parallel processing. Don't forget you have to check for loading and error before you can safely access data -

function UserProfile({ userId }) {
  const user = useAsync(() => axios.get(`/users/${userId}`), [userId])
  const friends = useAsync(() => axios.get(`/users/${userId}/friends`), [userId])
  const notifications = useAsync(() => axios.get(`/users/${userId}/notifications`), [userId])
  return <>
    { user.loading
    ? <Loading />
    : user.error
    ? <Error message={user.error.message }
    : <User user={user.data} />
    }
    { friends.loading
    ? <Loading />
    : friends.error
    ? <Error message={friends.error.message} />
    : <Friends friends={friends.data} />
    }
    { notifications.loading
    ? <Loading />
    : notifications.error
    ? <Error message={notifications.error.message} />
    : <Notifications notifications={notifications.data} />
    }
  </>
}

I would recommend you decouple axios from your components as well. You can do this by writing your own API module and even providing a useAPI hook. See this Q&A if that sounds interesting to you.

  • Related