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.
Related
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.
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.
I'm updating cache with react-query using useQuery and setQueryData, the problem is setQueryData can take up to 2mins to update the data, possibly due to a loop of some sort. I'm mapping each page on the 'Styles' data, and updating the Styles, Groups and Ranges on the page that matches the pageIndex with data from the response. I have no idea why the very long update, an I using react-query wrong here? The data being updated is not huge at all by the way.
Thanks in advance.
export const useStyle = (styleId, pageIndex) => {
const queryClient = useQueryClient();
const { refetch } = useQuery('Style', () => fetchStyle(styleId), {
staleTime: Infinity,
refetchOnWindowFocus: false,
initialData: {},
onSuccess: (res) => {
queryClient.setQueryData('Styles', (oldData) => ({
pages: map(oldData.pages, (page, index) => ({
...page,
...(index === pageIndex
? {
Styles: {
...page.Styles,
...res.Styles,
},
Groups: {
...page.Groups,
...res.Groups,
},
Ranges: {
...page.Ranges,
...res.Ranges,
},
}
: {}),
})),
pagesParams: oldData.pageParams,
}));
},
});
return { refetchStyle: refetch };
};
I have solved this problem by using useMutation instead of useQuery and setQueryData, this is the better approach if a loop of onSuccess'es is caused by setQueryData.
I think you're creating an infinite loop here:
useQuery('Styles') subscribes to the key Styles, so this component will always update / re-render whenever something in the cache under that key changes
in onSuccess of this query, you update that very same key (Styles)
this update informs all observers, because they should be aware of that change
calling setQueryData also triggers onSuccess, because setQueryData is to be treated the same as if the data came from the backend via the queryFn.
this will trigger your onSuccess again and so on...
The better question would be: What problem are you trying to solve? By the looks of it, you want to perform some data transformation after each fetch. For this, you have multiple options, all of which I have outlined extensively here: https://tkdodo.eu/blog/react-query-data-transformations
But usually, merging data like that should not be necessary. The cache should best be a 1:1 mapping of the response you get from the server.
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()}>
I am working on project with Apollo on client side. I am using react-apollo-hooks on my client side. And I have a problem with useApolloClient.
When i fire query with my client I got in useApolloClient I don't get back all data I need. FetchMore is missing. If I use regular query (useQuery) I get that. But problem is I need to fire that query on click and i need to use one provided with apollo client.
I have this function for fetching data on click:
const bulkSearch = async data => {
setContent(<Spinner />);
showModal();
try {
const response = await client.query({
query: BULK_SEARCH_PRODUCTS,
variables: { data }
});
if (!response.loading) {
setContent(
<ProductsListDisplay
products={response.data.bulkSearch.products}
fetchMore={response.fetchMore}
count={{ total: 10 }}
/>
);
return 200;
}
} catch (err) {
return 400;
}
};
And response doesn't contain fetchMore.
On the other way classic query returns fetchMore.
const newdata = useQuery(BULK_SEARCH_PRODUCTS, {
variables: { data: { ids: ["536003", "513010"] } }
});
Some help ? Thank you!
According to the apollo-client docs, ApolloClient.query returns a Promise that resolves to an ApolloQueryResult, which is a simpler object that has only data, errors, loading, networkStatus, and stale as properties.
On the other hand, the render prop argument of react-apollo's Query component gets fed a much richer object, with fetchMore being one of its additional properties. If you want to do something similar using the raw ApolloClient object, you would have to use ApolloClient.watchQuery, which returns an ObservableQuery that you can subscribe to consume results. The benefit of this is that you have access to more methods, such as ObservableQuery.fetchMore.
Note this approach is fundamentally different than using ApolloClient.query, since that function requests one query and returns the result as a Promise, while ApolloClient.watchQuery consistently monitors your cache and pushes updated results to your subscribe method when the cache store changes, so it's a more complicated lifecycle. In general, if you're already using react-apollo or one of the #apollo/react-X packages, you probably want to stay away from ApolloClient.watchQuery, since the functionality from those libraries builds directly on top of it and is designed to be easier to consume.
Hope this helps!
You have to create your own FetchMore method for this. This has to be handled by you that's the safer you to go.
In my case I needed
fetchMore
Adding Infinite loading and should event deal with loading state as well.
Problem with default loading state is that it will be always false as return of promise.
When you use await client.query.
In our query we have cursor based pagination.
read this
Create Function that will trigger on scroll ( end of page )
Check on value of after and update it with state management
Loading as well as data also needs to be in state.
Code:
const fetchMoreFilteredData = async (after) => {
try {
setFilteredLoading(true); // set this state in order to show loading indicator
const { data, loading } = await client.query({
query: QUERY,
variables: {
after: after,
...all variables,
},
fetchPolicy: "network-only",
notifyOnNetworkStatusChange: true,
});
const {
query: {
pageInfo: { hasNextPage, endCursor },
},
} = data;
setFilteredData({
// update your data ...filteredData,
});
setHasNextPage(hasNextPage); // check if there is next page
setEndCursor(endCursor); // set end cursor for next page this will guide the query to fetch next page
setFilteredLoading(loading); // set loading state to false
} catch (error) {
error.graphQLErrors.map((error) => {
console.log("error", error.message);
});
setFilteredLoading(false);
} };
const handleLoadMore = () => {
hasNextPage && fetchMoreFilteredData(_endCursor);
};