Skip to content

RTKQ: configurable structuralSharing on endpoints/queries/createApi #1954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
})
})