Skip to content

Commit 42b115f

Browse files
committed
Add tests and fix things 🎉
1 parent a9e0561 commit 42b115f

File tree

6 files changed

+101
-4
lines changed

6 files changed

+101
-4
lines changed

examples/query/react/pagination/src/app/services/posts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const api = createApi({
2424
endpoints: (build) => ({
2525
listPosts: build.query<ListResponse<Post>, number | void>({
2626
query: (page = 1) => `posts?page=${page}`,
27+
structuralSharing: false,
2728
}),
2829
}),
2930
})

examples/query/react/pagination/src/features/posts/PostsManager.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ const getColorForStatus = (status: Post['status']) => {
2929

3030
const PostList = () => {
3131
const [page, setPage] = React.useState(1)
32-
const { data: posts, isLoading, isFetching } = useListPostsQuery(page)
32+
const {
33+
data: posts,
34+
isLoading,
35+
isFetching,
36+
refetch,
37+
} = useListPostsQuery(page)
3338

3439
if (isLoading) {
3540
return <div>Loading</div>
@@ -42,6 +47,19 @@ const PostList = () => {
4247
return (
4348
<Box>
4449
<HStack spacing="14px">
50+
<Button
51+
onClick={async () => {
52+
try {
53+
const result = refetch()
54+
55+
console.log({ result })
56+
} catch (err) {
57+
console.error(err)
58+
}
59+
}}
60+
>
61+
REFETCH!
62+
</Button>
4563
<Button
4664
onClick={() => setPage((prev) => prev - 1)}
4765
isLoading={isFetching}

packages/toolkit/src/query/core/buildInitiate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ Features like automatic cache collection, automatic refetching etc. will not be
265265
subscribe = true,
266266
forceRefetch,
267267
subscriptionOptions,
268-
structuralSharing = true,
268+
structuralSharing,
269269
} = {}
270270
) =>
271271
(dispatch, getState) => {

packages/toolkit/src/query/core/buildSlice.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export function buildSlice({
9292
apiUid,
9393
extractRehydrationInfo,
9494
hasRehydrationInfo,
95-
structuralSharing,
95+
structuralSharing: globalStructuralSharing,
9696
},
9797
assertTagType,
9898
config,
@@ -156,10 +156,16 @@ export function buildSlice({
156156
draft,
157157
meta.arg.queryCacheKey,
158158
(substate) => {
159+
const endpointStructuralSharing =
160+
definitions[meta.arg.endpointName].structuralSharing
161+
const argStructuralSharing = meta.arg.structuralSharing
162+
159163
if (substate.requestId !== meta.requestId) return
160164
substate.status = QueryStatus.fulfilled
161165
substate.data =
162-
structuralSharing && meta.arg.structuralSharing
166+
argStructuralSharing ??
167+
endpointStructuralSharing ??
168+
globalStructuralSharing
163169
? copyWithStructuralSharing(substate.data, payload)
164170
: payload
165171
delete substate.error

packages/toolkit/src/query/react/buildHooks.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,16 @@ interface UseQuerySubscriptionOptions extends SubscriptionOptions {
142142
* If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false.
143143
*/
144144
refetchOnMountOrArgChange?: boolean | number
145+
/**
146+
* Defaults to `true`.
147+
*
148+
* Most apps should leave this setting on. The only time it can be a performance issue
149+
* is if an API returns extremely large amounts of data (e.g. 10,000 rows per request) and
150+
* you're unable to paginate it.
151+
*
152+
* @see https://redux-toolkit.js.org/api/other-exports#copywithstructuralsharing
153+
*/
154+
structuralSharing?: boolean
145155
}
146156

147157
/**
@@ -611,6 +621,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
611621
refetchOnMountOrArgChange,
612622
skip = false,
613623
pollingInterval = 0,
624+
structuralSharing,
614625
} = {}
615626
) => {
616627
const { initiate } = api.endpoints[name] as ApiEndpointQuery<
@@ -668,6 +679,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
668679
initiate(stableArg, {
669680
subscriptionOptions: stableSubscriptionOptions,
670681
forceRefetch: refetchOnMountOrArgChange,
682+
structuralSharing,
671683
})
672684
)
673685
promiseRef.current = promise

packages/toolkit/src/query/tests/createApi.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from './helpers'
1818
import { server } from './mocks/server'
1919
import { rest } from 'msw'
20+
import * as utils from '../utils'
2021

2122
const originalEnv = process.env.NODE_ENV
2223
beforeAll(() => void ((process.env as any).NODE_ENV = 'development'))
@@ -763,3 +764,62 @@ test('providesTags and invalidatesTags can use baseQueryMeta', async () => {
763764

764765
expect('request' in _meta! && 'response' in _meta!).toBe(true)
765766
})
767+
768+
describe('strucutralSharing flag behaviors', () => {
769+
const mockCopyFn = jest.spyOn(utils, 'copyWithStructuralSharing')
770+
771+
beforeEach(() => {
772+
mockCopyFn.mockClear()
773+
})
774+
775+
type SuccessResponse = { value: 'success' }
776+
777+
const apiSuccessResponse: SuccessResponse = { value: 'success' }
778+
779+
const api = createApi({
780+
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
781+
tagTypes: ['success'],
782+
endpoints: (build) => ({
783+
enabled: build.query<SuccessResponse, void>({
784+
query: () => '/success',
785+
}),
786+
disabled: build.query<SuccessResponse, void>({
787+
query: () => ({ url: '/success' }),
788+
structuralSharing: false,
789+
}),
790+
}),
791+
})
792+
793+
const storeRef = setupApiStore(api)
794+
795+
it('enables structural sharing for query endpoints by default', async () => {
796+
const result = await storeRef.store.dispatch(
797+
api.endpoints.enabled.initiate()
798+
)
799+
expect(mockCopyFn).toHaveBeenCalledTimes(1)
800+
expect(result.data).toMatchObject(apiSuccessResponse)
801+
})
802+
it('allows a query endpoint to opt-out of structural sharing', async () => {
803+
const result = await storeRef.store.dispatch(
804+
api.endpoints.disabled.initiate()
805+
)
806+
expect(mockCopyFn).toHaveBeenCalledTimes(0)
807+
expect(result.data).toMatchObject(apiSuccessResponse)
808+
})
809+
it('allows initiate to override endpoint and global settings and disable at the call site level', async () => {
810+
// global flag is enabled, endpoint is also enabled by default
811+
const result = await storeRef.store.dispatch(
812+
api.endpoints.enabled.initiate(undefined, { structuralSharing: false })
813+
)
814+
expect(mockCopyFn).toHaveBeenCalledTimes(0)
815+
expect(result.data).toMatchObject(apiSuccessResponse)
816+
})
817+
it('allows initiate to override the endpoint flag and enable sharing at the call site', async () => {
818+
// global flag is enabled, endpoint is disabled
819+
const result = await storeRef.store.dispatch(
820+
api.endpoints.disabled.initiate(undefined, { structuralSharing: true })
821+
)
822+
expect(mockCopyFn).toHaveBeenCalledTimes(1)
823+
expect(result.data).toMatchObject(apiSuccessResponse)
824+
})
825+
})

0 commit comments

Comments
 (0)