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
Improved React 18 support #1962
Changes from all commits
6178e7f
9d7d78f
b70c624
e03e1e2
892b7fa
15e2ea9
544a009
c277b5d
42dc3a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import SWRConfig from './utils/config-context' | ||
import * as revalidateEvents from './constants' | ||
|
||
export { SWRConfig, revalidateEvents } | ||
|
||
export { initCache } from './utils/cache' | ||
export { defaultConfig, cache, mutate, compare } from './utils/config' | ||
export * from './utils/env' | ||
export { SWRGlobalState } from './utils/global-state' | ||
export { stableHash } from './utils/hash' | ||
export * from './utils/helper' | ||
export { mergeConfigs } from './utils/merge-config' | ||
export { internalMutate } from './utils/mutate' | ||
export { normalize } from './utils/normalize-args' | ||
export { withArgs } from './utils/resolve-args' | ||
export { serialize } from './utils/serialize' | ||
export { useStateWithDeps } from './utils/state' | ||
export { subscribeCallback } from './utils/subscribe-key' | ||
export { getTimestamp } from './utils/timestamp' | ||
export { useSWRConfig } from './utils/use-swr-config' | ||
export { preset, defaultConfigOptions } from './utils/web-preset' | ||
export { withMiddleware } from './utils/with-middleware' | ||
|
||
export * from './types' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "swr-internal", | ||
"version": "0.0.1", | ||
"main": "./dist/index.js", | ||
"module": "./dist/index.esm.js", | ||
"types": "./dist/_internal", | ||
"exports": "./dist/index.mjs", | ||
"peerDependencies": { | ||
"react": "*" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"extends": "../tsconfig.json", | ||
"compilerOptions": { | ||
"rootDir": "..", | ||
"outDir": "./dist" | ||
}, | ||
"include": ["./**/*.ts"] | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,6 +1,14 @@ | ||||||||||||||
import * as revalidateEvents from './constants' | ||||||||||||||
import { defaultConfig } from './utils/config' | ||||||||||||||
|
||||||||||||||
export type GlobalState = [ | ||||||||||||||
Record<string, RevalidateCallback[]>, // EVENT_REVALIDATORS | ||||||||||||||
Record<string, [number, number]>, // MUTATION: [ts, end_ts] | ||||||||||||||
Record<string, [any, number]>, // FETCH: [data, ts] | ||||||||||||||
ScopedMutator, // Mutator | ||||||||||||||
(key: string, value: any, prev: any) => void, // Setter | ||||||||||||||
(key: string, callback: (current: any, prev: any) => void) => () => void // Subscriber | ||||||||||||||
] | ||||||||||||||
export type FetcherResponse<Data = unknown> = Data | Promise<Data> | ||||||||||||||
export type BareFetcher<Data = unknown> = ( | ||||||||||||||
...args: any[] | ||||||||||||||
|
@@ -144,7 +152,9 @@ export type MutatorCallback<Data = any> = ( | |||||||||||||
|
||||||||||||||
export type MutatorOptions<Data = any> = { | ||||||||||||||
revalidate?: boolean | ||||||||||||||
populateCache?: boolean | ((result: any, currentData?: Data) => Data) | ||||||||||||||
populateCache?: | ||||||||||||||
| boolean | ||||||||||||||
| ((result: any, currentData: Data | undefined) => Data) | ||||||||||||||
optimisticData?: Data | ((currentData?: Data) => Data) | ||||||||||||||
rollbackOnError?: boolean | ||||||||||||||
} | ||||||||||||||
|
@@ -255,12 +265,22 @@ export type RevalidateCallback = <K extends RevalidateEvent>( | |||||||||||||
type: K | ||||||||||||||
) => RevalidateCallbackReturnType[K] | ||||||||||||||
|
||||||||||||||
export type StateUpdateCallback<Data = any, Error = any> = ( | ||||||||||||||
state: State<Data, Error> | ||||||||||||||
) => void | ||||||||||||||
|
||||||||||||||
export interface Cache<Data = any, Error = any> { | ||||||||||||||
get(key: Key): State<Data, Error> | undefined | ||||||||||||||
set(key: Key, value: State<Data, Error>): void | ||||||||||||||
export interface Cache<Data = any> { | ||||||||||||||
get(key: Key): CacheValue<Data> | undefined | ||||||||||||||
set(key: Key, value: Data): void | ||||||||||||||
delete(key: Key): void | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
export interface CacheValue<Data = any, Error = any> { | ||||||||||||||
data?: Data | ||||||||||||||
error?: Error | ||||||||||||||
isValidating?: boolean | ||||||||||||||
isLoading?: boolean | ||||||||||||||
} | ||||||||||||||
Comment on lines
+274
to
+279
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Lines 177 to 182 in 9a0328f
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two types should be merged |
||||||||||||||
|
||||||||||||||
export interface StateDependencies { | ||||||||||||||
data?: boolean | ||||||||||||||
error?: boolean | ||||||||||||||
isValidating?: boolean | ||||||||||||||
isLoading?: boolean | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { defaultConfigOptions } from './web-preset' | ||
import { IS_SERVER } from './env' | ||
import { UNDEFINED, mergeObjects, noop } from './helper' | ||
import { internalMutate } from './mutate' | ||
import { SWRGlobalState } from './global-state' | ||
import * as revalidateEvents from '../constants' | ||
|
||
import { | ||
Cache, | ||
ScopedMutator, | ||
RevalidateEvent, | ||
RevalidateCallback, | ||
ProviderConfiguration, | ||
GlobalState | ||
} from '../types' | ||
|
||
const revalidateAllKeys = ( | ||
revalidators: Record<string, RevalidateCallback[]>, | ||
type: RevalidateEvent | ||
) => { | ||
for (const key in revalidators) { | ||
if (revalidators[key][0]) revalidators[key][0](type) | ||
} | ||
} | ||
|
||
export const initCache = <Data = any>( | ||
provider: Cache<Data>, | ||
options?: Partial<ProviderConfiguration> | ||
): | ||
| [Cache<Data>, ScopedMutator<Data>, () => void, () => void] | ||
| [Cache<Data>, ScopedMutator<Data>] | ||
| undefined => { | ||
// The global state for a specific provider will be used to deduplicate | ||
// requests and store listeners. As well as a mutate function that bound to | ||
// the cache. | ||
|
||
// Provider's global state might be already initialized. Let's try to get the | ||
// global state associated with the provider first. | ||
if (!SWRGlobalState.has(provider)) { | ||
const opts = mergeObjects(defaultConfigOptions, options) | ||
|
||
// If there's no global state bound to the provider, create a new one with the | ||
// new mutate function. | ||
const EVENT_REVALIDATORS = {} | ||
const mutate = internalMutate.bind( | ||
UNDEFINED, | ||
provider | ||
) as ScopedMutator<Data> | ||
let unmount = noop | ||
|
||
const subscriptions: Record<string, ((current: any, prev: any) => void)[]> = | ||
{} | ||
const subscribe = ( | ||
key: string, | ||
callback: (current: any, prev: any) => void | ||
) => { | ||
const subs = subscriptions[key] || [] | ||
subscriptions[key] = subs | ||
|
||
subs.push(callback) | ||
return () => { | ||
subs.splice(subs.indexOf(callback), 1) | ||
} | ||
} | ||
const setter = (key: string, value: any, prev: any) => { | ||
provider.set(key, value) | ||
const subs = subscriptions[key] | ||
if (subs) { | ||
for (let i = subs.length; i--; ) { | ||
subs[i](value, prev) | ||
} | ||
} | ||
} | ||
|
||
const initProvider = () => { | ||
if (!SWRGlobalState.has(provider)) { | ||
// Update the state if it's new, or the provider has been extended. | ||
SWRGlobalState.set(provider, [ | ||
EVENT_REVALIDATORS, | ||
{}, | ||
{}, | ||
mutate, | ||
setter, | ||
subscribe | ||
]) | ||
if (!IS_SERVER) { | ||
// When listening to the native events for auto revalidations, | ||
// we intentionally put a delay (setTimeout) here to make sure they are | ||
// fired after immediate JavaScript executions, which can possibly be | ||
// React's state updates. | ||
// This avoids some unnecessary revalidations such as | ||
// https://github.com/vercel/swr/issues/1680. | ||
const releaseFocus = opts.initFocus( | ||
setTimeout.bind( | ||
UNDEFINED, | ||
revalidateAllKeys.bind( | ||
UNDEFINED, | ||
EVENT_REVALIDATORS, | ||
revalidateEvents.FOCUS_EVENT | ||
) | ||
) | ||
) | ||
const releaseReconnect = opts.initReconnect( | ||
setTimeout.bind( | ||
UNDEFINED, | ||
revalidateAllKeys.bind( | ||
UNDEFINED, | ||
EVENT_REVALIDATORS, | ||
revalidateEvents.RECONNECT_EVENT | ||
) | ||
) | ||
) | ||
unmount = () => { | ||
releaseFocus && releaseFocus() | ||
releaseReconnect && releaseReconnect() | ||
// When un-mounting, we need to remove the cache provider from the state | ||
// storage too because it's a side-effect. Otherwise when re-mounting we | ||
// will not re-register those event listeners. | ||
SWRGlobalState.delete(provider) | ||
} | ||
} | ||
} | ||
} | ||
initProvider() | ||
|
||
// This is a new provider, we need to initialize it and setup DOM events | ||
// listeners for `focus` and `reconnect` actions. | ||
|
||
// We might want to inject an extra layer on top of `provider` in the future, | ||
// such as key serialization, auto GC, etc. | ||
// For now, it's just a `Map` interface without any modifications. | ||
return [provider, mutate, initProvider, unmount] | ||
} | ||
|
||
return [provider, (SWRGlobalState.get(provider) as GlobalState)[3]] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { Cache, GlobalState } from '../types' | ||
|
||
// Global state used to deduplicate requests and store listeners | ||
export const SWRGlobalState = new WeakMap<Cache, GlobalState>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type of
set
was updated in https://github.com/vercel/swr/pull/1961/files#r872245634 - seems like a regression.