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

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?

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" }
}

Related

Call useSWR by previous useSWR data.map then got Error(useSWR to get google calendar event)

I write a hook named useGoogleCalendarEvents, Try to get users google calendar event,below is my code.
import { Schedule, User } from '../../types/commons.type';
import { useLocalStorage } from 'usehooks-ts';
import useSWR from 'swr';
import { FetchCalendarList } from '../../types/googleCalendarAPIResponse.type';
import { currentUTCMonthString } from '../../utils/DateUtils';
const baseURL = 'https://www.googleapis.com/calendar/v3';
const fetcher = ([path, method, body = {}, accessToken = '']: [string, string, object, string]) => {
const headers = {
'Content-Type': ['POST', 'PUT', 'PATCH'].includes(method) ? 'application/json' : '',
Authorization: `Bearer ${accessToken}`,
};
const fetchOptions = {
method,
headers,
body: method === 'GET' ? undefined : JSON.stringify(body),
};
const apiURL = `${baseURL}${path}`;
return fetch(apiURL, fetchOptions).then((r) => r.json());
};
export const useGoogleCalendarEvents = () => {
const [user, setUser] = useLocalStorage<User | undefined>('user', undefined);
const events: Array<string> = [];
if (user?.googleAccessToken) {
// get user calendar list
const { data: GoogleCalendarListResult } = useSWR<FetchCalendarList>(
['/users/me/calendarList', 'GET', {}, user?.googleAccessToken],
fetcher,
);
const { startString, endString } = currentUTCMonthString();
const parameters = `timeMin=${startString}&timeMax=${endString}`;
// map loop to get event by calendar id
GoogleCalendarListResult?.items?.map((calendar) => {
const { data: GoogleCalendarEventResult } = useSWR(
[`/calendars/${calendar.id}/events?${parameters}`],
fetcher,
);
});
}
return { events };
};
the code not yet finish but i got the error
"React has detected a change in the order of Hooks called by MySettingPage2."
I modify the code
GoogleCalendarListResult?.items?.map((calendar) => {
const { data: GoogleCalendarEventResult } = useSWR(
[`/calendars/${calendar.id}/events?${parameters}`],
fetcher,
); });
to
['calendarid1', 'calendarid2'].map((calendar) => {
const { data: GoogleCalendarEventResult } = useSWR(
[`/calendars/${calendar.id}/events?${parameters}`],
fetcher,
);
});
It is work, but not flexble. I want to get calendar id list first, then get envets by calendar id
Is anybody known why cause this error? Thank for any reply

React-query useInfiniteQuery: getNextPageParam not working

I'm stuck using useInfiniteQuery.
The first call works fine, but the next page is not called with getNextPageParam
const getProductItems = async (par) => {
console.log("axios :", par);
const res = await axios.get(`/api/v1/products`, {
params: par,
});
return {
result: res.data,
};
};
export default function useGetProductItems(params) {
const { data, isLoading, fetchNextPage, hasNextPage, isFetching } =
useInfiniteQuery(
["getItems"],
({ pars = params }) => getProductItems(pars),
{
getNextPageParam: (res) => {
console.log(res);
const nextParams = {
...res.result.pageInfo,
page: res.result.pageInfo.page + 1,
};
console.log("next :", nextParams);
return nextParams;
},
select: (data) => {
return data.pages[0].result.data;
},
}
);
return {
data,
isLoading,
fetchNextPage,
hasNextPage,
isFetching,
};
}
And the Query Client setting is like this
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
queryCache: new QueryCache({
onError: errorHandler,
}),
mutationCache: new MutationCache({
onError: errorHandler,
}),
});
As I am new to react-query, I am also wondering if there is any data that must be received from the API.
plz answer for me
You can access pageParam and send it as argument to your fetching function. Also it'd be a good idea to check if there really is a next page before incrementing the actual page number in getNextPageParam. Something like this:
const { data, isLoading, fetchNextPage, hasNextPage, isFetching } =
useInfiniteQuery(
['getItems'],
({ pageParam = 1 }) => getProductItems(pageParam), // pageParam defaults to the first page
{
getNextPageParam: lastPage => {
return lastPage.result.pageInfo.page < lastPage.result.pageInfo.totalPages // Here I'm assuming you have access to the total number of pages
? lastPage.result.pageInfo.page + 1
: undefined // If there is not a next page, getNextPageParam will return undefined and the hasNextPage boolean will be set to 'false'
},
select: data => {
return data.pages[0].result.data
},
}
)
I don't have information about how is your API endpoint built, but typically the request should look, for example, like this:
const getProductItems = async (page) => {
const res = await axios.get(`/api/v1/products?page=${page}`);
return {
result: res.data,
};
};

How can I use SWR hook for all API methods? (CRUD)

I am switching certain CRUD functionality that I used to provide with a token, but now I am using SWR and I don't know how to convert it.
I used this hook for GET methods but for others, I don't know what to do!
export default function useGetData(apiKey) {
const fetcher = async (...args) => await fetch(...args).then(res => res.json());
const { data, mutate, error } = useSWR(apiKey, fetcher);
const loading = !data && !error;
return {
loading,
user: data,
mutate
}
}
OK, I found the answer :
import useSWR from 'swr';
import { getTokenFromLocalStorage } from '../../services/storage';
export default function useGetData({ url, payload, options = {}}) {
const mainUrl = process.env.NEXT_PUBLIC_BASE_URL + URL;
const method = payload ? 'POST' : 'GET';
const fetcher = async () => {
const headers = {
Authorization: `Bearer ${getTokenFromLocalStorage()}`
};
const options = {
method,
headers,
...(payload && { body: payload }),
};
return await fetch(mainUrl, options).then((res) => res.json());
};
const defaultOptions = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
};
const { data, mutate, error, isValidating } = useSWR(url + method, fetcher, {
...defaultOptions,
...options,
});
const loading = !data && !error;
return { data, loading, error, mutate, isValidating };
}

Why i am getting too many response from the API call

I am not able to stop the api call. what should be the right approach to handle this situation.
const successCallback = (position) => {
console.log("in success");
setCoord({ ...coord, latitude: position.coords.latitude.toFixed(4), longitude: position.coords.longitude.toFixed(4) })
}
const failCallback = () => {
alert("Give Location Permission for currect Location.")
}
if (window.navigator.geolocation) {
window.navigator.geolocation.getCurrentPosition(successCallback, failCallback)
} else {
alert("Geolocation is not supported by this browser.")
}
const url3 = `http://api.openweathermap.org/data/2.5/weather?lat=${coord.latitude}&lon=${coord.longitude}&appid=MYKEY`
const fetchWeather = async () => {
const responce = await Axios.get(url3)
console.log(responce);
}
useEffect(() => {
fetchWeather()
}, [coord])
Add options to throttle how often successCallback is callded
const options = {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 20000
};
window.navigator.geolocation.getCurrentPosition(successCallback, failCallback, options)
or if you want it to be called only once, use
const options = {
timeout: 0
};

useEffect hook infinite loop

I am new to react. here is my problem
import React, { useEffect, useState } from "react";
import { useJwt } from "react-jwt";
import { get_single_user_url } from "../../store/api";
import UrlCard from "../UrlCard/UrlCard";
import "./Urls.css";
function Urls() {
const [urls, setUrls] = useState([]);
const { decodedToken } = useJwt(localStorage.token);
const userId = decodedToken !== null ? decodedToken.userId : "";
useEffect(() => {
const hit = async (userId) => {
get_single_user_url(userId).then((data) => setUrls(data));
const data = await get_single_user_url(userId);
setUrls(data);
console.log(data);
};
hit(userId);
}, []);
return <div className="urls"></div>;
}
export default Urls;
so this useeffect will call a function
get_single_user_data(userId)
and it should return an array of urls from the database. But it returned this
{kind: "ObjectId",
path: "user",
reason: {},
stringValue: """",
value: "",
proto: Object}
This is the function
export const get_single_user_url = async (userId) => {
try {
const response = await axios({
method: "post",
url: "http://192.168.43.62:5000/getUrls",
data: { user: userId },
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
});
console.log(response.data);
return response.data;
} catch (error) {
console.log(error.message);
}
};
here userId is passed through the body. Now in the backend when I print the value of req.body, it gives user property with an empty string.
{ user: "" }
I have tried it without using useEffect but then it goes into an infinite loop.
Since you have an empty dependency array on your useEffect, it will only fire once. It looks like the userId is an empty string when running.
You'll want to add some logic in your hit function to only make the request if userId is not empty. Additionally, to get your effect to run when needed, you should add userId to the dependency array ([userId]).
If userId isn't needed anywhere other than this function, you might use the token instead, and parse the userId in your hit function.
const [urls, setUrls] = useState([]);
const { decodedToken } = useJwt(localStorage.token);
useEffect(() => {
const hit = async (decodedToken) => {
const userId = decodedToken !== null ? decodedToken.userId : "";
if (!userId) {
return;
}
get_single_user_url(userId).then((data) => setUrls(data));
const data = await get_single_user_url(userId);
setUrls(data);
console.log(data);
};
hit(decodedToken);
}, [decodedToken]);

Resources