Skip to content

Commit

Permalink
RTKQ: configurable structuralSharing on endpoints/queries/createApi (
Browse files Browse the repository at this point in the history
…#1954)

* Allow for endpoints to opt out of structural sharing
* Rewrite tests to expect on reference equality
  • Loading branch information
msutkowski authored Feb 1, 2022
1 parent d000b3b commit 8c23cd3
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 1 deletion.
5 changes: 4 additions & 1 deletion packages/toolkit/src/query/core/buildSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ export function buildSlice({
(substate) => {
if (substate.requestId !== meta.requestId) return
substate.status = QueryStatus.fulfilled
substate.data = copyWithStructuralSharing(substate.data, payload)
substate.data =
definitions[meta.arg.endpointName].structuralSharing ?? true
? copyWithStructuralSharing(substate.data, payload)
: payload
delete substate.error
substate.fulfilledTimeStamp = meta.fulfilledTimeStamp
}
Expand Down
1 change: 1 addition & 0 deletions packages/toolkit/src/query/createApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>(
reducerPath: (options.reducerPath ?? 'api') as any,
})
)

const optionsWithDefaults = {
reducerPath: 'api',
serializeQueryArgs: defaultSerializeQueryArgs,
Expand Down
26 changes: 26 additions & 0 deletions packages/toolkit/src/query/endpointDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ interface EndpointDefinitionWithQuery<
meta: BaseQueryMeta<BaseQuery>,
arg: QueryArg
): ResultType | Promise<ResultType>
/**
* 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
}

interface EndpointDefinitionWithQueryFn<
Expand Down Expand Up @@ -116,6 +129,19 @@ interface EndpointDefinitionWithQueryFn<
): MaybePromise<QueryReturnValue<ResultType, BaseQueryError<BaseQuery>>>
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
}

export type BaseEndpointDefinition<
Expand Down
48 changes: 48 additions & 0 deletions packages/toolkit/src/query/tests/createApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -763,3 +763,51 @@ test('providesTags and invalidatesTags can use baseQueryMeta', async () => {

expect('request' in _meta! && 'response' in _meta!).toBe(true)
})

describe('structuralSharing flag behaviors', () => {
type SuccessResponse = { value: 'success' }

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['success'],
endpoints: (build) => ({
enabled: build.query<SuccessResponse, void>({
query: () => '/success',
}),
disabled: build.query<SuccessResponse, void>({
query: () => ({ url: '/success' }),
structuralSharing: false,
}),
}),
})

const storeRef = setupApiStore(api)

it('enables structural sharing for query endpoints by default', async () => {
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 })
)

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 () => {
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 })
)

const secondRef = api.endpoints.disabled.select()(storeRef.store.getState())

expect(firstRef.requestId).not.toEqual(secondRef.requestId)
expect(firstRef.data === secondRef.data).toBeFalsy()
})
})

0 comments on commit 8c23cd3

Please sign in to comment.