From d52474bc8df71e13222c77894b8d4df770a01cea Mon Sep 17 00:00:00 2001 From: Matt Sutkowski Date: Sun, 23 Jan 2022 20:42:17 -0800 Subject: [PATCH 1/5] Add an escape hatch for structural sharing --- packages/toolkit/src/query/apiTypes.ts | 1 + packages/toolkit/src/query/core/buildSlice.ts | 5 ++++- packages/toolkit/src/query/createApi.ts | 21 ++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/query/apiTypes.ts b/packages/toolkit/src/query/apiTypes.ts index 3dafe46b02..949bb5376e 100644 --- a/packages/toolkit/src/query/apiTypes.ts +++ b/packages/toolkit/src/query/apiTypes.ts @@ -64,6 +64,7 @@ export interface ApiContext { action: AnyAction ) => CombinedState | undefined hasRehydrationInfo: (action: AnyAction) => boolean + isStructuralSharingEnabled: boolean } export type Api< diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 3c8fd81fcd..c627079f19 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -92,6 +92,7 @@ export function buildSlice({ apiUid, extractRehydrationInfo, hasRehydrationInfo, + isStructuralSharingEnabled, }, assertTagType, config, @@ -157,7 +158,9 @@ export function buildSlice({ (substate) => { if (substate.requestId !== meta.requestId) return substate.status = QueryStatus.fulfilled - substate.data = copyWithStructuralSharing(substate.data, payload) + substate.data = isStructuralSharingEnabled + ? copyWithStructuralSharing(substate.data, payload) + : payload delete substate.error substate.fulfilledTimeStamp = meta.fulfilledTimeStamp } diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index d846cd1c29..89c330a7d0 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -191,6 +191,19 @@ export interface CreateApiOptions< NoInfer, NoInfer > + /** + * Defaults to `true`. + * + * Most apps should leave this setting on. The only time it can be a performance issue + * is if an API returns extremely large amounts of data (e.g. 10,000 rows per request) and + * you're unable to paginate it. + * + * For details of how this works, please see the below. When it is set to `false`, + * every request will cause subscribed components to rerender, even when the data has not changed. + * + * @see https://redux-toolkit.js.org/api/other-exports#copywithstructuralsharing + */ + isStructuralSharingEnabled?: boolean } export type CreateApi = { @@ -229,12 +242,16 @@ export type CreateApi = { export function buildCreateApi, ...Module[]]>( ...modules: Modules ): CreateApi { - return function baseCreateApi(options) { + return function baseCreateApi({ + isStructuralSharingEnabled = true, + ...options + }) { const extractRehydrationInfo = defaultMemoize((action: AnyAction) => options.extractRehydrationInfo?.(action, { reducerPath: (options.reducerPath ?? 'api') as any, }) ) + const optionsWithDefaults = { reducerPath: 'api', serializeQueryArgs: defaultSerializeQueryArgs, @@ -242,6 +259,7 @@ export function buildCreateApi, ...Module[]]>( refetchOnMountOrArgChange: false, refetchOnFocus: false, refetchOnReconnect: false, + isStructuralSharingEnabled, ...options, extractRehydrationInfo, tagTypes: [...(options.tagTypes || [])], @@ -258,6 +276,7 @@ export function buildCreateApi, ...Module[]]>( hasRehydrationInfo: defaultMemoize( (action) => extractRehydrationInfo(action) != null ), + isStructuralSharingEnabled, } const api = { From a9e05618647371ece3566ef79d3d2aaedcab2c15 Mon Sep 17 00:00:00 2001 From: Matt Sutkowski Date: Mon, 24 Jan 2022 20:00:10 -0800 Subject: [PATCH 2/5] Allow for endpoints to opt out of structural sharing --- packages/toolkit/src/query/apiTypes.ts | 3 ++- packages/toolkit/src/query/core/buildInitiate.ts | 12 +++++++++++- packages/toolkit/src/query/core/buildSlice.ts | 9 +++++---- packages/toolkit/src/query/createApi.ts | 14 ++++++-------- packages/toolkit/src/query/endpointDefinitions.ts | 2 ++ 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/toolkit/src/query/apiTypes.ts b/packages/toolkit/src/query/apiTypes.ts index 949bb5376e..016580be10 100644 --- a/packages/toolkit/src/query/apiTypes.ts +++ b/packages/toolkit/src/query/apiTypes.ts @@ -46,6 +46,7 @@ export type Module = { | 'refetchOnFocus' | 'refetchOnReconnect' | 'tagTypes' + | 'structuralSharing' >, context: ApiContext ): { @@ -64,7 +65,7 @@ export interface ApiContext { action: AnyAction ) => CombinedState | undefined hasRehydrationInfo: (action: AnyAction) => boolean - isStructuralSharingEnabled: boolean + structuralSharing: boolean } export type Api< diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 2ecc8eb376..c6dc94ad6f 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -37,6 +37,7 @@ export interface StartQueryActionCreatorOptions { subscribe?: boolean forceRefetch?: boolean | number subscriptionOptions?: SubscriptionOptions + structuralSharing?: boolean } type StartQueryActionCreator< @@ -258,7 +259,15 @@ Features like automatic cache collection, automatic refetching etc. will not be endpointDefinition: QueryDefinition ) { const queryAction: StartQueryActionCreator = - (arg, { subscribe = true, forceRefetch, subscriptionOptions } = {}) => + ( + arg, + { + subscribe = true, + forceRefetch, + subscriptionOptions, + structuralSharing = true, + } = {} + ) => (dispatch, getState) => { const queryCacheKey = serializeQueryArgs({ queryArgs: arg, @@ -273,6 +282,7 @@ Features like automatic cache collection, automatic refetching etc. will not be endpointName, originalArgs: arg, queryCacheKey, + structuralSharing, }) const thunkResult = dispatch(thunk) middlewareWarning(getState) diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index c627079f19..b295763de7 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -92,7 +92,7 @@ export function buildSlice({ apiUid, extractRehydrationInfo, hasRehydrationInfo, - isStructuralSharingEnabled, + structuralSharing, }, assertTagType, config, @@ -158,9 +158,10 @@ export function buildSlice({ (substate) => { if (substate.requestId !== meta.requestId) return substate.status = QueryStatus.fulfilled - substate.data = isStructuralSharingEnabled - ? copyWithStructuralSharing(substate.data, payload) - : payload + substate.data = + structuralSharing && meta.arg.structuralSharing + ? copyWithStructuralSharing(substate.data, payload) + : payload delete substate.error substate.fulfilledTimeStamp = meta.fulfilledTimeStamp } diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index 89c330a7d0..b32756cf4c 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -196,14 +196,15 @@ export interface CreateApiOptions< * * Most apps should leave this setting on. The only time it can be a performance issue * is if an API returns extremely large amounts of data (e.g. 10,000 rows per request) and - * you're unable to paginate it. + * you're unable to paginate it. Even then, you should consider doing this at the endpoint + * or hook / initiate level instead for more granular control. * * For details of how this works, please see the below. When it is set to `false`, * every request will cause subscribed components to rerender, even when the data has not changed. * * @see https://redux-toolkit.js.org/api/other-exports#copywithstructuralsharing */ - isStructuralSharingEnabled?: boolean + structuralSharing?: boolean } export type CreateApi = { @@ -242,10 +243,7 @@ export type CreateApi = { export function buildCreateApi, ...Module[]]>( ...modules: Modules ): CreateApi { - return function baseCreateApi({ - isStructuralSharingEnabled = true, - ...options - }) { + return function baseCreateApi({ structuralSharing = true, ...options }) { const extractRehydrationInfo = defaultMemoize((action: AnyAction) => options.extractRehydrationInfo?.(action, { reducerPath: (options.reducerPath ?? 'api') as any, @@ -259,7 +257,7 @@ export function buildCreateApi, ...Module[]]>( refetchOnMountOrArgChange: false, refetchOnFocus: false, refetchOnReconnect: false, - isStructuralSharingEnabled, + structuralSharing, ...options, extractRehydrationInfo, tagTypes: [...(options.tagTypes || [])], @@ -276,7 +274,7 @@ export function buildCreateApi, ...Module[]]>( hasRehydrationInfo: defaultMemoize( (action) => extractRehydrationInfo(action) != null ), - isStructuralSharingEnabled, + structuralSharing, } const api = { diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index dc8ccc87c0..01f5530760 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -63,6 +63,7 @@ interface EndpointDefinitionWithQuery< meta: BaseQueryMeta, arg: QueryArg ): ResultType | Promise + structuralSharing?: boolean } interface EndpointDefinitionWithQueryFn< @@ -116,6 +117,7 @@ interface EndpointDefinitionWithQueryFn< ): MaybePromise>> query?: never transformResponse?: never + structuralSharing?: boolean } export type BaseEndpointDefinition< From e4c6be89d48139555665e9af7e7da3c58ed9f4c8 Mon Sep 17 00:00:00 2001 From: Matt Sutkowski Date: Mon, 24 Jan 2022 21:02:42 -0800 Subject: [PATCH 3/5] Add tests and fix things :tada: --- .../toolkit/src/query/core/buildInitiate.ts | 2 +- packages/toolkit/src/query/core/buildSlice.ts | 10 +++- .../toolkit/src/query/react/buildHooks.ts | 12 ++++ .../toolkit/src/query/tests/createApi.test.ts | 60 +++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index c6dc94ad6f..157124763f 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -265,7 +265,7 @@ Features like automatic cache collection, automatic refetching etc. will not be subscribe = true, forceRefetch, subscriptionOptions, - structuralSharing = true, + structuralSharing, } = {} ) => (dispatch, getState) => { diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index b295763de7..01f30bfb27 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -92,7 +92,7 @@ export function buildSlice({ apiUid, extractRehydrationInfo, hasRehydrationInfo, - structuralSharing, + structuralSharing: globalStructuralSharing, }, assertTagType, config, @@ -156,10 +156,16 @@ export function buildSlice({ draft, meta.arg.queryCacheKey, (substate) => { + const endpointStructuralSharing = + definitions[meta.arg.endpointName].structuralSharing + const argStructuralSharing = meta.arg.structuralSharing + if (substate.requestId !== meta.requestId) return substate.status = QueryStatus.fulfilled substate.data = - structuralSharing && meta.arg.structuralSharing + argStructuralSharing ?? + endpointStructuralSharing ?? + globalStructuralSharing ? copyWithStructuralSharing(substate.data, payload) : payload delete substate.error diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 15e2977937..3fe29a34f9 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -142,6 +142,16 @@ interface UseQuerySubscriptionOptions extends SubscriptionOptions { * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false. */ refetchOnMountOrArgChange?: boolean | number + /** + * Defaults to `true`. + * + * Most apps should leave this setting on. The only time it can be a performance issue + * is if an API returns extremely large amounts of data (e.g. 10,000 rows per request) and + * you're unable to paginate it. + * + * @see https://redux-toolkit.js.org/api/other-exports#copywithstructuralsharing + */ + structuralSharing?: boolean } /** @@ -611,6 +621,7 @@ export function buildHooks({ refetchOnMountOrArgChange, skip = false, pollingInterval = 0, + structuralSharing, } = {} ) => { const { initiate } = api.endpoints[name] as ApiEndpointQuery< @@ -668,6 +679,7 @@ export function buildHooks({ initiate(stableArg, { subscriptionOptions: stableSubscriptionOptions, forceRefetch: refetchOnMountOrArgChange, + structuralSharing, }) ) promiseRef.current = promise diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index 8ccc480657..485261d44e 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -17,6 +17,7 @@ import { } from './helpers' import { server } from './mocks/server' import { rest } from 'msw' +import * as utils from '../utils' const originalEnv = process.env.NODE_ENV beforeAll(() => void ((process.env as any).NODE_ENV = 'development')) @@ -763,3 +764,62 @@ test('providesTags and invalidatesTags can use baseQueryMeta', async () => { expect('request' in _meta! && 'response' in _meta!).toBe(true) }) + +describe('strucutralSharing flag behaviors', () => { + const mockCopyFn = jest.spyOn(utils, 'copyWithStructuralSharing') + + beforeEach(() => { + mockCopyFn.mockClear() + }) + + type SuccessResponse = { value: 'success' } + + const apiSuccessResponse: SuccessResponse = { value: 'success' } + + const api = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }), + tagTypes: ['success'], + endpoints: (build) => ({ + enabled: build.query({ + query: () => '/success', + }), + disabled: build.query({ + query: () => ({ url: '/success' }), + structuralSharing: false, + }), + }), + }) + + const storeRef = setupApiStore(api) + + it('enables structural sharing for query endpoints by default', async () => { + const result = await storeRef.store.dispatch( + api.endpoints.enabled.initiate() + ) + expect(mockCopyFn).toHaveBeenCalledTimes(1) + expect(result.data).toMatchObject(apiSuccessResponse) + }) + it('allows a query endpoint to opt-out of structural sharing', async () => { + const result = await storeRef.store.dispatch( + api.endpoints.disabled.initiate() + ) + expect(mockCopyFn).toHaveBeenCalledTimes(0) + expect(result.data).toMatchObject(apiSuccessResponse) + }) + it('allows initiate to override endpoint and global settings and disable at the call site level', async () => { + // global flag is enabled, endpoint is also enabled by default + const result = await storeRef.store.dispatch( + api.endpoints.enabled.initiate(undefined, { structuralSharing: false }) + ) + expect(mockCopyFn).toHaveBeenCalledTimes(0) + expect(result.data).toMatchObject(apiSuccessResponse) + }) + it('allows initiate to override the endpoint flag and enable sharing at the call site', async () => { + // global flag is enabled, endpoint is disabled + const result = await storeRef.store.dispatch( + api.endpoints.disabled.initiate(undefined, { structuralSharing: true }) + ) + expect(mockCopyFn).toHaveBeenCalledTimes(1) + expect(result.data).toMatchObject(apiSuccessResponse) + }) +}) From 738faa666bae275fde7cbac823733e94892ed973 Mon Sep 17 00:00:00 2001 From: Matt Sutkowski Date: Wed, 26 Jan 2022 19:00:42 -0800 Subject: [PATCH 4/5] Limit structuralSharing flag to endpoint definitions --- packages/toolkit/src/query/apiTypes.ts | 2 -- .../toolkit/src/query/core/buildInitiate.ts | 12 +--------- packages/toolkit/src/query/core/buildSlice.ts | 9 +------ packages/toolkit/src/query/createApi.ts | 18 +------------- .../toolkit/src/query/endpointDefinitions.ts | 24 +++++++++++++++++++ .../toolkit/src/query/react/buildHooks.ts | 12 ---------- .../toolkit/src/query/tests/createApi.test.ts | 16 ------------- 7 files changed, 27 insertions(+), 66 deletions(-) diff --git a/packages/toolkit/src/query/apiTypes.ts b/packages/toolkit/src/query/apiTypes.ts index 016580be10..3dafe46b02 100644 --- a/packages/toolkit/src/query/apiTypes.ts +++ b/packages/toolkit/src/query/apiTypes.ts @@ -46,7 +46,6 @@ export type Module = { | 'refetchOnFocus' | 'refetchOnReconnect' | 'tagTypes' - | 'structuralSharing' >, context: ApiContext ): { @@ -65,7 +64,6 @@ export interface ApiContext { action: AnyAction ) => CombinedState | undefined hasRehydrationInfo: (action: AnyAction) => boolean - structuralSharing: boolean } export type Api< diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 157124763f..2ecc8eb376 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -37,7 +37,6 @@ export interface StartQueryActionCreatorOptions { subscribe?: boolean forceRefetch?: boolean | number subscriptionOptions?: SubscriptionOptions - structuralSharing?: boolean } type StartQueryActionCreator< @@ -259,15 +258,7 @@ Features like automatic cache collection, automatic refetching etc. will not be endpointDefinition: QueryDefinition ) { const queryAction: StartQueryActionCreator = - ( - arg, - { - subscribe = true, - forceRefetch, - subscriptionOptions, - structuralSharing, - } = {} - ) => + (arg, { subscribe = true, forceRefetch, subscriptionOptions } = {}) => (dispatch, getState) => { const queryCacheKey = serializeQueryArgs({ queryArgs: arg, @@ -282,7 +273,6 @@ Features like automatic cache collection, automatic refetching etc. will not be endpointName, originalArgs: arg, queryCacheKey, - structuralSharing, }) const thunkResult = dispatch(thunk) middlewareWarning(getState) diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 01f30bfb27..8e1c9e05d1 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -92,7 +92,6 @@ export function buildSlice({ apiUid, extractRehydrationInfo, hasRehydrationInfo, - structuralSharing: globalStructuralSharing, }, assertTagType, config, @@ -156,16 +155,10 @@ export function buildSlice({ draft, meta.arg.queryCacheKey, (substate) => { - const endpointStructuralSharing = - definitions[meta.arg.endpointName].structuralSharing - const argStructuralSharing = meta.arg.structuralSharing - if (substate.requestId !== meta.requestId) return substate.status = QueryStatus.fulfilled substate.data = - argStructuralSharing ?? - endpointStructuralSharing ?? - globalStructuralSharing + definitions[meta.arg.endpointName].structuralSharing ?? true ? copyWithStructuralSharing(substate.data, payload) : payload delete substate.error diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index b32756cf4c..e70e02281b 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -191,20 +191,6 @@ export interface CreateApiOptions< NoInfer, NoInfer > - /** - * Defaults to `true`. - * - * Most apps should leave this setting on. The only time it can be a performance issue - * is if an API returns extremely large amounts of data (e.g. 10,000 rows per request) and - * you're unable to paginate it. Even then, you should consider doing this at the endpoint - * or hook / initiate level instead for more granular control. - * - * For details of how this works, please see the below. When it is set to `false`, - * every request will cause subscribed components to rerender, even when the data has not changed. - * - * @see https://redux-toolkit.js.org/api/other-exports#copywithstructuralsharing - */ - structuralSharing?: boolean } export type CreateApi = { @@ -243,7 +229,7 @@ export type CreateApi = { export function buildCreateApi, ...Module[]]>( ...modules: Modules ): CreateApi { - return function baseCreateApi({ structuralSharing = true, ...options }) { + return function baseCreateApi(options) { const extractRehydrationInfo = defaultMemoize((action: AnyAction) => options.extractRehydrationInfo?.(action, { reducerPath: (options.reducerPath ?? 'api') as any, @@ -257,7 +243,6 @@ export function buildCreateApi, ...Module[]]>( refetchOnMountOrArgChange: false, refetchOnFocus: false, refetchOnReconnect: false, - structuralSharing, ...options, extractRehydrationInfo, tagTypes: [...(options.tagTypes || [])], @@ -274,7 +259,6 @@ export function buildCreateApi, ...Module[]]>( hasRehydrationInfo: defaultMemoize( (action) => extractRehydrationInfo(action) != null ), - structuralSharing, } const api = { diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index 01f5530760..c10c6627eb 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -63,6 +63,18 @@ interface EndpointDefinitionWithQuery< meta: BaseQueryMeta, arg: QueryArg ): ResultType | Promise + /** + * Defaults to `true`. + * + * Most apps should leave this setting on. The only time it can be a performance issue + * is if an API returns extremely large amounts of data (e.g. 10,000 rows per request) and + * you're unable to paginate it. + * + * For details of how this works, please see the below. When it is set to `false`, + * every request will cause subscribed components to rerender, even when the data has not changed. + * + * @see https://redux-toolkit.js.org/api/other-exports#copywithstructuralsharing + */ structuralSharing?: boolean } @@ -117,6 +129,18 @@ interface EndpointDefinitionWithQueryFn< ): MaybePromise>> query?: never transformResponse?: never + /** + * Defaults to `true`. + * + * Most apps should leave this setting on. The only time it can be a performance issue + * is if an API returns extremely large amounts of data (e.g. 10,000 rows per request) and + * you're unable to paginate it. + * + * For details of how this works, please see the below. When it is set to `false`, + * every request will cause subscribed components to rerender, even when the data has not changed. + * + * @see https://redux-toolkit.js.org/api/other-exports#copywithstructuralsharing + */ structuralSharing?: boolean } diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 3fe29a34f9..15e2977937 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -142,16 +142,6 @@ interface UseQuerySubscriptionOptions extends SubscriptionOptions { * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false. */ refetchOnMountOrArgChange?: boolean | number - /** - * Defaults to `true`. - * - * Most apps should leave this setting on. The only time it can be a performance issue - * is if an API returns extremely large amounts of data (e.g. 10,000 rows per request) and - * you're unable to paginate it. - * - * @see https://redux-toolkit.js.org/api/other-exports#copywithstructuralsharing - */ - structuralSharing?: boolean } /** @@ -621,7 +611,6 @@ export function buildHooks({ refetchOnMountOrArgChange, skip = false, pollingInterval = 0, - structuralSharing, } = {} ) => { const { initiate } = api.endpoints[name] as ApiEndpointQuery< @@ -679,7 +668,6 @@ export function buildHooks({ initiate(stableArg, { subscriptionOptions: stableSubscriptionOptions, forceRefetch: refetchOnMountOrArgChange, - structuralSharing, }) ) promiseRef.current = promise diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index 485261d44e..1b9a3e7431 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -806,20 +806,4 @@ describe('strucutralSharing flag behaviors', () => { expect(mockCopyFn).toHaveBeenCalledTimes(0) expect(result.data).toMatchObject(apiSuccessResponse) }) - it('allows initiate to override endpoint and global settings and disable at the call site level', async () => { - // global flag is enabled, endpoint is also enabled by default - const result = await storeRef.store.dispatch( - api.endpoints.enabled.initiate(undefined, { structuralSharing: false }) - ) - expect(mockCopyFn).toHaveBeenCalledTimes(0) - expect(result.data).toMatchObject(apiSuccessResponse) - }) - it('allows initiate to override the endpoint flag and enable sharing at the call site', async () => { - // global flag is enabled, endpoint is disabled - const result = await storeRef.store.dispatch( - api.endpoints.disabled.initiate(undefined, { structuralSharing: true }) - ) - expect(mockCopyFn).toHaveBeenCalledTimes(1) - expect(result.data).toMatchObject(apiSuccessResponse) - }) }) From e7820d16dec9bec29ee0b53396a2cd0f7efa760b Mon Sep 17 00:00:00 2001 From: Matt Sutkowski Date: Sun, 30 Jan 2022 16:27:34 -0800 Subject: [PATCH 5/5] Rewrite tests to expect on reference equality --- .../toolkit/src/query/tests/createApi.test.ts | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index 1b9a3e7431..a81c22381b 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -17,7 +17,6 @@ import { } from './helpers' import { server } from './mocks/server' import { rest } from 'msw' -import * as utils from '../utils' const originalEnv = process.env.NODE_ENV beforeAll(() => void ((process.env as any).NODE_ENV = 'development')) @@ -765,17 +764,9 @@ test('providesTags and invalidatesTags can use baseQueryMeta', async () => { expect('request' in _meta! && 'response' in _meta!).toBe(true) }) -describe('strucutralSharing flag behaviors', () => { - const mockCopyFn = jest.spyOn(utils, 'copyWithStructuralSharing') - - beforeEach(() => { - mockCopyFn.mockClear() - }) - +describe('structuralSharing flag behaviors', () => { type SuccessResponse = { value: 'success' } - const apiSuccessResponse: SuccessResponse = { value: 'success' } - const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }), tagTypes: ['success'], @@ -793,17 +784,30 @@ describe('strucutralSharing flag behaviors', () => { const storeRef = setupApiStore(api) it('enables structural sharing for query endpoints by default', async () => { - const result = await storeRef.store.dispatch( - api.endpoints.enabled.initiate() + await storeRef.store.dispatch(api.endpoints.enabled.initiate()) + const firstRef = api.endpoints.enabled.select()(storeRef.store.getState()) + + await storeRef.store.dispatch( + api.endpoints.enabled.initiate(undefined, { forceRefetch: true }) ) - expect(mockCopyFn).toHaveBeenCalledTimes(1) - expect(result.data).toMatchObject(apiSuccessResponse) + + const secondRef = api.endpoints.enabled.select()(storeRef.store.getState()) + + expect(firstRef.requestId).not.toEqual(secondRef.requestId) + expect(firstRef.data === secondRef.data).toBeTruthy() }) + it('allows a query endpoint to opt-out of structural sharing', async () => { - const result = await storeRef.store.dispatch( - api.endpoints.disabled.initiate() + await storeRef.store.dispatch(api.endpoints.disabled.initiate()) + const firstRef = api.endpoints.disabled.select()(storeRef.store.getState()) + + await storeRef.store.dispatch( + api.endpoints.disabled.initiate(undefined, { forceRefetch: true }) ) - expect(mockCopyFn).toHaveBeenCalledTimes(0) - expect(result.data).toMatchObject(apiSuccessResponse) + + const secondRef = api.endpoints.disabled.select()(storeRef.store.getState()) + + expect(firstRef.requestId).not.toEqual(secondRef.requestId) + expect(firstRef.data === secondRef.data).toBeFalsy() }) })