How to refetch/fetch after a series of mutation in graphql/apollo/react - reactjs

Right now I have a use case to use two useMutations to create/update database. So the second one is depends on the success of the first one. And also the second mutation needs to be called in a loop, just think about that I have a array and I need loop through the array and apply the second mutation.
After all these mutation finished I have to refetch another api to update caches, because the cache would be impacted by the two mutations.
I am really struggling with how to achieve this.
From another post: Apollo Client - refetchQueries after multiple updates
I can do probably like
const [creatEnrollment] = useMutation(mut1)
const [updateEnrollment] = useMutation(mut2)
const [toFetch, {loading, error, data}] = useLazyQuery(UsersDocument)
await Promise.all([creatEnrollment(), updateEnrollment()])
const result = () => toFetch({
variables: {name: 'i'}
})
but the problem is 1. I need to execute second mutations after the first one; 2, I need to have an array that applied to second mutations one by one.
I also saw
here How can I wait for mutation execution in React Query?
we can use onSuccess
const mutate1 = useMutation((data) => axios.post('/something', { data }))
const mutate2 = useMutation(somethingResult) => axios.put('/somethingElse', { somethingResult })
<button onClick={() => {
mutate1.mutate('data', {
onSuccess: mutate2.mutate
})
}} />
But still 1. how to loop thru mutate2.mutate? and how to fetch after mutate2 finished
do like this????:
<button onClick={() => {
mutate1.mutate('data', {
onSuccess: mutate2.mutate
})
mutate2.mutate('data', {
onSuccess: query
})
}} />
Thank you for helping!!

You can have a function for useMutation and onSuccess the data which use get on success use other mutation
const mutationFuntion = (id) => {
// this is first mutation
return useMutation(
(newTitle) => axios
.patch(`/posts/${id}`, { title: newTitle })
.then(response => response.data),
{
// 💡 response of the mutation is passed to onSuccess
onSuccess: (data) => {
// call the api which will get all the latest update
},
}
)
}
Get the Data of first mutation
const [addTodo, { data, loading, error }] = mutationFuntion(//send data);
This is consecutive mutation I found it from this https://react-query-v3.tanstack.com/guides/mutations#consecutive-mutations doc
useMutation(addTodo, {
onSuccess: (data, error, variables, context) => {
// Will be called 3 times
},
})
['Todo 1', 'Todo 2', 'Todo 3'].forEach((todo) => {
mutate(todo, {
onSuccess: (data, error, variables, context) => {
// Will execute only once, for the last mutation (Todo 3),
// regardless which mutation resolves first
},
})
})
For handle the promise of every mutation call
const mutation = useMutation(addTodo)
try {
const todo = await mutation.mutateAsync(todo)
console.log(todo)
} catch (error) {
console.error(error)
} finally {
console.log('done')
}
Please you need to verify on what kind of object you want to call mutation in loop it array or some thing alse.

Related

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'] })

Query never stops fetching after refetching or invalidating

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.

Multiple useLazyQuery hooks (Apollo Client) in React Function Component

I am trying to include two Apollo-Client useLazyQuery hooks in my function component. Either works fine alone with the other one commented out, but as soon as I include both, the second one does nothing. Any ideas?
export default function MainScreen(props) {
useEffect(() => {
validateWhenMounting();
}, []);
const [validateWhenMounting, { loading, error, data }] = useLazyQuery(
validateSessionToken,
{
onCompleted: (data) => console.log('data', data),
},
);
const [validate, { loading: loading2, error: error2, data: data2 }] =
useLazyQuery(validateSessionTokenWhenSending, {
onCompleted: (data2) => console.log('data2', data2),
});
const handleSendFirstMessage = (selectedCategory, title, messageText) => {
console.log(selectedCategory, title, messageText);
validate();
};
Figured it out: Adding the key-value pair fetchPolicy: 'network-only', after onCompleted does the trick. It seems that otherwise, no query is being conducted due to caching...
This is the pattern that I was talking about and mentioned in the comments:
const dummyComponent = () => {
const [lazyQuery] = useLazyQuery(DUMMY_QUERY, {variables: dummyVariable,
onCompleted: data => // -> some code here, you can also accept an state dispatch function here for manipulating some state outside
onError: error => // -> you can accept state dispatch function here to manipulate state from outside
});
return null;
}
this is also a pattern that you are going to need sometimes

Should manually updating the cache always be the preferred option after mutations as long as I get proper data from server?

I am writing a CRUD app with React Query and I created some custom hooks as described here: https://react-query.tanstack.com/examples/custom-hooks
In the docs I see that there are basically two ways to update the cache after a mutation:
Query invalidation (https://react-query.tanstack.com/guides/query-invalidation)
onSuccess: () => {
queryClient.invalidateQueries("posts");
}
Updating the cache manually (https://react-query.tanstack.com/guides/invalidations-from-mutations)
// Update post example
// I get the updated post data for onSuccess
onSuccess: (data) => {
queryClient.setQueryData("posts", (oldData) => {
const index = oldData.findIndex((post) => post.id === data.id);
if (index > -1) {
return [
...oldData.slice(0, index),
data,
...oldData.slice(index + 1),
];
}
});
},
I understand that manual update has the advantage of not doing an extra call for fetching the 'posts', but I wonder if there is any advantage of invalidating cache over the manual update. For example:
import { useMutation, useQueryClient } from "react-query";
const { API_URL } = process.env;
const createPost = async (payload) => {
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
};
if (API_URL) {
try {
const response = await fetch(API_URL, options);
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
} catch (error) {
throw new Error(error);
}
} else {
throw new Error("No api url is set");
}
};
export default function useCreatePost() {
const queryClient = useQueryClient();
return useMutation((payload) => createPost(payload), {
// DOES INVALIDATING HAVE ANY ADVANTAGE OVER MANUAL UPDATE IN THIS CASE?
// onSuccess: () => {
// queryClient.invalidateQueries("posts");
// },
onSuccess: (data) => {
queryClient.setQueryData("posts", (oldData) => {
return [...oldData, data];
});
},
});
}
Thanks for your time!
As you state it yourself, the only advantage is that you don't waste another network call to update data we already have.
Here we have a create and delete example.
import { useMutation, useQueryClient } from 'react-query'
const queryClient = useQueryClient()
// createPost(post: PostT) {
// const { data } = await http.post<{ post: PostT >('/posts', { post });
// return data.post;
// }
const mutation = useMutation(createPost, {
onSuccess: (post) => {
queryClient.setQueryData<PostT[]>(['posts'], (oldData || []) => [ ...oldData, post])
},
})
// deletePost(id: string) {
// await http.delete(`/posts/${id}`);
// }
const mutation = useMutation(deletePost, {
onSuccess: (_, id) => {
queryClient.setQueryData<PostT[]>(['posts'], (oldData || []) => oldData.filter((post) => id !== post.id)
},
})
Invalidating the query can also be an option is some cases. The query will be invalidated and the data will be marked as stale. This will trigger a refetching in the background. So you know for a fact that the data will be as fresh as possible.
This can be handy if you got:
multiple queries to update with data from a mutation
have a (difficult) nested data structure to update
import { useMutation, useQueryClient } from 'react-query'
const queryClient = useQueryClient()
const mutation = useMutation(createPost, {
onSuccess: () => {
queryClient.invalidateQueries('posts')
queryClient.invalidateQueries('meta')
queryClient.invalidateQueries('headers')
},
})
But it really is up to you.
The main advantage of using manual updates comes from the fact that you can do it before the request is sent to the server; so if you manually update after the request is successful, then there's not much of an advantage if the data that you get from the server doesn't need to be immediately present to the user & in those cases (which I have found to be the majority) you better off invalidating. when you use optimistic updates, you assume the request is successful before you send it to server & then if the request fails you just roll back your update. this way your action happens instantly which is a better UX than doing the action, showing a loading spinner or something & then showing the updated state. so I have found it more useful for giving instantaneous feedback to the user than saving an extra request to the server. in most cases (as in yours) you still need to invalidate the query after, because your manually added post doesn't have an id, so you should sync it with the list of posts from the server. so be very careful about that because if you reading from that id somewhere else in that page, it would be undefined & would throw an error. so at the end of the day your mutation is not a great candidate for optimistic update & you should be careful to handle all the problems that can come up with your posts value having a post with no id in it (as opposed to something like a follow action which is just changing a boolean value in your database & you can confidently mutate the cache & undo it if request was not successful). so if we assume that you can handle that problem your useMutation hook would be something like this:
return useMutation(
(payload) => {
queryClient.setQueryData("posts", (oldData) => {
return [...oldData, payload];
});
return createPost(payload);
},
{
onSettled: () => {
queryClient.invalidateQueries("posts");
},
}
);

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()

Resources