Skip to content

Commit 4e7e8ab

Browse files
authored
perf: optimize useSWRConfig with useMemo to maintain stable reference (#4110)
1 parent 9dc2c4b commit 4e7e8ab

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { useContext } from 'react'
1+
import { useContext, useMemo } from 'react'
22
import { defaultConfig } from './config'
33
import { SWRConfigContext } from './config-context'
44
import { mergeObjects } from './shared'
55
import type { FullConfiguration } from '../types'
66

77
export const useSWRConfig = (): FullConfiguration => {
8-
return mergeObjects(defaultConfig, useContext(SWRConfigContext))
8+
const parentConfig = useContext(SWRConfigContext)
9+
const mergedConfig = useMemo(
10+
() => mergeObjects(defaultConfig, parentConfig),
11+
[parentConfig]
12+
)
13+
return mergedConfig
914
}

test/use-swr-context-config.test.tsx

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
import { act, screen } from '@testing-library/react'
2-
import useSWR, { mutate } from 'swr'
1+
import { act, render, screen } from '@testing-library/react'
2+
import useSWR, {
3+
mutate,
4+
SWRConfig,
5+
type SWRConfiguration,
6+
useSWRConfig
7+
} from 'swr'
38
import { createKey, createResponse, renderWithGlobalCache } from './utils'
9+
import { useCallback, useEffect, useState } from 'react'
410

511
describe('useSWR - context configs', () => {
612
it('mutate before mount should not block rerender', async () => {
@@ -25,3 +31,46 @@ describe('useSWR - context configs', () => {
2531
await screen.findByText('data')
2632
})
2733
})
34+
35+
describe('useSWRConfig hook maintains stable reference across re-renders', () => {
36+
it('should maintain the same swrConfig reference when counter updates', () => {
37+
const parentConfig: SWRConfiguration = {
38+
revalidateOnMount: true,
39+
revalidateIfStale: false,
40+
revalidateOnFocus: false,
41+
revalidateOnReconnect: false
42+
}
43+
const counterButtonText = 'counter + 1'
44+
let useSWRConfigReferenceChangedTimes = 0
45+
function Page() {
46+
return (
47+
<SWRConfig value={parentConfig}>
48+
<ChildComponent />
49+
</SWRConfig>
50+
)
51+
}
52+
function ChildComponent() {
53+
const swrConfig = useSWRConfig()
54+
const [, setCounter] = useState(0)
55+
const counterAddOne = useCallback(
56+
() => setCounter(prev => prev + 1),
57+
[setCounter]
58+
)
59+
useEffect(() => {
60+
useSWRConfigReferenceChangedTimes += 1
61+
}, [swrConfig])
62+
return <button onClick={counterAddOne}>{counterButtonText}</button>
63+
}
64+
render(<Page />)
65+
act(() => {
66+
screen.getByText(counterButtonText).click()
67+
})
68+
act(() => {
69+
screen.getByText(counterButtonText).click()
70+
})
71+
act(() => {
72+
screen.getByText(counterButtonText).click()
73+
})
74+
expect(useSWRConfigReferenceChangedTimes).toBe(1)
75+
})
76+
})

0 commit comments

Comments
 (0)