React-query useInfiniteQuery: getNextPageParam not working - reactjs

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,
};
};

Related

Is there a way for react-query to revalidate cached data in case server response is OK but does not contain actual data?

I'm exploring react-query but couldn't find an example for one particular use case.
A demo todo API server returns the following JSON for getTodos(ifModifiedSince=1234567) request:
In case todo list not modified since specified time:
{"status":"not_modified"}
Otherwise:
{"status":"ok","todos":[{"userId":1,"id":2,"title":"TodoItem1","completed":false}],"lastModified":"1234589"}
The closest I came up with:
const { isLoading, isError, data } = useQuery(
[todosQueryKey],
() => {
const lastModified = data?.lastModified || '';
const result = getTodos({
ifModifiedSince: lastModified,
});
return result;
},
{
select: (newData): GetTodosResponse | undefined => {
let resolvedData = newData;
if (newData.status === StatusNotModified) {
//! This actually does not work because getQueryData
//! already returns data from most recent 'not_modified' request.
resolvedData = queryClient.getQueryData([todosQueryKey]) || newData;
}
resolvedData.todos?.sort((a: TodoItem, b: TodoItem) => b.id - a.id);
return resolvedData;
},
refetchInterval: 5000,
staleTime: Infinity,
keepPreviousData: true,
}
);
What is the correct way to do the above mentioned task with react-query?
My current solution is to use useQuery() as usual but instead modify getTodos() function
so that it receives cached data as an optional second parameter and
returns it if status==='not_modified':
export const getTodos = async (
req: GetTodosRequest,
currentData?: GetTodosResponse | undefined
): Promise<GetTodosResponse> => {
return todosApi
.get(`/todos/`, {
params: req,
})
.then((resp) => {
const newData = validateResponse(resp);
if (newData.status === StatusNotModified) {
// If server responded with StatusNotModified,
// return cached data if any.
return currentData || newData;
}
return newData;
});
};
const { isLoading, isError, data } = useQuery(
[todosQueryKey],
() => {
const lastModified = data?.lastModified || '';
const result = getTodos(
{
ifModifiedSince: lastModified,
},
data
);
return result;
},
{
select: (newData): GetTodosResponse => {
newData.todos?.sort((a: TodoItem, b: TodoItem) => b.id - a.id);
return newData;
},
onSuccess: () => {
setWaitingForResponse(false);
},
refetchInterval: 5000,
staleTime: Infinity,
}
);

HTTP put and get(id) request ReactQuery

I change the redux in my project to ReactQuery,and i got some problem with put req in my code.
this is my code
const { dispatch } = store;
const editClientDataAsync = async ( id,data ) => {
await axiosObj().put(`clients/${id}`,data);
}
const { mutateAsync: editClientData, isLoading } = useMutation(['editClientData'], editClientDataAsync, {
onSuccess: () => dispatch({ type: SUCCESS_DATA, payload: { message: "Success" } }),
onError: () => dispatch({ type: ERROR_DATA, payload: { message: "Error" } })
});
return { editClientData, isLoading }
}
same problem with when i try to get some req with id
const id = useSelector((state) => state?.clientData?.clientInfo?.data.id)
const getClientDetails = async ({ queryKey }) => {
const [_, { id }] = queryKey;
console.log(queryKey)
if (!id)
return;
const { data } = await axiosObj().get(`clients/${id}`)
console.log(data)
return data;
}
const { data: clientDetails, isLoading } = useQuery(['ClientId', { id }], getClientDetails)
return { clientDetails, isLoading }
Mutation functions only take 1 argument
Check where you use the editClientData mutation and pass the arguments in one object.
const editClientDataAsync = async ({ id, data }) => {
await axiosObj().put(`clients/${id}`,data);
}
return useMutation(['editClientData'], editClientDataAsync, ...);
Are you sure you get an id passed to the function?
You can disable the query until you get that id with the enabled option, so you don't make an unnecessary http call.
const id = useSelector((state) => state?.clientData?.clientInfo?.data.id)
const getClientDetails = async (id) => {
const { data } = await axiosObj().get(`clients/${id}`)
return data;
}
return useQuery(['client', id], () => getClientDetails(id), { enabled: !!id })
Disable/pausing queries

Re-Usable fetch function with query string

I have a fetch function inside of my react component, which I wish to "outsourse" in a separate component.
export const fetchBooksBySubject = (selectedValue) => {
const options = {
method: `GET`,
};
fetch(`${server}/books?subjects_like=${selectedValue}`, options)
.then((response) => {
if(response.ok){
return response.json()
}
throw new Error('Api is not available')
})
.catch(error => {
console.error('Error fetching data: ', error)
})
}
Basically selectedValue is a prop coming from a child of App.jsx. As soon as the value is selected in a component, fetch should fire with this value in a query string. I tried to export the function above as a component and use it in App.jsx
useEffect(() => {
fetchBooksBySubject(selectedValue).then(data => setBookList(data));
}, [selectedValue])
const handleChange = e => {
setSelectedValue(e);
fetchBooksBySubject(selectedValue);
};
But this throws Property 'then' does not exist on type 'void'.
Here's a custom hook you can use with fast and reusable data fetching, a built-in cache, and other features like polling intervals and revalidation.
Hook:
const useBooks = (selectedValue) =>
{
const fetcher = (...args) => fetch(...args).then(res => res.json())
const { data, error } = useSWR(`/api/books?subjects_like=${selectedValue}`, fetcher)
return {
books: data,
isLoading: !error && !data,
isError: error
}
}
Usage:
const { books, isLoading, isError } = useBooks(selectedValue)
if (isLoading) return <div>Loading...</div>
else return <div>Your content here</div>
swr docs
Without swr:
useEffect(() =>
{
const fetchData = async (selectedValue) =>
{
const books = await fetchBookBySubject(selectedValue)
setBookList(books)
}
fetchData(selectedValue)
}, [selectedValue, bookList])
So the problem was, that I wasn't returning my fetch. I am a beginner, so my understanding is, that my App.js just couldn't access the data from fetchBooksBySubject withot this return
const dev = process.env.NODE_ENV !== 'production';
const server = dev ? 'http://localhost:3001' : 'https://your_deployment.server.com';
// later definable for developement, test, production
export const FetchBooksBySubject = (selectedValue) => {
const options = {
method: `GET`,
};
return fetch(`${server}/books?subjects_like=${selectedValue}`, options)
.then((response) => {
if(response.ok){
return response.json()
}
throw new Error('Api is not available')
})
.catch(error => {
console.error('Error fetching data: ', error)
})
}
Same as here:
let sum = (a,b) => {a+b}
sum(1,2) //undefined
let sum1 = (a,b) => {return a+b}
sum1(1,2) //3

Next.js using SWR with axios

I'm trying to use SWR to prefetch data in my project.
Here is my code:
export const getStaticProps = async (res) => {
const result = await axios.get(
`/orders/detail/${res.params.cid}/${res.params.oid}`
);
const orderDetailById = await result.data;
return {
props: { orderDetailById },
};
};
export const getStaticPaths = async () => {
const result = await fetch(`${server}/api/orders`);
const orders = await result.json();
const ids = orders.map((order_detail) => ({
oid: order_detail.oid,
cid: order_detail.cid,
}));
const paths = ids.map((id) => ({
params: { oid: id.oid.toString(), cid: id.cid.toString() },
}));
return {
paths,
fallback: false,
};
};
const fetcher = (url, params) => {
return fetch(url + params.cid + '/' + params.oid).then((r) => r.json());
};
const OrderDetailByOId = ({ orderDetailById }) => {
const cid = orderDetailById.customer[0].cid;
const oid = orderDetailById.detail[0].oid;
const params = useMemo(() => ({ cid, oid }), [cid, oid]);
const { data, error } = useSWR(['/orders/detail/', params], fetcher, {
initialData: orderDetailById,
});
if (error) {
console.log('errorHere', error);
return <div>failed to load</div>;
}
if (!data) return <div>Loading...</div>;
return <OrderDetailForm orderDetailById={orderDetailById} />;
};
export default OrderDetailByOId;
It works well in the first render.
At the same time, I didn't change any data in my database,
so when it renders the second time by refreshInterval:1000 it wouldn't change anything, but it popped up with some errors!
errorHere SyntaxError: Unexpected token < in JSON at position 0
When I first saw the error I guessed it was just some JSON problems, so I changed the fetcher's return like (r)=>r.data
After I changed this, it caused the web to return loading...
It means it didn't fetch anything in the second render or even each after the first render.
Did anyone can help me find out what problems caused the errors.
Thanks~
I forgot I have set Axios basic URl like Axios.defaults.baseURL = server + '/api';
so I changed the fetcher return like return axios.get(url + params.cid + '/' + params.oid).then((r) => r.data);
It works for me now~ Thanks for the #juliomalves pointing me out where could be a problem ~ Thanks!

How to set loading true in graphql query.?

I am using graphQl in this i want to set loading = true for 1 second to show loader after that it will reset by response how will i do that
i am using below code right now,
const loadData = graphql(initialData, {
options: ({ params: { Id }, authData: { userPermissions } }) => ({
variables: {
Id,
Settings: hasPermissions(userPermissions, [USER_PERMISSIONS.du]),
},
fetchPolicy: APOLLO_FETCH_POLICIES.NETWORK_ONLY,
errorPolicy: APOLLO_ERROR_POLICIES.ALL,
notifyOnNetworkStatusChange: true,
}),
// skip: props => props.loading = true,
props: ({ data }) => {
const { error, loading, refetch, networkStatus, buy, timeZones, manager } = data;
return {
error:error,
loading: networkStatus === 1 && !loading ? true : loading,
networkStatus,
onReload: refetch,
timeZones,
manager: get(manager, 'itUsers', []),
};
},
});
Any help is appreciated.
Well, you can use custom fetch. Something like this might work:
const customFetch = (url, {delay, ...opts}) => {
return Promise.all([
fetch(url, opts),
new Promise(resolve => setTimeout(resolve, delay || 0)),
]).then(([res, _]) => res)
}
const uploadLink = createUploadLink({
uri,
fetch: customFetch,
})
const client = new ApolloClient({
cache,
link: uploadLink,
})
//////////////////////////////////////////////
// Then you can add delay option via context
//////////////////////////////////////////////
const loadData = graphql(initialData, {
options: ({ params: { Id }, authData: { userPermissions } }) => ({
variables: {
Id,
Settings: hasPermissions(userPermissions, [USER_PERMISSIONS.du]),
},
fetchPolicy: APOLLO_FETCH_POLICIES.NETWORK_ONLY,
errorPolicy: APOLLO_ERROR_POLICIES.ALL,
notifyOnNetworkStatusChange: true,
///////////////////////////////////////////
// add context with delay
context: {
fetchOptions: {delay: 1000},
///////////////////////////////////////////
},
}),
// skip: props => props.loading = true,
props: ({ data }) => {
const { error, loading, refetch, networkStatus, buy, timeZones, manager } = data;
return {
error:error,
loading: networkStatus === 1 && !loading ? true : loading,
networkStatus,
onReload: refetch,
timeZones,
manager: get(manager, 'itUsers', []),
};
},
});
I have not tested it.

Resources