Skip to content

Commit df5fc9c

Browse files
shudingnevilm-lt
authored andcommitted
feat: Add populateCache option to mutate (vercel#1729)
* add populateCache option to mutate * rename type and export it
1 parent 0112c5b commit df5fc9c

File tree

5 files changed

+79
-31
lines changed

5 files changed

+79
-31
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export {
2121
BareFetcher,
2222
Fetcher,
2323
MutatorCallback,
24+
MutatorOptions,
2425
Middleware,
2526
Arguments
2627
} from './types'

src/types.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,19 @@ export type MutatorCallback<Data = any> = (
142142
currentValue?: Data
143143
) => Promise<undefined | Data> | undefined | Data
144144

145+
export type MutatorOptions = {
146+
revalidate?: boolean
147+
populateCache?: boolean
148+
}
149+
145150
export type Broadcaster<Data = any, Error = any> = (
146151
cache: Cache<Data>,
147152
key: string,
148153
data: Data,
149154
error?: Error,
150155
isValidating?: boolean,
151-
shouldRevalidate?: boolean
156+
revalidate?: boolean,
157+
populateCache?: boolean
152158
) => Promise<Data>
153159

154160
export type State<Data, Error> = {
@@ -161,27 +167,27 @@ export type Mutator<Data = any> = (
161167
cache: Cache,
162168
key: Key,
163169
data?: Data | Promise<Data> | MutatorCallback<Data>,
164-
shouldRevalidate?: boolean
170+
opts?: boolean | MutatorOptions
165171
) => Promise<Data | undefined>
166172

167173
export interface ScopedMutator<Data = any> {
168174
/** This is used for bound mutator */
169175
(
170176
key: Key,
171177
data?: Data | Promise<Data> | MutatorCallback<Data>,
172-
shouldRevalidate?: boolean
178+
opts?: boolean | MutatorOptions
173179
): Promise<Data | undefined>
174180
/** This is used for global mutator */
175181
<T = any>(
176182
key: Key,
177183
data?: T | Promise<T> | MutatorCallback<T>,
178-
shouldRevalidate?: boolean
184+
opts?: boolean | MutatorOptions
179185
): Promise<T | undefined>
180186
}
181187

182188
export type KeyedMutator<Data> = (
183189
data?: Data | Promise<Data> | MutatorCallback<Data>,
184-
shouldRevalidate?: boolean
190+
opts?: boolean | MutatorOptions
185191
) => Promise<Data | undefined>
186192

187193
// Public types

src/utils/broadcast-state.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ export const broadcastState: Broadcaster = (
88
data,
99
error,
1010
isValidating,
11-
revalidate
11+
revalidate,
12+
populateCache = true
1213
) => {
1314
const [
1415
EVENT_REVALIDATORS,
@@ -21,9 +22,11 @@ export const broadcastState: Broadcaster = (
2122
const revalidators = EVENT_REVALIDATORS[key]
2223
const updaters = STATE_UPDATERS[key] || []
2324

24-
// Always update states of all hooks.
25-
for (let i = 0; i < updaters.length; ++i) {
26-
updaters[i](data, error, isValidating)
25+
// Cache was populated, update states of all hooks.
26+
if (populateCache && updaters) {
27+
for (let i = 0; i < updaters.length; ++i) {
28+
updaters[i](data, error, isValidating)
29+
}
2730
}
2831

2932
// If we also need to revalidate, only do it for the first hook.

src/utils/mutate.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,26 @@ import { SWRGlobalState, GlobalState } from './global-state'
44
import { broadcastState } from './broadcast-state'
55
import { getTimestamp } from './timestamp'
66

7-
import { Key, Cache, MutatorCallback } from '../types'
7+
import { Key, Cache, MutatorCallback, MutatorOptions } from '../types'
88

99
export const internalMutate = async <Data>(
1010
...args: [
1111
Cache,
1212
Key,
1313
undefined | Data | Promise<Data | undefined> | MutatorCallback<Data>,
14-
undefined | boolean
14+
undefined | boolean | MutatorOptions
1515
]
1616
) => {
17-
const [cache, _key] = args
17+
const [cache, _key, _data, _opts] = args
18+
19+
// When passing as a boolean, it's explicitily used to disable/enable
20+
// revalidation.
21+
const options =
22+
typeof _opts === 'boolean' ? { revalidate: _opts } : _opts || {}
23+
1824
// Fallback to `true` if it's not explicitly set to `false`
19-
const revalidate = args[3] !== false
20-
let _data = args[2]
25+
const revalidate = options.revalidate !== false
26+
const populateCache = options.populateCache !== false
2127

2228
// Serilaize key
2329
const [key, , keyErr] = serialize(_key)
@@ -36,31 +42,33 @@ export const internalMutate = async <Data>(
3642
cache.get(key),
3743
cache.get(keyErr),
3844
UNDEFINED,
39-
revalidate
45+
revalidate,
46+
populateCache
4047
)
4148
}
4249

43-
let data: any, error: unknown
50+
let data: any = _data
51+
let error: unknown
4452

4553
// Update global timestamps.
4654
const beforeMutationTs = (MUTATION_TS[key] = getTimestamp())
4755
MUTATION_END_TS[key] = 0
4856

49-
if (isFunction(_data)) {
50-
// `_data` is a function, call it passing current cache value.
57+
if (isFunction(data)) {
58+
// `data` is a function, call it passing current cache value.
5159
try {
52-
_data = (_data as MutatorCallback<Data>)(cache.get(key))
60+
data = (data as MutatorCallback<Data>)(cache.get(key))
5361
} catch (err) {
5462
// If it throws an error synchronously, we shouldn't update the cache.
5563
error = err
5664
}
5765
}
5866

59-
// `_data` is a promise/thenable, resolve the final data first.
60-
if (_data && isFunction((_data as Promise<Data>).then)) {
67+
// `data` is a promise/thenable, resolve the final data first.
68+
if (data && isFunction((data as Promise<Data>).then)) {
6169
// This means that the mutation is async, we need to check timestamps to
6270
// avoid race conditions.
63-
data = await (_data as Promise<Data>).catch(err => {
71+
data = await (data as Promise<Data>).catch(err => {
6472
error = err
6573
})
6674

@@ -71,16 +79,16 @@ export const internalMutate = async <Data>(
7179
if (error) throw error
7280
return data
7381
}
74-
} else {
75-
data = _data
7682
}
7783

78-
// Only update cached data if there's no error. Data can be `undefined` here.
79-
if (!error) {
80-
cache.set(key, data)
84+
if (populateCache) {
85+
if (!error) {
86+
// Only update cached data if there's no error. Data can be `undefined` here.
87+
cache.set(key, data)
88+
}
89+
// Always update or reset the error.
90+
cache.set(keyErr, error)
8191
}
82-
// Always update or reset the error.
83-
cache.set(keyErr, error)
8492

8593
// Reset the timestamp to mark the mutation has ended.
8694
MUTATION_END_TS[key] = getTimestamp()
@@ -92,10 +100,11 @@ export const internalMutate = async <Data>(
92100
data,
93101
error,
94102
UNDEFINED,
95-
revalidate
103+
revalidate,
104+
populateCache
96105
)
97106

98107
// Throw error or return data
99108
if (error) throw error
100-
return res
109+
return populateCache ? res : data
101110
}

test/use-swr-local-mutation.test.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,4 +982,33 @@ describe('useSWR - local mutation', () => {
982982
await sleep(300)
983983
await screen.findByText('success')
984984
})
985+
986+
it('should not update the cache when `populateCache` is disabled', async () => {
987+
const key = createKey()
988+
function Page() {
989+
const { data, mutate } = useSWR(key, () => 'foo')
990+
return (
991+
<>
992+
<div>data: {String(data)}</div>
993+
<button
994+
onClick={() =>
995+
mutate('bar', {
996+
revalidate: false,
997+
populateCache: false
998+
})
999+
}
1000+
>
1001+
mutate
1002+
</button>
1003+
</>
1004+
)
1005+
}
1006+
1007+
renderWithConfig(<Page />)
1008+
await screen.findByText('data: foo')
1009+
1010+
fireEvent.click(screen.getByText('mutate'))
1011+
await sleep(30)
1012+
await screen.findByText('data: foo')
1013+
})
9851014
})

0 commit comments

Comments
 (0)