Skip to content

Commit 924b384

Browse files
fix: improve selectFromResult memoization (#4029)
* use a slice instead of a hand-written reducer * add test for unnecessary selectFromResult renders * improve memoization of selectFromResult * streamline test a bit --------- Co-authored-by: Lenz Weber-Tronic <mail@lenzw.de> Co-authored-by: Jeremiah Montoya <jeremiah.montoya@albert.com>
1 parent 8e0942e commit 924b384

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,12 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
915915
(_: ApiRootState, lastResult: any) => lastResult,
916916
(_: ApiRootState) => stableArg,
917917
],
918-
queryStatePreSelector
918+
queryStatePreSelector,
919+
{
920+
memoizeOptions: {
921+
resultEqualityCheck: shallowEqual,
922+
},
923+
}
919924
),
920925
[select, stableArg]
921926
)

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

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { server } from './mocks/server'
3535
import type { UnknownAction } from 'redux'
3636
import type { SubscriptionOptions } from '@reduxjs/toolkit/dist/query/core/apiState'
3737
import type { SerializedError } from '@reduxjs/toolkit'
38-
import { createListenerMiddleware, configureStore } from '@reduxjs/toolkit'
38+
import { createListenerMiddleware, configureStore, createSlice } from '@reduxjs/toolkit'
3939
import { delay } from '../../utils'
4040
import type { SubscriptionSelectors } from '../core/buildMiddleware/types'
4141
import { countObjectKeys } from '../utils/countObjectKeys'
@@ -2052,7 +2052,19 @@ describe('hooks with createApi defaults set', () => {
20522052
}),
20532053
})
20542054

2055-
const storeRef = setupApiStore(api)
2055+
const counterSlice = createSlice({
2056+
name: "counter",
2057+
initialState: { count: 0 },
2058+
reducers: {
2059+
increment(state) {
2060+
state.count++
2061+
}
2062+
}
2063+
})
2064+
2065+
const storeRef = setupApiStore(api, {
2066+
counter: counterSlice.reducer,
2067+
})
20562068

20572069
expectExactType(api.useGetPostsQuery)(api.endpoints.getPosts.useQuery)
20582070
expectExactType(api.useUpdatePostMutation)(
@@ -2317,6 +2329,52 @@ describe('hooks with createApi defaults set', () => {
23172329
await waitFor(() => expect(getRenderCount()).toBe(3))
23182330
})
23192331

2332+
test('useQuery with selectFromResult option does not update when unrelated data in the store changes', async () => {
2333+
function Posts() {
2334+
const { posts } = api.endpoints.getPosts.useQuery(undefined, {
2335+
selectFromResult: ({ data }) => ({
2336+
// Intentionally use an unstable reference to force a rerender
2337+
posts: data?.filter((post) => post.name.includes('post')),
2338+
}),
2339+
})
2340+
2341+
getRenderCount = useRenderCounter()
2342+
2343+
return (
2344+
<div>
2345+
{posts?.map((post) => (
2346+
<div key={post.id}>{post.name}</div>
2347+
))}
2348+
</div>
2349+
)
2350+
}
2351+
2352+
function CounterButton() {
2353+
return (
2354+
<div
2355+
data-testid="incrementButton"
2356+
onClick={() => storeRef.store.dispatch(counterSlice.actions.increment())}
2357+
>
2358+
Increment Count
2359+
</div>
2360+
)
2361+
}
2362+
2363+
render(
2364+
<div>
2365+
<Posts />
2366+
<CounterButton />
2367+
</div>,
2368+
{ wrapper: storeRef.wrapper }
2369+
)
2370+
2371+
await waitFor(() => expect(getRenderCount()).toBe(2))
2372+
2373+
const incrementBtn = screen.getByTestId('incrementButton')
2374+
fireEvent.click(incrementBtn)
2375+
expect(getRenderCount()).toBe(2)
2376+
})
2377+
23202378
test('useQuery with selectFromResult option has a type error if the result is not an object', async () => {
23212379
function SelectedPost() {
23222380
const _res1 = api.endpoints.getPosts.useQuery(undefined, {

0 commit comments

Comments
 (0)