query
query copied to clipboard
feat(react-query): add mutationOptions
mutationOptions helps extracting mutation options into separate functions.
View your CI Pipeline Execution ↗ for commit 357269fdbc15029a1badefe555d201b92cee7315
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
✅ Succeeded | 46s | View ↗ |
nx run-many --target=build --exclude=examples/*... |
✅ Succeeded | 2s | View ↗ |
☁️ Nx Cloud last updated this comment at 2025-07-09 14:13:27 UTC
More templates
- @tanstack/query-example-angular-auto-refetching
- @tanstack/query-example-angular-basic
- @tanstack/query-example-angular-basic-persister
- @tanstack/query-example-angular-devtools-panel
- @tanstack/query-example-angular-infinite-query-with-max-pages
- @tanstack/query-example-angular-optimistic-updates
- @tanstack/query-example-angular-pagination
- @tanstack/query-example-angular-query-options-from-a-service
- @tanstack/query-example-angular-router
- @tanstack/query-example-angular-rxjs
- @tanstack/query-example-angular-simple
- @tanstack/query-example-solid-astro
- @tanstack/query-example-solid-basic
- @tanstack/query-example-solid-basic-graphql-request
- @tanstack/query-example-solid-default-query-function
- @tanstack/query-example-solid-simple
- @tanstack/query-example-solid-start-streaming
- @tanstack/query-example-react-algolia
- @tanstack/query-example-react-auto-refetching
- @tanstack/query-example-react-basic
- @tanstack/query-example-react-basic-graphql-request
- @tanstack/query-example-chat
- @tanstack/query-example-react-default-query-function
- @tanstack/query-example-react-devtools-panel
- @tanstack/query-example-eslint-legacy
- @tanstack/query-example-react-infinite-query-with-max-pages
- @tanstack/query-example-react-load-more-infinite-scroll
- @tanstack/query-example-react-nextjs
- @tanstack/query-example-react-nextjs-app-prefetching
- @tanstack/query-example-nextjs-suspense-streaming
- @tanstack/query-example-react-offline
- @tanstack/query-example-react-optimistic-updates-cache
- @tanstack/query-example-react-optimistic-updates-ui
- @tanstack/query-example-react-pagination
- @tanstack/query-example-react-playground
- @tanstack/query-example-react-prefetching
- @tanstack/query-example-react-react-native
- @tanstack/query-example-react-router
- @tanstack/query-example-react-rick-morty
- @tanstack/query-example-react-shadow-dom
- @tanstack/query-example-react-simple
- @tanstack/query-example-react-star-wars
- @tanstack/query-example-react-suspense
- @tanstack/query-example-svelte-auto-refetching
- @tanstack/query-example-svelte-basic
- @tanstack/query-example-svelte-load-more-infinite-scroll
- @tanstack/query-example-svelte-optimistic-updates
- @tanstack/query-example-svelte-playground
- @tanstack/query-example-svelte-simple
- @tanstack/query-example-svelte-ssr
- @tanstack/query-example-svelte-star-wars
- @tanstack/query-example-vue-2.6-basic
- @tanstack/query-example-vue-2.7-basic
- @tanstack/query-example-vue-basic
- @tanstack/query-example-vue-dependent-queries
- @tanstack/query-example-vue-nuxt3
- @tanstack/query-example-vue-persister
- @tanstack/query-example-vue-simple
@tanstack/angular-query-devtools-experimental
npm i https://pkg.pr.new/@tanstack/angular-query-devtools-experimental@8960
@tanstack/angular-query-experimental
npm i https://pkg.pr.new/@tanstack/angular-query-experimental@8960
@tanstack/eslint-plugin-query
npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@8960
@tanstack/query-async-storage-persister
npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@8960
@tanstack/query-broadcast-client-experimental
npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@8960
@tanstack/query-core
npm i https://pkg.pr.new/@tanstack/query-core@8960
@tanstack/query-devtools
npm i https://pkg.pr.new/@tanstack/query-devtools@8960
@tanstack/query-persist-client-core
npm i https://pkg.pr.new/@tanstack/query-persist-client-core@8960
@tanstack/query-sync-storage-persister
npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@8960
@tanstack/react-query
npm i https://pkg.pr.new/@tanstack/react-query@8960
@tanstack/react-query-devtools
npm i https://pkg.pr.new/@tanstack/react-query-devtools@8960
@tanstack/react-query-next-experimental
npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@8960
@tanstack/react-query-persist-client
npm i https://pkg.pr.new/@tanstack/react-query-persist-client@8960
@tanstack/solid-query
npm i https://pkg.pr.new/@tanstack/solid-query@8960
@tanstack/solid-query-devtools
npm i https://pkg.pr.new/@tanstack/solid-query-devtools@8960
@tanstack/solid-query-persist-client
npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@8960
@tanstack/svelte-query
npm i https://pkg.pr.new/@tanstack/svelte-query@8960
@tanstack/svelte-query-devtools
npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@8960
@tanstack/svelte-query-persist-client
npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@8960
@tanstack/vue-query
npm i https://pkg.pr.new/@tanstack/vue-query@8960
@tanstack/vue-query-devtools
npm i https://pkg.pr.new/@tanstack/vue-query-devtools@8960
commit: 357269f
Codecov Report
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 84.38%. Comparing base (
ed8cc23) to head (357269f). Report is 1 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #8960 +/- ##
===========================================
+ Coverage 45.37% 84.38% +39.00%
===========================================
Files 207 26 -181
Lines 8277 365 -7912
Branches 1865 107 -1758
===========================================
- Hits 3756 308 -3448
+ Misses 4080 48 -4032
+ Partials 441 9 -432
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
- :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
Thank you for reviewing my PR. I thought queryOptions and mutationOptions could be structured similarly since it was an options-related function. I re-created useMutation as start. I changed it to only have UseMutationOptions, excluding unnecessary data tags and initialData.
I merged https://github.com/TanStack/query/pull/9094 to resolve below ci failure because of flaky timer tests
@Ubinquitous Resolve eslint error please
Sorry, I fix error that 'should not allow excess properties' test don't have assertions
Really looking forward to this PR!
Its really helpful to add reusable optimistic updates, rollbacks etc callbacks for mutations. But what if we want to use these reusable callbacks but also want a specific side effect in one component on onSucces, like on login I want to redirect. Is there a way to merge these callbacks somehow?
Thank you, @andredewaard.
I'm not sure if I understood it correctly, but it seems like this can basically be implemented using the spread operator. I’ve used a similar pattern when working with queryOptions as well. Is this what you were referring to?
const options = mutationOptions({
...
})
useMutation({
...options,
onSuccess: () => {},
})
If you're only changing the funnel in a specific component, I recommend wrapping it in a function for better management. I used to do it that way as well when working with queryOptions.
Alternatively, if you're using a function, you could also add logic to branch only when a specific component name is passed in.
export const postQueries = {
all: () => ['post-list'],
list: (): UseQueryOptions<Post[], Error, string> =>
queryOptions({
queryKey: [...postListQueries.all()],
queryFn: () => getPostList(),
select: (data) => getPostListString(data),
}),
detail: (type: PostType): UseSuspenseQueryOptions<PostInfo, Error> =>
queryOptions({
queryKey: [...postListQueries.all(), 'detail', type],
queryFn: () => getPostDetail({ type }),
select: (data) => data,
}),
...
};
@Ubinquitous Thanks for your reply. I meant to not override the logic happening in onSuccess if I add an onSuccess callback in the component.
So more like this.
useMutation({
...options,
onSuccess: () => {
options.onSuccess()
// added logic
},
})
yes, with optional chaining on the options callback:
useMutation({
...options,
onSuccess: () => {
options?.onSuccess()
// added logic
},
})
To override queryOptions without using the spread operator twice, you can use a prop getter.
const compose = (...functions) => (...args) =>
functions.forEach((fn) => fn?.(...args))
const options = queryOptions({ ... })
const getOptions = ({ onSuccess }) => {
return {
onSuccess: compose(onSuccess, options.onSuccess),
...options
}
}
getOptions({
onSuccess: () => {} // behaves the same
})
I find mutationOptions useful. Currently, I am using a style that manages queryKeys and mutationKeys by bundling them into javascript objects, and mutationOptions can reduce developers' type mistakes and boilerplate code when using it this way. In addition, it can be used with useMutation, useIsMutating, and queryClient.isMutating.
Just like queryOptions allows call invalidateQueries and use that variable, wouldn't mutationOptions do the same (at useMutation, useIsMutating), keeping the code clean?
export const QUERY_KEY = (uniqueKey) => {
return {
get: [...queries.all(), 'get-key', uniqueKey],
list: [...queries.all(), 'list-key', uniqueKey],
};
};
export const queries = {
all: () => ['query-key'],
get: ({ some }) =>
queryOptions({
queryKey: QUERY_KEY(some).get,
queryFn: () => ...,
}),
list: ({ some }) =>
queryOptions({
queryKey: QUERY_KEY(some).list,
queryFn: () => ...,
}),
// this
update: (): UseMutationOptions<
null,
Error,
Request
> => ({
mutationFn: (req) => ...,
}),
};
and mutationOptions can reduce developers' type mistakes and boilerplate code when using it this way
you can achieve the same thing (avoiding type mistakes) by using satisfies UseMutationOptions
it can be used with useMutation, useIsMutating, and queryClient.isMutating.
it will only work with things like isMutating if we make the MutationKey required. It’s not required for useMutation though. That’s the big difference to queries.#
I fear that if we make this helper, it should have mutationKey required, or it won’t do anything with functions that accept mutationFilters.
But if we do that, a lot of people will ask why all a sudden they have to give every mutation a MutationKey when only using it for useMutation ...
you can achieve the same thing (avoiding type mistakes) by using satisfies UseMutationOptions
okay, it actually needs to be satisfies UseMutationOptions<any, any, any, any>, and that won’t work for inference that well:
https://www.typescriptlang.org/play/?ssl=6&ssc=3&pln=6&pc=51#code/JYWwDg9gTgLgBAbwKoGcCmBZArjAhjYCAOwHkwDiUAaOLdbPCogXzgDMoIQ4ByAATxEUeAMYBrAPRQ0uETAC0ARyxooATx4BYAFChIsOLgAehaojgw1YNHACCJiCgCiUTlDisOXXsdNbtOiKU8BDkhEJwALyIOnAgOPjhAGJEAFyGKGpEInAAFEbpRFggAEaqAJRRAHxwAMowUMBEAOb55TqsKIkobMBoKHComAlMZEwoADy4RGo007OGM3MzVTqBweYAJvi4HlG09CPhuaHj7doSEnDXNwB6APxAA
guess we need that helper after all
any ETA on this? would be really helpful in my current app.
@Nick-Lucas Thanks for idea suggestion to solve mutationKey optional problem! I added you as co-author in 2f7eb30
I fixed the part where the test code was incorrect and added test code to check that useMutation works properly when there is no mutationKey.
Thanks everyone who worked on this ❤️
is there a plan to port this to solid-query?