Skip to content

Commit a44511e

Browse files
phryneasmsutkowski
andauthored
use useSerializedStableValue for value comparison (#1533)
Co-authored-by: Matt Sutkowski <[email protected]>
1 parent 18ef51d commit a44511e

File tree

5 files changed

+65
-13
lines changed

5 files changed

+65
-13
lines changed

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ import type {
3131
QueryActionCreatorResult,
3232
MutationActionCreatorResult,
3333
} from '@reduxjs/toolkit/dist/query/core/buildInitiate'
34+
import type { SerializeQueryArgs } from '@reduxjs/toolkit/dist/query/defaultSerializeQueryArgs'
3435
import { shallowEqual } from 'react-redux'
35-
import type { Api } from '@reduxjs/toolkit/dist/query/apiTypes'
36+
import type { Api, ApiContext } from '@reduxjs/toolkit/dist/query/apiTypes'
3637
import type {
3738
Id,
3839
NoInfer,
@@ -45,9 +46,10 @@ import type {
4546
PrefetchOptions,
4647
} from '@reduxjs/toolkit/dist/query/core/module'
4748
import type { ReactHooksModuleOptions } from './module'
48-
import { useShallowStableValue } from './useShallowStableValue'
49+
import { useStableQueryArgs } from './useSerializedStableValue'
4950
import type { UninitializedValue } from './constants'
5051
import { UNINITIALIZED_VALUE } from './constants'
52+
import { useShallowStableValue } from './useShallowStableValue'
5153

5254
// Copy-pasted from React-Redux
5355
export const useIsomorphicLayoutEffect =
@@ -482,9 +484,13 @@ type GenericPrefetchThunk = (
482484
export function buildHooks<Definitions extends EndpointDefinitions>({
483485
api,
484486
moduleOptions: { batch, useDispatch, useSelector, useStore },
487+
serializeQueryArgs,
488+
context,
485489
}: {
486490
api: Api<any, Definitions, any, any, CoreModule>
487491
moduleOptions: Required<ReactHooksModuleOptions>
492+
serializeQueryArgs: SerializeQueryArgs<any>
493+
context: ApiContext<Definitions>
488494
}) {
489495
return { buildQueryHooks, buildMutationHook, usePrefetch }
490496

@@ -523,7 +529,12 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
523529
Definitions
524530
>
525531
const dispatch = useDispatch<ThunkDispatch<any, any, AnyAction>>()
526-
const stableArg = useShallowStableValue(skip ? skipToken : arg)
532+
const stableArg = useStableQueryArgs(
533+
skip ? skipToken : arg,
534+
serializeQueryArgs,
535+
context.endpointDefinitions[name],
536+
name
537+
)
527538
const stableSubscriptionOptions = useShallowStableValue({
528539
refetchOnReconnect,
529540
refetchOnFocus,
@@ -658,7 +669,12 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
658669
QueryDefinition<any, any, any, any, any>,
659670
Definitions
660671
>
661-
const stableArg = useShallowStableValue(skip ? skipToken : arg)
672+
const stableArg = useStableQueryArgs(
673+
skip ? skipToken : arg,
674+
serializeQueryArgs,
675+
context.endpointDefinitions[name],
676+
name
677+
)
662678

663679
const lastValue = useRef<any>()
664680

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export const reactHooksModule = ({
109109
useStore = rrUseStore,
110110
}: ReactHooksModuleOptions = {}): Module<ReactHooksModule> => ({
111111
name: reactHooksModuleName,
112-
init(api, options, context) {
112+
init(api, { serializeQueryArgs }, context) {
113113
const anyApi = api as any as Api<
114114
any,
115115
Record<string, any>,
@@ -120,6 +120,8 @@ export const reactHooksModule = ({
120120
const { buildQueryHooks, buildMutationHook, usePrefetch } = buildHooks({
121121
api,
122122
moduleOptions: { batch, useDispatch, useSelector, useStore },
123+
serializeQueryArgs,
124+
context,
123125
})
124126
safeAssign(anyApi, { usePrefetch })
125127
safeAssign(context, { batch })
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useEffect, useRef, useMemo } from 'react'
2+
import type { SerializeQueryArgs } from '@reduxjs/toolkit/dist/query/defaultSerializeQueryArgs'
3+
import type { EndpointDefinition } from '@reduxjs/toolkit/dist/query/endpointDefinitions'
4+
5+
export function useStableQueryArgs<T>(
6+
queryArgs: T,
7+
serialize: SerializeQueryArgs<any>,
8+
endpointDefinition: EndpointDefinition<any, any, any, any>,
9+
endpointName: string
10+
) {
11+
const incoming = useMemo(
12+
() => ({
13+
queryArgs,
14+
serialized:
15+
typeof queryArgs == 'object'
16+
? serialize({ queryArgs, endpointDefinition, endpointName })
17+
: queryArgs,
18+
}),
19+
[queryArgs, serialize, endpointDefinition, endpointName]
20+
)
21+
const cache = useRef(incoming)
22+
useEffect(() => {
23+
if (cache.current.serialized !== incoming.serialized) {
24+
cache.current = incoming
25+
}
26+
}, [incoming])
27+
28+
return cache.current.serialized === incoming.serialized
29+
? cache.current.queryArgs
30+
: queryArgs
31+
}

packages/toolkit/src/query/tests/buildHooks.test.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -494,18 +494,19 @@ describe('hooks tests', () => {
494494

495495
let { unmount } = render(<User />, { wrapper: storeRef.wrapper })
496496

497+
expect(screen.getByTestId('isFetching').textContent).toBe('false')
498+
497499
// skipped queries do nothing by default, so we need to toggle that to get a cached result
498500
fireEvent.click(screen.getByText('change skip'))
499501

500502
await waitFor(() =>
501503
expect(screen.getByTestId('isFetching').textContent).toBe('true')
502504
)
503-
await waitFor(() =>
504-
expect(screen.getByTestId('isFetching').textContent).toBe('false')
505-
)
506-
await waitFor(() =>
505+
506+
await waitFor(() => {
507507
expect(screen.getByTestId('amount').textContent).toBe('1')
508-
)
508+
expect(screen.getByTestId('isFetching').textContent).toBe('false')
509+
})
509510

510511
unmount()
511512

packages/toolkit/src/query/tests/buildThunks.test.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,14 @@ describe('re-triggering behavior on arg change', () => {
156156
}
157157
})
158158

159-
test('re-trigger every time on deeper value changes', async () => {
159+
test('re-triggers every time on deeper value changes', async () => {
160+
const name = 'Tim'
161+
160162
const { result, rerender, waitForNextUpdate } = renderHook(
161163
(props) => getUser.useQuery(props),
162164
{
163165
wrapper: withProvider(store),
164-
initialProps: { person: { name: 'Tim' } },
166+
initialProps: { person: { name } },
165167
}
166168
)
167169

@@ -171,7 +173,7 @@ describe('re-triggering behavior on arg change', () => {
171173
expect(spy).toHaveBeenCalledTimes(1)
172174

173175
for (let x = 1; x < 3; x++) {
174-
rerender({ person: { name: 'Tim' } })
176+
rerender({ person: { name: name + x } })
175177
// @ts-ignore
176178
while (result.current.status === 'pending') {
177179
await waitForNextUpdate()

0 commit comments

Comments
 (0)