Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mutate filter #1989

Merged
merged 22 commits into from
Jun 26, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion _internal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export type RevalidateCallback = <K extends RevalidateEvent>(
) => RevalidateCallbackReturnType[K]

export interface Cache<Data = any> {
values(): IterableIterator<State<Data, any>>
keys(): IterableIterator<string>
get(key: Key): State<Data> | undefined
set(key: Key, value: State<Data>): void
delete(key: Key): void
Expand Down
13 changes: 5 additions & 8 deletions _internal/utils/helper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SWRGlobalState } from './global-state'
import type { Key, Cache, State, GlobalState } from '../types'

const EMPTY_CACHE = {}
export const noop = () => {}

// Using noop() as the undefined value as undefined can possibly be replaced
Expand All @@ -18,10 +19,7 @@ export const isFunction = <
v: unknown
): v is T => typeof v == 'function'
export const mergeObjects = (a: any, b: any) => OBJECT.assign({}, a, b)
export const isEmptyCacheState = (v: State<any, any>): boolean => {
const keys = Object.keys(v)
return keys.length === 1 && keys[0] === 'key'
}
export const isEmptyCache = (v: any): boolean => v === EMPTY_CACHE

const STR_UNDEFINED = 'undefined'

Expand All @@ -36,14 +34,13 @@ export const createCacheHelper = <Data = any, T = State<Data, any>>(
key: Key
) => {
const state = SWRGlobalState.get(cache) as GlobalState
const emptyCache = { key }
return [
// Getter
() => (cache.get(key) || emptyCache) as T,
() => (cache.get(key) || EMPTY_CACHE) as T,
// Setter
(info: T) => {
const prev = cache.get(key) || emptyCache
state[5](key as string, mergeObjects(prev, info), prev)
const prev = cache.get(key)
state[5](key as string, mergeObjects(prev, info), prev || EMPTY_CACHE)
},
// Subscriber
state[6]
Expand Down
14 changes: 9 additions & 5 deletions _internal/utils/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,20 @@ export async function internalMutate<Data>(
const revalidate = options.revalidate !== false
const rollbackOnError = options.rollbackOnError !== false

// If 2nd arg is key filter, return the mutation results of filtered keys
// If the second argument is a key filter, return the mutation results for all
// filtered keys.
if (isFunction(_key)) {
const keyFilter = _key
const matchedKeys: Key[] = []
for (const state of cache.values()) {
if (keyFilter(state.key)) matchedKeys.push(state.key)
for (const key of cache.keys()) {
if (keyFilter((cache.get(key) as { _k: any })._k)) {
huozhi marked this conversation as resolved.
Show resolved Hide resolved
matchedKeys.push(key)
}
}
return await Promise.all(matchedKeys.map(mutateByKey))
return Promise.all(matchedKeys.map(mutateByKey))
}
return await mutateByKey(_key)

return mutateByKey(_key)

async function mutateByKey(_k: Key): Promise<Data | undefined> {
// Serialize key
Expand Down
20 changes: 12 additions & 8 deletions core/use-swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
OBJECT,
isFunction,
createCacheHelper,
isEmptyCacheState,
isEmptyCache,
SWRConfig as ConfigProvider,
withArgs,
subscribeCallback,
Expand Down Expand Up @@ -87,16 +87,18 @@ export const useSWRHandler = <Data = any, Error = any>(

// Refs to keep the key and config.
const keyRef = useRef(key)
const originKeyRef = useRef(_key)
const fetcherRef = useRef(fetcher)
const configRef = useRef(config)
const getConfig = () => configRef.current
const isActive = () => getConfig().isVisible() && getConfig().isOnline()

const [getCache, setCache, subscribeCache] = createCacheHelper<Data>(
cache,
key
)
const [getCache, setCache, subscribeCache] = createCacheHelper<
Data,
State<Data, any> & {
// The original key arguments.
_k?: Key
}
>(cache, key)

const stateDependencies = useRef<StateDependencies>({}).current

Expand All @@ -118,7 +120,7 @@ export const useSWRHandler = <Data = any, Error = any>(
return true
})()
if (!shouldStartRequest) return snapshot
if (isEmptyCacheState(snapshot)) {
if (isEmptyCache(snapshot)) {
return {
isValidating: true,
isLoading: true
Expand Down Expand Up @@ -497,9 +499,11 @@ export const useSWRHandler = <Data = any, Error = any>(
// Mark the component as mounted and update corresponding refs.
unmountedRef.current = false
keyRef.current = key
originKeyRef.current = _key
initialMountedRef.current = true

// Keep the original key in the cache.
setCache({ _k: fnArg })

// Trigger a revalidation.
if (shouldDoInitialRevalidation) {
if (isUndefined(data) || IS_SERVER) {
Expand Down
1 change: 0 additions & 1 deletion test/use-swr-cache.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ describe('useSWR - cache provider', () => {
provider: parentCache_ => {
parentCache = parentCache_
return {
values: () => parentCache.values(),
set: (k, v) => parentCache_.set(k, v),
get: k => {
// We append `-extended` to the value returned by the parent cache.
Expand Down
34 changes: 34 additions & 0 deletions test/use-swr-local-mutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1608,4 +1608,38 @@ describe('useSWR - local mutation', () => {

expect(mutationOneResults).toEqual([undefined])
})

it('should pass the original key to the key filter', async () => {
const key = createKey()
const keys = []

function Page() {
useSWR([key, 'first'])
useSWR([key, 'second'])
useSWR(key)
const { mutate } = useSWRConfig()
return (
<span
data-testid="mutator-filter-all"
onClick={() => {
mutate(
k => {
keys.push(k)
return false
},
undefined,
false
)
}}
/>
)
}
renderWithConfig(<Page />)

// add and mutate `first` and `second`
fireEvent.click(screen.getByTestId('mutator-filter-all'))
await nextTick()

expect(keys).toEqual([[key, 'first'], [key, 'second'], key])
})
})