Skip to content

Commit 5395cd2

Browse files
committed
custom cache
1 parent 9f37400 commit 5395cd2

File tree

4 files changed

+118
-4
lines changed

4 files changed

+118
-4
lines changed

src/cache.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1-
import { Cache as CacheType, Key, CacheListener } from './types'
1+
import { Cache as CacheType, Key, CacheListener, CacheProvider } from './types'
22
import hash from './libs/hash'
33

4+
const defaultNoopProvider: CacheProvider = {
5+
initialData: {},
6+
// eslint-disable-next-line @typescript-eslint/no-empty-function
7+
set() {},
8+
// eslint-disable-next-line @typescript-eslint/no-empty-function
9+
clear() {}
10+
}
411
export default class Cache implements CacheType {
512
private __cache: Map<string, any>
613
private __listeners: CacheListener[]
14+
private __provider: CacheProvider
715

816
constructor(initialData: any = {}) {
917
this.__cache = new Map(Object.entries(initialData))
1018
this.__listeners = []
19+
this.__provider = defaultNoopProvider
20+
}
21+
22+
setProvider(provider: CacheProvider) {
23+
this.__provider = Object.assign({}, defaultNoopProvider, provider)
1124
}
1225

1326
get(key: Key): any {
@@ -19,6 +32,7 @@ export default class Cache implements CacheType {
1932
const [_key] = this.serializeKey(key)
2033
this.__cache.set(_key, value)
2134
this.notify()
35+
this.__provider.set(_key, value)
2236
}
2337

2438
keys() {
@@ -33,12 +47,14 @@ export default class Cache implements CacheType {
3347
clear() {
3448
this.__cache.clear()
3549
this.notify()
50+
this.__provider.clear()
3651
}
3752

3853
delete(key: Key) {
3954
const [_key] = this.serializeKey(key)
4055
this.__cache.delete(_key)
4156
this.notify()
57+
this.__provider.set(_key, undefined)
4258
}
4359

4460
// TODO: introduce namespace for the cache

src/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,14 @@ export interface Cache {
214214
serializeKey(key: Key): [string, any, string, string]
215215
subscribe(listener: CacheListener): () => void
216216
}
217+
218+
export type CacheProvider = {
219+
initialData?: any
220+
set?(key: Key, value: any): void
221+
clear?(): void
222+
}
223+
224+
export type SWRContext = SWRConfiguration & {
225+
cache?: CacheProvider
226+
}
227+

src/use-swr.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
createElement,
23
useCallback,
34
useContext,
45
useEffect,
@@ -22,7 +23,8 @@ import {
2223
SWRResponse,
2324
RevalidatorOptions,
2425
Trigger,
25-
Updater
26+
Updater,
27+
SWRContext
2628
} from './types'
2729

2830
const IS_SERVER =
@@ -816,7 +818,24 @@ function useSWR<Data = any, Error = any>(
816818
return memoizedState
817819
}
818820

819-
const SWRConfig = SWRConfigContext.Provider
821+
function SWRConfig(props: React.ProviderProps<SWRContext>) {
822+
const onceRef = useRef(false)
823+
if (!onceRef.current) {
824+
const {
825+
value: { cache: cacheProvider }
826+
} = props
827+
if (cacheProvider) {
828+
cache.setProvider(cacheProvider)
829+
Object.entries(cacheProvider.initialData || {}).forEach(
830+
([key, value]) => {
831+
cache.set(key, value)
832+
}
833+
)
834+
}
835+
onceRef.current = true
836+
}
837+
return createElement(SWRConfigContext.Provider, props)
838+
}
820839

821840
export { trigger, mutate, SWRConfig }
822841
export default useSWR

test/use-swr-cache.test.tsx

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { act, render, screen } from '@testing-library/react'
22
import React from 'react'
3-
import useSWR, { mutate, cache } from '../src'
3+
import useSWR, { mutate, cache, SWRConfig } from '../src'
44
import Cache from '../src/cache'
55

66
describe('useSWR - cache', () => {
@@ -60,3 +60,71 @@ describe('useSWR - cache', () => {
6060
expect(listener).toHaveBeenCalledTimes(1)
6161
})
6262
})
63+
64+
describe('useSWR - UNSTABLE custom cache', () => {
65+
it('should consume initial cache state from cache.initialData', async () => {
66+
const key = 'custom-cache-1'
67+
const cacheProvider = {
68+
initialData: { [key]: 1 }
69+
}
70+
71+
function Section() {
72+
const { data } = useSWR(key)
73+
return <div>data: {data}</div>
74+
}
75+
76+
function Page() {
77+
return (
78+
<SWRConfig value={{ cache: cacheProvider }}>
79+
<Section />
80+
</SWRConfig>
81+
)
82+
}
83+
84+
render(<Page />)
85+
expect(cache.get(key)).toBe(1)
86+
await screen.findByText('data: 1')
87+
})
88+
89+
it('should be able to get cache changes through cache.set', async () => {
90+
const getKey = _key => 'custom-cache-2-' + _key
91+
const fetcher = _key => 'res:' + _key
92+
const key1 = getKey('1')
93+
const key2 = getKey('2')
94+
95+
const globalStorage = {}
96+
const cacheProvider = {
97+
set(_key, _value) {
98+
globalStorage[_key] = _value
99+
}
100+
}
101+
102+
function Section() {
103+
const { data } = useSWR(key1, fetcher)
104+
105+
return <div>data: {data}</div>
106+
}
107+
108+
function Page() {
109+
return (
110+
<SWRConfig value={{ cache: cacheProvider }}>
111+
<Section />
112+
</SWRConfig>
113+
)
114+
}
115+
116+
render(<Page />)
117+
118+
const [, , keyErr1, isValidatingKey1] = cache.serializeKey(key1)
119+
const [, , keyErr2] = cache.serializeKey(key2)
120+
await act(() => mutate(key2, fetcher(key2)))
121+
122+
expect(globalStorage).toEqual({
123+
[key1]: fetcher(key1),
124+
[key2]: fetcher(key2),
125+
[keyErr1]: undefined,
126+
[keyErr2]: undefined,
127+
[isValidatingKey1]: false
128+
})
129+
})
130+
})

0 commit comments

Comments
 (0)