Skip to content

Commit

Permalink
fix: improve selectFromResult memoization (#4029)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people committed Jan 1, 2024
1 parent 8e0942e commit 924b384
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 3 deletions.
7 changes: 6 additions & 1 deletion packages/toolkit/src/query/react/buildHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,12 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
(_: ApiRootState, lastResult: any) => lastResult,
(_: ApiRootState) => stableArg,
],
queryStatePreSelector
queryStatePreSelector,
{
memoizeOptions: {
resultEqualityCheck: shallowEqual,
},
}
),
[select, stableArg]
)
Expand Down
62 changes: 60 additions & 2 deletions packages/toolkit/src/query/tests/buildHooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { server } from './mocks/server'
import type { UnknownAction } from 'redux'
import type { SubscriptionOptions } from '@reduxjs/toolkit/dist/query/core/apiState'
import type { SerializedError } from '@reduxjs/toolkit'
import { createListenerMiddleware, configureStore } from '@reduxjs/toolkit'
import { createListenerMiddleware, configureStore, createSlice } from '@reduxjs/toolkit'
import { delay } from '../../utils'
import type { SubscriptionSelectors } from '../core/buildMiddleware/types'
import { countObjectKeys } from '../utils/countObjectKeys'
Expand Down Expand Up @@ -2052,7 +2052,19 @@ describe('hooks with createApi defaults set', () => {
}),
})

const storeRef = setupApiStore(api)
const counterSlice = createSlice({
name: "counter",
initialState: { count: 0 },
reducers: {
increment(state) {
state.count++
}
}
})

const storeRef = setupApiStore(api, {
counter: counterSlice.reducer,
})

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

test('useQuery with selectFromResult option does not update when unrelated data in the store changes', async () => {
function Posts() {
const { posts } = api.endpoints.getPosts.useQuery(undefined, {
selectFromResult: ({ data }) => ({
// Intentionally use an unstable reference to force a rerender
posts: data?.filter((post) => post.name.includes('post')),
}),
})

getRenderCount = useRenderCounter()

return (
<div>
{posts?.map((post) => (
<div key={post.id}>{post.name}</div>
))}
</div>
)
}

function CounterButton() {
return (
<div
data-testid="incrementButton"
onClick={() => storeRef.store.dispatch(counterSlice.actions.increment())}
>
Increment Count
</div>
)
}

render(
<div>
<Posts />
<CounterButton />
</div>,
{ wrapper: storeRef.wrapper }
)

await waitFor(() => expect(getRenderCount()).toBe(2))

const incrementBtn = screen.getByTestId('incrementButton')
fireEvent.click(incrementBtn)
expect(getRenderCount()).toBe(2)
})

test('useQuery with selectFromResult option has a type error if the result is not an object', async () => {
function SelectedPost() {
const _res1 = api.endpoints.getPosts.useQuery(undefined, {
Expand Down

0 comments on commit 924b384

Please sign in to comment.