From accf669080eb697c76fe39e7195b5f179f2f2ec0 Mon Sep 17 00:00:00 2001 From: Daniel Bartsch Date: Tue, 9 Nov 2021 14:20:16 +0100 Subject: [PATCH 1/6] feat: meta to Mutation the same meta that already exists in Query and is accessible in queryCache is now also usable in mutationCache and can be passed in useMutation, just as in useQuery --- src/core/mutation.ts | 5 ++- src/core/mutationCache.ts | 1 + src/core/types.ts | 3 ++ src/react/tests/useMutation.test.tsx | 52 ++++++++++++++++++++++++++++ src/react/types.ts | 2 ++ 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/core/mutation.ts b/src/core/mutation.ts index 54ba5473a9..2647746459 100644 --- a/src/core/mutation.ts +++ b/src/core/mutation.ts @@ -1,4 +1,4 @@ -import type { MutationOptions, MutationStatus } from './types' +import type { MutationOptions, MutationStatus, MutationMeta } from './types' import type { MutationCache } from './mutationCache' import type { MutationObserver } from './mutationObserver' import { getLogger } from './logger' @@ -14,6 +14,7 @@ interface MutationConfig { options: MutationOptions defaultOptions?: MutationOptions state?: MutationState + meta?: MutationMeta } export interface MutationState< @@ -84,6 +85,7 @@ export class Mutation< state: MutationState options: MutationOptions mutationId: number + meta?: MutationMeta private observers: MutationObserver[] private mutationCache: MutationCache @@ -98,6 +100,7 @@ export class Mutation< this.mutationCache = config.mutationCache this.observers = [] this.state = config.state || getDefaultState() + this.meta = config.meta } setState(state: MutationState): void { diff --git a/src/core/mutationCache.ts b/src/core/mutationCache.ts index 02b97b19b4..eef98e3ec4 100644 --- a/src/core/mutationCache.ts +++ b/src/core/mutationCache.ts @@ -52,6 +52,7 @@ export class MutationCache extends Subscribable { defaultOptions: options.mutationKey ? client.getMutationDefaults(options.mutationKey) : undefined, + meta: options.meta, }) this.add(mutation) diff --git a/src/core/types.ts b/src/core/types.ts index 2aa3dda467..583b34a157 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -501,6 +501,8 @@ export type MutationKey = string | readonly unknown[] export type MutationStatus = 'idle' | 'loading' | 'success' | 'error' +export type MutationMeta = Record + export type MutationFunction = ( variables: TVariables ) => Promise @@ -536,6 +538,7 @@ export interface MutationOptions< retry?: RetryValue retryDelay?: RetryDelayValue _defaulted?: boolean + meta?: MutationMeta } export interface MutationObserverOptions< diff --git a/src/react/tests/useMutation.test.tsx b/src/react/tests/useMutation.test.tsx index b253fd2351..34051fd86f 100644 --- a/src/react/tests/useMutation.test.tsx +++ b/src/react/tests/useMutation.test.tsx @@ -519,4 +519,56 @@ describe('useMutation', () => { consoleMock.mockRestore() }) + + it('should pass meta to mutation', async () => { + const successMock = jest.fn() + const errorMock = jest.fn() + + const queryClientAy = new QueryClient({ + mutationCache: new MutationCache({ + onSuccess: (_, __, ___, mutation) => { + successMock(mutation.meta?.mySuccessMessage) + }, + onError: (_, __, ___, mutation) => { + errorMock(mutation.meta?.myErrorMessage) + }, + }), + }) + + const mySuccessMessage = 'mutation succeeded' + const myErrorMessage = 'mutation succeeded' + + function Page() { + const { mutate: succeed } = useMutation(async () => '', { + meta: { mySuccessMessage }, + }) + const { mutate: error } = useMutation( + async () => { + throw new Error('') + }, + { + meta: { myErrorMessage }, + } + ) + + return ( +
+ + +
+ ) + } + + const { getByText } = renderWithClient(queryClientAy, ) + + fireEvent.click(getByText('succeed')) + fireEvent.click(getByText('error now')) + + await sleep(500) + + expect(successMock).toHaveBeenCalledTimes(1) + expect(successMock).toHaveBeenCalledWith(mySuccessMessage) + expect(errorMock).toHaveBeenCalledTimes(1) + expect(errorMock).toHaveBeenCalledWith(myErrorMessage) + }) }) diff --git a/src/react/types.ts b/src/react/types.ts index fce4069159..79aa3d4507 100644 --- a/src/react/types.ts +++ b/src/react/types.ts @@ -9,6 +9,7 @@ import { QueryKey, MutationFunction, MutateOptions, + MutationMeta, } from '../core/types' export interface UseBaseQueryOptions< @@ -97,6 +98,7 @@ export interface UseMutationOptions< retry?: RetryValue retryDelay?: RetryDelayValue useErrorBoundary?: boolean | ((error: TError) => boolean) + meta?: MutationMeta } export type UseMutateFunction< From ccc85c8a7fc96529eab343af8aef097a82d3a877 Mon Sep 17 00:00:00 2001 From: Daniel Bartsch Date: Tue, 9 Nov 2021 15:07:57 +0100 Subject: [PATCH 2/6] Update src/core/mutation.ts Reason: https://github.com/tannerlinsley/react-query/pull/2906#discussion_r745620893 Co-authored-by: Dominik Dorfmeister --- src/core/mutation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/mutation.ts b/src/core/mutation.ts index 2647746459..3846e60ba4 100644 --- a/src/core/mutation.ts +++ b/src/core/mutation.ts @@ -85,7 +85,7 @@ export class Mutation< state: MutationState options: MutationOptions mutationId: number - meta?: MutationMeta + meta: MutationMeta | undefined private observers: MutationObserver[] private mutationCache: MutationCache From 5ea0c1c66397e1401326ac7cfe9a83462862e38e Mon Sep 17 00:00:00 2001 From: Daniel Bartsch Date: Tue, 9 Nov 2021 15:19:14 +0100 Subject: [PATCH 3/6] chore: rewrite test to get rid of arbitrary sleep time --- src/react/tests/useMutation.test.tsx | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/react/tests/useMutation.test.tsx b/src/react/tests/useMutation.test.tsx index 34051fd86f..f49213dcfa 100644 --- a/src/react/tests/useMutation.test.tsx +++ b/src/react/tests/useMutation.test.tsx @@ -521,54 +521,54 @@ describe('useMutation', () => { }) it('should pass meta to mutation', async () => { - const successMock = jest.fn() - const errorMock = jest.fn() + let successMessage: string | null = null + let errorMessage: string | null = null const queryClientAy = new QueryClient({ mutationCache: new MutationCache({ onSuccess: (_, __, ___, mutation) => { - successMock(mutation.meta?.mySuccessMessage) + successMessage = mutation.meta?.metaSuccessMessage as string }, onError: (_, __, ___, mutation) => { - errorMock(mutation.meta?.myErrorMessage) + errorMessage = mutation.meta?.metaErrorMessage as string }, }), }) - const mySuccessMessage = 'mutation succeeded' - const myErrorMessage = 'mutation succeeded' + const metaSuccessMessage = 'mutation succeeded' + const metaErrorMessage = 'mutation failed' function Page() { const { mutate: succeed } = useMutation(async () => '', { - meta: { mySuccessMessage }, + meta: { metaSuccessMessage }, }) const { mutate: error } = useMutation( async () => { throw new Error('') }, { - meta: { myErrorMessage }, + meta: { metaErrorMessage }, } ) return (
- + +
{successMessage}
+
{errorMessage}
) } - const { getByText } = renderWithClient(queryClientAy, ) + const { getByText, queryByText } = renderWithClient(queryClientAy, ) fireEvent.click(getByText('succeed')) - fireEvent.click(getByText('error now')) + fireEvent.click(getByText('error')) - await sleep(500) - - expect(successMock).toHaveBeenCalledTimes(1) - expect(successMock).toHaveBeenCalledWith(mySuccessMessage) - expect(errorMock).toHaveBeenCalledTimes(1) - expect(errorMock).toHaveBeenCalledWith(myErrorMessage) + await waitFor(() => { + expect(queryByText(metaErrorMessage)).not.toBeNull() + expect(queryByText(metaSuccessMessage)).not.toBeNull() + }) }) }) From 4699132237c1dd164a91f232a3bae791d915f0ae Mon Sep 17 00:00:00 2001 From: Daniel Bartsch Date: Wed, 10 Nov 2021 08:44:40 +0100 Subject: [PATCH 4/6] chore: use text to signal when async test is done to wait for, instead of arbitrary wait times --- src/react/tests/useMutation.test.tsx | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/react/tests/useMutation.test.tsx b/src/react/tests/useMutation.test.tsx index f49213dcfa..6b3c15caaf 100644 --- a/src/react/tests/useMutation.test.tsx +++ b/src/react/tests/useMutation.test.tsx @@ -521,16 +521,18 @@ describe('useMutation', () => { }) it('should pass meta to mutation', async () => { - let successMessage: string | null = null - let errorMessage: string | null = null + const consoleMock = mockConsoleError() + + const errorMock = jest.fn() + const successMock = jest.fn() const queryClientAy = new QueryClient({ mutationCache: new MutationCache({ onSuccess: (_, __, ___, mutation) => { - successMessage = mutation.meta?.metaSuccessMessage as string + successMock(mutation.meta?.metaSuccessMessage) }, onError: (_, __, ___, mutation) => { - errorMessage = mutation.meta?.metaErrorMessage as string + errorMock(mutation.meta?.metaErrorMessage) }, }), }) @@ -539,10 +541,10 @@ describe('useMutation', () => { const metaErrorMessage = 'mutation failed' function Page() { - const { mutate: succeed } = useMutation(async () => '', { + const { mutate: succeed, isSuccess } = useMutation(async () => '', { meta: { metaSuccessMessage }, }) - const { mutate: error } = useMutation( + const { mutate: error, isError } = useMutation( async () => { throw new Error('') }, @@ -555,8 +557,8 @@ describe('useMutation', () => {
-
{successMessage}
-
{errorMessage}
+ {isSuccess &&
successTest
} + {isError &&
errorTest
}
) } @@ -567,8 +569,15 @@ describe('useMutation', () => { fireEvent.click(getByText('error')) await waitFor(() => { - expect(queryByText(metaErrorMessage)).not.toBeNull() - expect(queryByText(metaSuccessMessage)).not.toBeNull() + expect(queryByText('successTest')).not.toBeNull() + expect(queryByText('errorTest')).not.toBeNull() }) + + expect(successMock).toHaveBeenCalledTimes(1) + expect(successMock).toHaveBeenCalledWith(metaSuccessMessage) + expect(errorMock).toHaveBeenCalledTimes(1) + expect(errorMock).toHaveBeenCalledWith(metaErrorMessage) + + consoleMock.mockRestore() }) }) From 0cd5cd2ae6df6aaaddf0d1d781f155a65ca7baf7 Mon Sep 17 00:00:00 2001 From: Daniel Bartsch Date: Wed, 10 Nov 2021 09:37:59 +0100 Subject: [PATCH 5/6] chore: get rid of placeholder queryClient variable name --- src/react/tests/useMutation.test.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/react/tests/useMutation.test.tsx b/src/react/tests/useMutation.test.tsx index 6b3c15caaf..f4c25e0998 100644 --- a/src/react/tests/useMutation.test.tsx +++ b/src/react/tests/useMutation.test.tsx @@ -526,7 +526,7 @@ describe('useMutation', () => { const errorMock = jest.fn() const successMock = jest.fn() - const queryClientAy = new QueryClient({ + const queryClientMutationMeta = new QueryClient({ mutationCache: new MutationCache({ onSuccess: (_, __, ___, mutation) => { successMock(mutation.meta?.metaSuccessMessage) @@ -563,7 +563,10 @@ describe('useMutation', () => { ) } - const { getByText, queryByText } = renderWithClient(queryClientAy, ) + const { getByText, queryByText } = renderWithClient( + queryClientMutationMeta, + + ) fireEvent.click(getByText('succeed')) fireEvent.click(getByText('error')) From b464b49538ec37903422333ee9a90c59e5a8454e Mon Sep 17 00:00:00 2001 From: Daniel Bartsch Date: Wed, 17 Nov 2021 09:07:06 +0100 Subject: [PATCH 6/6] chore: add to docs --- docs/src/pages/reference/useMutation.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/pages/reference/useMutation.md b/docs/src/pages/reference/useMutation.md index 9af167d1b4..6e9789136b 100644 --- a/docs/src/pages/reference/useMutation.md +++ b/docs/src/pages/reference/useMutation.md @@ -23,6 +23,7 @@ const { onSettled, onSuccess, useErrorBoundary, + meta, }) mutate(variables, { @@ -71,6 +72,9 @@ mutate(variables, { - Set this to `true` if you want mutation errors to be thrown in the render phase and propagate to the nearest error boundary - Set this to `false` to disable the behaviour of throwing errors to the error boundary. - If set to a function, it will be passed the error and should return a boolean indicating whether to show the error in an error boundary (`true`) or return the error as state (`false`) +- `meta: Record` + - Optional + - If set, stores additional information on the mutation cache entry that can be used as needed. It will be accessible wherever the `mutation` is available (eg. `onError`, `onSuccess` functions of the `MutationCache`). **Returns**