Query never stops fetching after refetching or invalidating - reactjs

Fetching for the first time works, same goes for resetting the query. The data is fresh right after, then becomes stale after 5 seconds.
However, I want to refetch or invalidate the queries after applying mutations, but whenever I do so, the data just keeps refetching and never returning:
DevTools scr showing data just refetching
Same when i use the DevTools directly.
My useQuery hook:
export const useFetchCandidates = (id: string) => {
return useQuery<Entry<IKandidatFields>[], Error>(
'candidates',
async () => {
const res = await getCandidates(id)
return res
},
{
staleTime: 5000,
cacheTime: 10,
}
)
}
Using the hook to access data:
const {
data: candidates,
}: UseQueryResult<Entry<IKandidatFields>[], Error> = useFetchCandidates(id!)
The mutation:
const mutation = useMutation(
() =>
sendCandidateToNextStage(candidateID, utlysning).then(() =>
getCandidates(id!)
),
{
onMutate: () => {
queryClient.cancelQueries(['candidates', id])
},
onSettled: () => {
queryClient.resetQueries(['candidates', id])
},
}
)

This was solved by using a get axios request instead of utilizing the cms client directly... still don't know why the one works instead of the other when both of them return the same object.
When using the contentful API client:
export async function getCandidates(
id: string
): Promise<Entry<IKandidatFields>[]> {
const res = await client
.getEntries<IKandidatFields>({
content_type: 'kandidat',
})
return res
}
it was constantly fetching and never worked.
However, when using an axios request instead
export const getCandidatesFromAPI = async (id: string) => {
const { data } = await axios.get(
`https://cdn.contentful.com/spaces/${spaceID}/environments/${environmentId}/entries?access_token=${accessToken}&content_type=${contentTypeKandidat}`
)
return data
}
as the mutation function, everything worked perfectly.

Related

All my TRPC queries fail with a 500. What is wrong with my setup?

I am new to TRPC and have set up a custom hook in my NextJS app to make queries. This hook is sending out a query to generateRandomWorker but the response always returns a generic 500 error. I am completely stuck until I can figure out this issue.
The hook:
// filepath: src\utilities\hooks\useCreateRandomWorker.ts
type ReturnType = {
createWorker: () => Promise<Worker>,
isCreating: boolean,
}
const useCreateRandomWorker = (): ReturnType => {
const [isCreating, setIsCreating] = useState(false);
const createWorker = async (): Promise<Worker> => {
setIsCreating(true);
const randomWorker: CreateWorker = await client.generateRandomWorker.query(null);
const createdWorker: Worker = await client.createWorker.mutate(randomWorker);
setIsCreating(false);
return createdWorker;
}
return { createWorker, isCreating };
Here is the router. I know the WorkerService calls work because they are returning the proper values when passed into getServerSideProps which directly calls them. WorkerService.generateRandomWorker is synchronous, the others are async.
// filepath: src\server\routers\WorkerAPI.ts
export const WorkerRouter = router({
generateRandomWorker: procedure
.input(z.null()) // <---- I have tried completely omitting `.input` and with a `null` property
.output(PrismaWorkerCreateInputSchema)
.query(() => WorkerService.generateRandomWorker()),
getAllWorkers: procedure
.input(z.null())
.output(z.array(WorkerSchema))
.query(async () => await WorkerService.getAllWorkers()),
createWorker: procedure
.input(PrismaWorkerCreateInputSchema)
.output(WorkerSchema)
.mutation(async ({ input }) => await WorkerService.createWorker(input)),
});
The Next API listener is at filepath: src\pages\api\trpc\[trpc].ts
When the .input is omitted the request URL is /api/trpc/generateRandomWorker?batch=1&input={"0":{"json":null,"meta":{"values":["undefined"]}}} and returns a 500.
When the .input is z.null() the request URL is /api/trpc/generateRandomWorker?batch=1&input={"0":{"json":null}} and returns a 500.
Can anyone help on what I might be missing?
Additional Info
The client declaration.
// filepath: src\utilities\trpc.ts
export const client = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: `${getBaseUrl() + trpcUrl}`, // "http://localhost:3000/api/trpc"
fetch: async (input, init?) => {
const fetch = getFetch();
return fetch(input, {
...init,
credentials: "include",
})
}
}),
],
transformer: SuperJSON,
});
The init:
// filepath: src\server\trpc.ts
import SuperJSON from "superjson";
import { initTRPC } from "#trpc/server";
export const t = initTRPC.create({
transformer: SuperJSON,
});
export const { router, middleware, procedure, mergeRouters } = t;
Sorry I am not familiar with the vanilla client. But since you're in react you might be interested in some ways you can call a trpc procedure from anywhere while using the react client:
By using the context you can pretty much do anything from anywhere:
const client = trpc.useContext()
const onClick = async () => {
const data = await client.playlist.get.fetch({id})
}
For a known query, you can disable it at declaration and refetch it on demand
const {refetch} = trpc.playlist.get.useQuery({id}, {enabled: false})
const onClick = async () => {
const data = await refetch()
}
If your procedure is a mutation, it's trivial, so maybe you can turn your GET into a POST
const {mutateAsync: getMore} = trpc.playlist.more.useMutation()
const onClick = async () => {
const data = await getMore({id})
}
Answered.
Turns out I was missing the export for the API handler in api/trpc/[trpc].ts

Pausing react query and re-fetching new data

I have a useQuery which is disabled in a react function component. I have another useQuery that uses mutate and on the success it calls refetchMovies(). This all seems to work well but I'm seeing old data in the refetchMovies. Is there a way for to get the refetchMovies to always call fresh data from the server when its called ?
const MyComponent = () => {
const {data, refetch: refetchMovies} = useQuery('movies', fetchMovies, {
query: {
enabled: false
}
})
const {mutate} = useQuery('list', fetchList)
const addList = useCallback(
(data) => {
mutate(
{
data: {
collection: data,
},
},
{
onSuccess: () => refetchMovies(),
onError: (error) => console.log('error')
}
)
},
[mutate, refetchMovies]
)
return (
<div onClick={addList}> {data} </div>
)
}
Try to invalidate the query in your onSuccess callback instead of manually refetching it:
https://tanstack.com/query/v4/docs/react/guides/query-invalidation
Example:
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries({ queryKey: ['todos'] })

How to apply getServerSideProps and react-query for prefecthing data in NextJS

I'm beginner of NextJS. I want to make notice board.
Now,
When I click prev or next button router.push(?page=${currentPage})
Then, getServerSideProps get query and request (GET) to server to get currentPage data
getServerSideProps returns data -> in page notice, I use useQuery (react-query)
Then, in this situation, the problem is:
when I click next button, getServerSideProps => correctly gets currentPageData!
BUT: react-query data disappeared with reactqueryDevTools...
It doesn't work correctly.
I want to prefecth nextPageData and show currentPageData using getServerSideProps and react-query.
How can I solve it?
This is my code:
// page/notice/index.tsx
export async function getServerSideProps(context) {
const res = await getNotices(context.query.page)
const data = res.data
return {
props: { result: data },
}
}
export async function getNotices(page) {
return await api.get(`/admin/notice?page=${page}`)
}
export default function Notice({ result }) {
const queryClient = useQueryClient()
const { isLoading, data } = useQuery(
['notices', result.currentPage],
() => getNotices(result.currentPage),
{
initialData: result,
select: (data) => data.data,
},
)
useEffect(() => {
const nextPage = result.currentPage + 1
queryClient.prefetchQuery(['notices', nextPage], () => getNotices(nextPage))
console.log('reactQueryData', data)
}, [result, queryClient])
...

React-Query and Query Invalidation Question

I don't really know how to ask clearly but, I will paste my code first and ask below.
function useToDos() {
const queryCache = useQueryCache();
const fetchTodos = useQuery(
'fetchTodos',
() => client.get(paths.todos).then(({ data }: any) => data),
{ enabled: false }
);
const createTodo = async ({ name ) =>
await client.post(paths.todos, { name }).then(({ data }) => data);
return {
fetchTodos,
createTodo: useMutation(createTodo, {
onMutate: newItem => {
queryCache.cancelQueries('fetchTodos');
const previousTodos = queryCache.getQueryData('fetchTodos');
queryCache.setQueryData('fetchTodos', old => [
...old,
newItem,
]);
return () => queryCache.setQueryData('fetchTodos', previousTodos);
},
}),
};
}
As you can see, I am trying to create my own custom hooks that wrap react-query functionality. Because of this, I need to set my fetchTodos query to be disabled so it doesn't run right away. However, does this break all background data fetching?
Specifically, when I run createTodo and the onMutate method triggers, I would ideally like to have the fetchTodos query update in the background so that my list of todos on the frontend is updated without having to make the request again. But it seems that with the query initially set to be disabled, the background updating doesn't take effect.
As I don't think wrapping react-query hooks into a library of custom hooks is a very great idea, I will probably have more questions about this same setup but for now, I will start here. Thank you. 😊
The mutation does not automatically triggers a refetch. The way to achieve this using react-query is via queryCache.invalidateQueries to invalidate the cache after the mutation. From the docs:
The invalidateQueries method can be used to invalidate and refetch single or multiple queries in the cache based on their query keys or any other functionally accessible property/state of the query. By default, all matching queries are immediately marked as stale and active queries are refetched in the background.
So you can configure the useMutation to invalidate the query when the mutation settles. Example:
function useToDos() {
const queryCache = useQueryCache();
const fetchTodos = useQuery(
'fetchTodos',
() => client.get(paths.todos).then(({ data }: any) => data),
{ enabled: false }
);
const createTodo = async ({ name ) =>
await client.post(paths.todos, { name }).then(({ data }) => data);
return {
fetchTodos,
createTodo: useMutation(createTodo, {
onMutate: newItem => {
queryCache.cancelQueries('fetchTodos');
const previousTodos = queryCache.getQueryData('fetchTodos');
queryCache.setQueryData('fetchTodos', old => [
...old,
newItem,
]);
return () => queryCache.setQueryData('fetchTodos', previousTodos);
},
onSettled: () => {
cache.invalidateQueries('fetchTodos');
}
}),
};
}
What about splitting the logic into two different hooks? Instead of a monolith like useToDos?
That way you could have a hook for fetching:
const fetchData = _ => client.get(paths.todos).then(({ data }: any) => data)
export default function useFetchTodo(
config = {
refetchOnWindowFocus: false,
enabled: false
}
) {
return useQuery('fetchData', fetchData, config)
}
And in your mutation hook you can refetch manually, before createTodo
import useFetchTodo from './useFetchTodo'
//
const { refetch } = useFetchTodo()
// before createTodo
refetch()

React hooks dependencies, including it creates an infinite loop, not including it doesn't give me the latest value

Using React hooks.
I'm trying to do a simple API fetch call with some data, but I can't seem to make this work.
Here is the sandbox link
In this example, the objective is that every 5secs, it fetches to the server to get any updates to the username since the latest latestUpdate.
But for convenience, I will include the code here as well:
const SmallComponent = () => {
const { id, username, latestUpdate } = useItemState();
const dispatch = useItemDispatch();
console.log("Render id", id, "Latest", latestUpdate);
const fetchUsername = useCallback(async () => {
console.log("Getting Id", id, "Latest", latestUpdate);
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/" + id
);
const user = await response.json();
dispatch({ type: "setUsername", usernameUpdated: user.name });
}, [dispatch, id]);
const updateId = useCallback(() => {
dispatch({ type: "setId", id: id + 1 });
}, [dispatch, id]);
useEffect(() => {
fetchUsername();
const refresh = setInterval(() => {
updateId();
}, 5000);
return () => clearInterval(refresh);
}, [fetchUsername, updateId]);
return (
<div>
<h4>Username from fetch:</h4>
<p>{username || "not set"}</p>
</div>
);
};
As you'll notice, my fetchUsername is missing a dependency for latestUpdate (which is used on my server to only send udpates since that date). I update latestUpdate when the fetchUsername is finished in my reducer.
What I need:
on mount: fetch username => updates state for username and latestUpdate
interval: every 5secs => fetch updates to username and update latestUpdate to new Date()
The problem is:
If I add the dependency to the useCallback for fetchUsername, I get an infinite refresh loop.
If I don't add it, my latestUpdate value is wrong (ie initial value)
What am I doing wrong?
As you're not using the fetch method anywhere else, it makes sense to put it inside the useEffect directly. No need for useCallback:
useEffect(() => {
const fetchUsername = async () => {
console.log("FETCH", latestUpdate);
const url =
"https://jsonplaceholder.typicode.com/users/" + id + "#" + latestUpdate;
const response = await fetch(url);
const user = await response.json();
dispatch({ type: "setUsername", usernameUpdated: user.name });
};
const refresh = setInterval(() => {
fetchUsername();
}, 5000);
return () => clearInterval(refresh);
}, [dispatch, id, latestUpdate]);
Here is the full CodeSandBox:
https://codesandbox.io/s/trusting-framework-hvw06?file=/src/App.js
You can find more in the official docs (look for "...to move that function inside of your effect"):
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
And I also recommend Robin Wieruch's hook-fetching tutorial: https://www.robinwieruch.de/react-hooks-fetch-data
In general, I would highly recommend using something like react-query, as it will also take care of caching. It is a better way to consume your server data (instead of fetching and putting the response in your context): https://github.com/tannerlinsley/react-query

Resources