How to fetch data with React Query? - reactjs

I'm building a Pokedex (Pokemon Index) application, using React Hook Form and React Query in which a user can submit a pokemon's name and that pokemon's data will render. I'm having trouble retrieving the individual pokemon's data. When I submit a pokemon's name, I receive a status 200 for the query, but the console prints an empty object. Please let me know what I'm doing wrong.
https://codesandbox.io/s/pokedex-5j1jf?file=/src/App.js

useQuery hook expects a function that returns a promise. So handlePokemonFetch should look like this:
const handlePokemonFetch = () => {
return axios(`https://pokeapi.co/api/v2/pokemon/${query}`);
};
Normally react-query would run the query when the component is mounted. If you do not want this behaviour, you have to disable the query by setting, enabled: false, which you actually did. If you want to trigger a fetch by yourself react-query gives you refetch function:
//notice that we also destructure the refetch function
const { isLoading, isError, data, error, refetch } = useQuery(
"pokemon",
handlePokemonFetch,
{
refetchOnWindowFocus: false,
enabled: false
}
);
//when the data is available save it on the state
if(data) setPokemonCharacter(data);
And the you call that function when the form is submitted:
<form onSubmit={()=>refetch()}>

Related

react-query useQuery not refetching after useMutation

I am using react-query (actually tanstack/react-query v4) to query from and mutate a db. Based on docs and research, I gather that useQuery will automatically refetch from the server if/when the server-state differs from the cached state.
However, after I useMutation to update my db, the impacted query does not immediately refetch.
I know that the useMutation is working based on viewing the db on server-side, but also because I can manually refetch using react-query dev tools, and get the new values just fine.
On reading, I have tried two approaches:
the "invalidateQueries" pattern, hoping that the useQuery refetches and re-renders (from the docs on queryInvalidation: "...If the query is currently being rendered via useQuery or related hooks, it will also be refetched in the background")...
const addMover = useMutation({
mutationFn: (newMover) => { ... },
onSuccess: () => {
queryClient.invalidateQueries(["movers"]);
console.log("The mutation is sucessful!");
},
});
---> When this mutation gets run, I do see the 'onSuccess' console.log() coming through, but the query still shows as 'stale' in the dev-tools and does not get re-rendered.
I also tried (in a different place) the "SetQueryData" pattern from the useMutation response, as outlined in the docs...
const handleDelete = useMutation(
{
mutationFn: (wktID) => { ... },
onSuccess: (data) => {
queryClient.setQueryData(["workouts", [activeMover]], data);
},
}
);
My expectation from either approach is simply that the mutated db gets re-queried and re-rendered. I'd prefer to SetQueryData and save a network request, but either approach would make me happy :).
If you want to re-fetch data after mutation you edit your mutation and leave it like this:
const [createHabit, { error, loading }] = useMutation(CREATE_HABIT_MUTATION, {
refetchQueries: [{ query: HABITS_QUERY }],
});
Here you can find an example.

React Query - How to update query results in a React Query with enabled option set?

I'm using React Query for async state management in my React Native app. I've a useQuery() hook with following query options:
useQuery Hook in useStudentAssignments.ts:
const data = useQuery(
studentAssignmentKeys.list({
assignedToIdEq: studentId
}),
async ({ queryKey: [{ params }] }) => {
// Function to persist assignment media
},
{
enabled: !!assignment && !!isConnected,
cacheTime: Infinity,
staleTime: Infinity,
retry: 0,
refetchOnReconnect: 'always',
refetchOnMount: 'always',
onSuccess(data) {
// Async storage of assignments
},
onError() {
// show error message
}
}
App.tsx:
In the main screen I'm using the useQuery hook as:
const { data, isLoading, isSuccess, isError, refetch, isStale } = useStudentAssignments(
studentAssignment?.id
);
useEffect(() => {
if (!!isConnected) {
refetch();
}, [refetch, isConnected);
}
I've these questions:
Since, I've enabled option set, my understanding is that I can't use QueryClient.invalidateQueries(), since it will have no effect. How else can I mark the query key as stale so that it can automatically be refresh?
If automatic query refresh isn't possible, how can I refresh it on some condition or state change?
useQuery() method has an async function (queryFn). Does this function runs only once, or on intervals/ every fetch?
the invalidateQueries, refetchQueries is ignored only when the enabled is false. So when it becomes true, those function could work as your expected
you could call refetch to manually update your data. In RN, it would be called when screen/components is focused.
As I know, it would be triggered when an instance of useQuery is mounted or refetch called. However, they would save the result into cache, then within the cache timeout, it would return the data from cache when you call that from second instance, while queryFn is still running on background. When completed, it updates the cache data.

react query refetches with old query

I have a list component that uses react-query to fetch the list data (in this case tickets)
<List
fetch={{
route: "tickets",
queryKey: "tickets"
}}
...
/>
The component fetches the data like so
const { isLoading, isError, data, refetch } = useQuery([queryKey], () => {
return fetchEntity({ route: `${route}${window.location.search}` })
})
fetchEntity does nothing else than using axios to fetch an return the data
I'm also providing window.location.search to the route because I have a filter component that sets the filter to the url.
The filter works, but when I refetch (by refocusing the window), there are 2 different requests in the network tab.
One with the filter params and the other one without the filter params. Therefore, the list shows the unfiltered data for a few milliseconds and then the filtered data again.
I also tried to use the query like so
const { isLoading, isError, data, refetch } = useQuery([queryKey, window.location.search], () => {
return fetchEntity({ route: `${route}${window.location.search}` })
})
This fixed the problem where the unfiltered data was shown for a few milliseconds. However, the old data is still fetched (maybe it is just faster now so not visible in the ui?).
Can anyone explain this behaviour of react query?
The problem was I was fetching with the same query key in a child component. That's why I had multiple requests.

How to use zustand to store the result of a query

I want to put the authenticated user in a zustand store. I get the authenticated user using react-query and that causes some problems. I'm not sure why I'm doing this. I want everything related to authentication can be accessed in a hook, so I thought zustand was a good choice.
This is the hook that fetches auth user:
const getAuthUser = async () => {
const { data } = await axios.get<AuthUserResponse>(`/auth/me`, {
withCredentials: true,
});
return data.user;
};
export const useAuthUserQuery = () => {
return useQuery("auth-user", getAuthUser);
};
And I want to put auth user in this store:
export const useAuthStore = create(() => ({
authUser: useAuthUserQuery(),
}));
This is the error that I get:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons.
you can read about it in the react documentation:
https://reactjs.org/warnings/invalid-hook-call-warning.html
(I changed the name of some functions in this post for the sake of understandability. useMeQuery = useAuthUserQuery)
I understand the error but I don't know how to fix it.
The misunderstanding here is that you don’t need to put data from react query into any other state management solution. React query is in itself a global state manager. You can just do:
const { data } = useAuthUserQuery()
in every component that needs the data. React query will automatically try to keep your data updated with background refetches. If you don’t need that for your resource, consider setting a staleTime.
—-
That being said, if you really want to put data from react-query into zustand, create a setter in zustand and call it in the onSuccess callback of the query:
useQuery(key, queryFn, { onSuccess: data => setToZustand(data) })

React Query invalidateQueries does not set loading to true

I'm using the useMutation hook to delete the entity and the useQuery hook to load the entities from the api as follows:
const { mutate: $delete } = useMutation(deleteDiscipline, {
onSuccess: () => {
queryClient.invalidateQueries('disciplines')
},
})
const { isLoading, data: disciplines } = useQuery(['disciplines', filter], getFilteredDisciplines)
I rely on the isLoading field to display the loading status bar.
It works when I trigger refetch by switching tabs or changing the filter (query depends on filter state).
But when I call queryClient.invalidateQueries the api call is made and data is updated, but the isLoading field stays true for the entire refetching time.
Awaiting for the invalidation didn't help either:
const { mutate: $delete } = useMutation(deleteDiscipline, {
onSuccess: async () => {
await queryClient.invalidateQueries('disciplines')
},
})
How can I detect the request occurs (including all the triggers like query invalidations and others that I haven't encounter yet)?
isLoading is only true for a hard-loading state, where you have no data to display. Since react-query embraces stale-while-revaliate, it will give you stale data while at the same time doing a background refetch. So your status in that case is still success, with isSuccess being true, and data being available.
isFetching is an additional flag that is always true when a request is in-flight. This is true for the first loading as well as for all background updates.
The solution was to use the isFetching field instead of the isLoading for subsequent fetches.

Resources