Search code examples
reactjsreact-hooksreact-query

react-query custom hook how to use useMutation and useQuery conditionaly


i create custom hook for use useQuery() :


export const useRequest = (
  {
    path = "",
    params = {},
    body = {},
    key = "",
    options= {},
    method = "get"
  }
) => {

  // async function for get API:
  const callApi = async () => {
    const { data: { response } } = await axios[method](baseUrl._serviceUrl + path,
      {
        params,
        ...body
      });

    return response;
  }

  const query = useQuery(key, callApi, {
    refetchOnWindowFocus: false,
    ...options
  });

  return { ...query, isLoading: query.isLoading && query.fetchStatus !== "idle" }
}

To use the useMutation hook without using the separate function, I changed my useRequest() as follows:


export const useRequest = (
  {
    path = "",
    params = {},
    body = {},
    key = "",
    options= {},
    method = "get",
    mutation = false
  }
) => {

  // async function for get API:
  const callApi = async () => {
    const { data: { response } } = await axios[method](baseUrl._serviceUrl + path,
      {
        params,
        ...body
      });

    return response;
  }
  
  if (mutation) {
    const callMutationApi = async (data) => {
      const {params, body} = data;
      const { data: { response } } = await axios.post(baseUrl._serviceUrl + path,
        {
          params,
          ...body
        });

      return response;
    }

    return useMutation(callMutationApi, options);
  }

  const query = useQuery(key, callApi, {
    refetchOnWindowFocus: false,
    ...options
  });

  return { ...query, isLoading: query.isLoading && query.fetchStatus !== "idle" }
}

But I get the following error: React Hook "useMutation" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

What changes should I make in the useRequest() function? Thank you for your guidance.

///// UPDATE /////

According to the answer of dear @kapobajza...

I changed the code as follows and it worked:

export const useRequest = (
  {
    path = "",
    params = {},
    body = {},
    key = "",
    options= {},
    method = "get",
    mutation = false
  }
) => {

  // async function for get API:
  const callApi = async () => {
    const { data: { response } } = await axios[method](baseUrl._serviceUrl + path,
      {
        params,
        ...body
      });

    return response;
  }


  const callMutationApi = async (data) => {
    const {params, body} = data;
    const { data: { response } } = await axios.post(baseUrl._serviceUrl + path,
      {
        params,
        ...body
      });

    return response;
  }

  // Instead of returning here, just save the result in a variable
  const useMutationResult = useMutation(callMutationApi, options);

  const query = useQuery(key, callApi, {
    refetchOnWindowFocus: false,
    enabled: (!mutation && options?.enabled),
    ...options
  });
  
  // If mutation is defined, return that result
  if (mutation) {
    return useMutationResult;
  }

  return { ...query, isLoading: query.isLoading && query.fetchStatus !== "idle" }
}

I added the option part of the following code to useQuery().. This will prevent useQuery() from running:

{
    refetchOnWindowFocus: false,
    enabled: (!mutation && options?.enabled),
    ...options
}

Please tell me if this solution is correct?


Solution

  • Unfortunately you cannot call React hooks conditionally.

    One thing you'll find out early adopting react is that you cannot have conditional hooks. This is because every hook is initially added into a list that is reviewed on every render cycle, so if the hooks don't add up, there is something amiss and any linter set up correctly will warn you.

    Taken from this article

    But the thing you could do is to return the result of the useMutation hook if mutation is defined:

    export const useRequest = (
      {
        path = "",
        params = {},
        body = {},
        key = "",
        options= {},
        method = "get",
        mutation = false
      }
    ) => {
    
      // async function for get API:
      const callApi = async () => {
        const { data: { response } } = await axios[method](baseUrl._serviceUrl + path,
          {
            params,
            ...body
          });
    
        return response;
      }
    
    
      const callMutationApi = async (data) => {
        const {params, body} = data;
        const { data: { response } } = await axios.post(baseUrl._serviceUrl + path,
          {
            params,
            ...body
          });
    
        return response;
      }
    
      // Instead of returning here, just save the result in a variable
      const useMutationResult = useMutation(callMutationApi, options);
    
      const query = useQuery(key, callApi, {
        refetchOnWindowFocus: false,
        ...options
      });
      
      // If mutation is defined, return that result
      if (mutation) {
        return useMutationResult;
      }
    
      return { ...query, isLoading: query.isLoading && query.fetchStatus !== "idle" }
    }