diff --git a/docs/framework/react/reference/mutationOptions.md b/docs/framework/react/reference/mutationOptions.md new file mode 100644 index 0000000000..0fa145a890 --- /dev/null +++ b/docs/framework/react/reference/mutationOptions.md @@ -0,0 +1,15 @@ +--- +id: mutationOptions +title: mutationOptions +--- + +```tsx +mutationOptions({ + mutationFn, + ...options, +}) +``` + +**Options** + +You can generally pass everything to `mutationOptions` that you can also pass to [`useMutation`](./useMutation.md). diff --git a/docs/framework/react/typescript.md b/docs/framework/react/typescript.md index baac2cc771..74a07c8101 100644 --- a/docs/framework/react/typescript.md +++ b/docs/framework/react/typescript.md @@ -239,6 +239,24 @@ const data = queryClient.getQueryData(['groups']) [//]: # 'TypingQueryOptions' [//]: # 'Materials' +## Typing Mutation Options + +Similarly to `queryOptions`, you can use `mutationOptions` to extract mutation options into a separate function: + +```ts +function useGroupPostMutation() { + const queryClient = useQueryClient() + + return mutationOptions({ + mutationKey: ['groups'], + mutationFn: executeGroups, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['posts'] }) + }, + }) +} +``` + ## Further Reading For tips and tricks around type inference, have a look at [React Query and TypeScript](./community/tkdodos-blog.md#6-react-query-and-typescript) from diff --git a/packages/react-query/src/__tests__/mutationOptions.test-d.tsx b/packages/react-query/src/__tests__/mutationOptions.test-d.tsx new file mode 100644 index 0000000000..e16d3ac302 --- /dev/null +++ b/packages/react-query/src/__tests__/mutationOptions.test-d.tsx @@ -0,0 +1,51 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { dataTagSymbol } from '@tanstack/query-core' +import { mutationOptions } from '../mutationOptions' + +describe('mutationOptions', () => { + it('should not allow excess properties', () => { + return mutationOptions({ + mutationFn: () => Promise.resolve(5), + mutationKey: ['key'], + // @ts-expect-error this is a good error, because onMutates does not exist! + onMutates: 1000, + }) + }) + + it('should infer types for callbacks', () => { + return mutationOptions({ + mutationFn: () => Promise.resolve(5), + mutationKey: ['key'], + onSuccess: (data) => { + expectTypeOf(data).toEqualTypeOf() + }, + }) + }) + + it('should tag the mutationKey with the result type of the MutationFn', () => { + const { mutationKey } = mutationOptions({ + mutationKey: ['key'], + mutationFn: () => Promise.resolve(5), + }) + + expectTypeOf(mutationKey[dataTagSymbol]).toEqualTypeOf() + }) + + it('should tag the mutationKey with unknown if there is no mutationFn', () => { + const { mutationKey } = mutationOptions({ + mutationKey: ['key'], + }) + + expectTypeOf(mutationKey[dataTagSymbol]).toEqualTypeOf() + }) + + it('should tag the mutationKey with the result type of the MutationFn if onSuccess is used', () => { + const { mutationKey } = mutationOptions({ + mutationKey: ['key'], + mutationFn: () => Promise.resolve(5), + onSuccess: () => {}, + }) + + expectTypeOf(mutationKey[dataTagSymbol]).toEqualTypeOf() + }) +}) diff --git a/packages/react-query/src/__tests__/mutationOptions.test.tsx b/packages/react-query/src/__tests__/mutationOptions.test.tsx new file mode 100644 index 0000000000..cea9683491 --- /dev/null +++ b/packages/react-query/src/__tests__/mutationOptions.test.tsx @@ -0,0 +1,14 @@ +import { describe, expect, it } from 'vitest' +import { mutationOptions } from '../mutationOptions' +import type { UseMutationOptions } from '../types' + +describe('mutationOptions', () => { + it('should return the object received as a parameter without any modification.', () => { + const object: UseMutationOptions = { + mutationKey: ['key'], + mutationFn: () => Promise.resolve(5), + } as const + + expect(mutationOptions(object)).toStrictEqual(object) + }) +}) diff --git a/packages/react-query/src/mutationOptions.ts b/packages/react-query/src/mutationOptions.ts new file mode 100644 index 0000000000..a37c9d745a --- /dev/null +++ b/packages/react-query/src/mutationOptions.ts @@ -0,0 +1,105 @@ +import type { + DataTag, + DefaultError, + InitialDataFunction, + MutationFunction, + OmitKeyof, + SkipToken, +} from '@tanstack/query-core' +import type { UseMutationOptions } from './types' + +export type UndefinedInitialDataOptions< + TMutationFnData = unknown, + TError = DefaultError, + TData = void, + TMutationKey = unknown, +> = UseMutationOptions & { + initialData?: + | undefined + | InitialDataFunction> + | NonUndefinedGuard +} + +export type UnusedSkipTokenOptions< + TMutationFnData = unknown, + TError = DefaultError, + TData = void, + TMutationKey = unknown, +> = OmitKeyof< + UseMutationOptions, + 'mutationFn' +> & { + mutationFn?: Exclude< + UseMutationOptions< + TMutationFnData, + TError, + TData, + TMutationKey + >['mutationFn'], + SkipToken | undefined + > +} + +type NonUndefinedGuard = T extends undefined ? never : T + +export type DefinedInitialDataOptions< + TMutationFnData = unknown, + TError = DefaultError, + TData = void, + TMutationKey = unknown, +> = Omit< + UseMutationOptions, + 'mutationFn' +> & { + initialData: + | NonUndefinedGuard + | (() => NonUndefinedGuard) + mutationFn?: MutationFunction +} + +export function mutationOptions< + TMutationFnData = unknown, + TError = DefaultError, + TData = void, + TMutationKey = unknown, +>( + options: DefinedInitialDataOptions< + TMutationFnData, + TError, + TData, + TMutationKey + >, +): DefinedInitialDataOptions & { + mutationKey: DataTag +} + +export function mutationOptions< + TMutationFnData = unknown, + TError = DefaultError, + TData = void, + TMutationKey = unknown, +>( + options: UnusedSkipTokenOptions, +): UnusedSkipTokenOptions & { + mutationKey: DataTag +} + +export function mutationOptions< + TMutationFnData = unknown, + TError = DefaultError, + TData = void, + TMutationKey = unknown, +>( + options: UndefinedInitialDataOptions< + TMutationFnData, + TError, + TData, + TMutationKey + >, +): UndefinedInitialDataOptions & { + mutationKey: DataTag +} + +export function mutationOptions(options: unknown) { + return options +}