Skip to content

Code structure refactoring #1303

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

Merged
merged 5 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- run: yarn install
- run: yarn types:check
- run: yarn lint
- run: yarn build:core
- run: yarn build
- run: yarn test
env:
CI: true
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export interface RevalidatorOptions {
}

export type Revalidator = (
revalidateOpts: RevalidatorOptions
revalidateOpts?: RevalidatorOptions
) => Promise<boolean>

export interface Cache<Data = any> {
Expand Down
156 changes: 50 additions & 106 deletions src/use-swr.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useCallback, useRef, useDebugValue } from 'react'
import defaultConfig from './utils/config'
import { provider as defaultProvider } from './utils/web-preset'
import { wrapCache } from './utils/cache'
import { wrapCache, SWRGlobalState } from './utils/cache'
import { IS_SERVER, rAF, useIsomorphicLayoutEffect } from './utils/env'
import { serialize } from './utils/serialize'
import { isUndefined, UNDEFINED } from './utils/helper'
Expand Down Expand Up @@ -30,40 +29,6 @@ type Revalidator = (...args: any[]) => void
// Generate strictly increasing timestamps.
let __timestamp = 0

// Global state used to deduplicate requests and store listeners
const SWRGlobalState = new WeakMap<Cache, any>()
const getGlobalState = (cache: Cache) => {
if (!SWRGlobalState.has(cache)) {
SWRGlobalState.set(cache, [{}, {}, {}, {}, {}, {}, {}])
}
return SWRGlobalState.get(cache) as [
Record<string, Revalidator[]>, // FOCUS_REVALIDATORS
Record<string, Revalidator[]>, // RECONNECT_REVALIDATORS
Record<string, Updater[]>, // CACHE_REVALIDATORS
Record<string, number>, // MUTATION_TS
Record<string, number>, // MUTATION_END_TS
Record<string, any>, // CONCURRENT_PROMISES
Record<string, number> // CONCURRENT_PROMISES_TS
]
}

function setupGlobalEvents(cache: Cache, _opts: Partial<ProviderOptions> = {}) {
if (IS_SERVER) return
const opts = { ...defaultProvider, ..._opts }
const [FOCUS_REVALIDATORS, RECONNECT_REVALIDATORS] = getGlobalState(cache)
const revalidate = (revalidators: Record<string, Revalidator[]>) => {
for (const key in revalidators) {
if (revalidators[key][0]) revalidators[key][0]()
}
}

opts.setupOnFocus(() => revalidate(FOCUS_REVALIDATORS))
opts.setupOnReconnect(() => revalidate(RECONNECT_REVALIDATORS))
}

// Setup DOM events listeners for `focus` and `reconnect` actions
setupGlobalEvents(defaultConfig.cache)

const broadcastState: Broadcaster = (
cache: Cache,
key,
Expand All @@ -72,7 +37,7 @@ const broadcastState: Broadcaster = (
isValidating,
shouldRevalidate = false
) => {
const [, , CACHE_REVALIDATORS] = getGlobalState(cache)
const [, , CACHE_REVALIDATORS] = SWRGlobalState.get(cache)!
const updaters = CACHE_REVALIDATORS[key]
const promises = []
if (updaters) {
Expand All @@ -94,7 +59,7 @@ async function internalMutate<Data = any>(
const [key, , keyErr] = serialize(_key)
if (!key) return UNDEFINED

const [, , , MUTATION_TS, MUTATION_END_TS] = getGlobalState(cache)
const [, , , MUTATION_TS, MUTATION_END_TS] = SWRGlobalState.get(cache)!

// if there is no new data to update, let's just revalidate the key
if (isUndefined(_data)) {
Expand All @@ -108,18 +73,14 @@ async function internalMutate<Data = any>(
)
}

// update global timestamps
MUTATION_TS[key] = ++__timestamp
MUTATION_END_TS[key] = 0

// track timestamps before await asynchronously
const beforeMutationTs = MUTATION_TS[key]

let data: any, error: unknown
let isAsyncMutation = false

// Update global timestamps.
const beforeMutationTs = (MUTATION_TS[key] = ++__timestamp)
MUTATION_END_TS[key] = 0

if (typeof _data === 'function') {
// `_data` is a function, call it passing current cache value
// `_data` is a function, call it passing current cache value.
try {
_data = (_data as MutatorCallback<Data>)(cache.get(key))
} catch (err) {
Expand All @@ -130,8 +91,7 @@ async function internalMutate<Data = any>(
}

if (_data && typeof (_data as Promise<Data>).then === 'function') {
// `_data` is a promise
isAsyncMutation = true
// `_data` is a promise/thenable, resolve the final data.
try {
data = await _data
} catch (err) {
Expand All @@ -141,32 +101,25 @@ async function internalMutate<Data = any>(
data = _data
}

const shouldAbort = (): boolean | void => {
// check if other mutations have occurred since we've started this mutation
if (beforeMutationTs !== MUTATION_TS[key]) {
if (error) throw error
return true
}
}
// Check if other mutations have occurred since we've started this mutation.
const shouldAbort = beforeMutationTs !== MUTATION_TS[key]

// If there's a race we don't update cache or broadcast change, just return the data
if (shouldAbort()) return data
// If there's a race we don't update cache or broadcast change, just return the data.
if (shouldAbort) {
if (error) throw error
return data
}

if (!isUndefined(data)) {
// update cached data
cache.set(key, data)
}
// Always update or reset the error
// Always update or reset the error.
cache.set(keyErr, error)

// Reset the timestamp to mark the mutation has ended
MUTATION_END_TS[key] = ++__timestamp

if (!isAsyncMutation) {
// We skip broadcasting if there's another mutation happened synchronously
if (shouldAbort()) return data
}

// Update existing SWR Hooks' internal states:
return broadcastState(
cache,
Expand Down Expand Up @@ -223,6 +176,7 @@ export function useSWRHandler<Data = any, Error = any>(
refreshWhenHidden,
refreshWhenOffline
} = config

const [
FOCUS_REVALIDATORS,
RECONNECT_REVALIDATORS,
Expand All @@ -231,7 +185,7 @@ export function useSWRHandler<Data = any, Error = any>(
MUTATION_END_TS,
CONCURRENT_PROMISES,
CONCURRENT_PROMISES_TS
] = getGlobalState(cache)
] = SWRGlobalState.get(cache)!

// `key` is the identifier of the SWR `data` state.
// `keyErr` and `keyValidating` are identifiers of `error` and `isValidating`
Expand All @@ -248,11 +202,8 @@ export function useSWRHandler<Data = any, Error = any>(
const configRef = useRef(config)

// Get the current state that SWR should return.
const resolveData = () => {
const cachedData = cache.get(key)
return isUndefined(cachedData) ? initialData : cachedData
}
const data = resolveData()
const cachedData = cache.get(key)
const data = isUndefined(cachedData) ? initialData : cachedData
const error = cache.get(keyErr)

// A revalidation must be triggered when mounted if:
Expand All @@ -278,10 +229,7 @@ export function useSWRHandler<Data = any, Error = any>(
}
const isValidating = resolveValidating()

const [stateRef, stateDependenciesRef, setState] = useStateWithDeps<
Data,
Error
>(
const [stateRef, stateDependencies, setState] = useStateWithDeps<Data, Error>(
{
data,
error,
Expand All @@ -293,14 +241,15 @@ export function useSWRHandler<Data = any, Error = any>(
// The revalidation function is a carefully crafted wrapper of the original
// `fetcher`, to correctly handle the many edge cases.
const revalidate = useCallback(
async (revalidateOpts: RevalidatorOptions = {}): Promise<boolean> => {
async (revalidateOpts?: RevalidatorOptions): Promise<boolean> => {
if (!key || !fn || unmountedRef.current || configRef.current.isPaused()) {
return false
}

const { retryCount, dedupe } = revalidateOpts

let newData: Data
let startAt: number
let loading = true
const { retryCount, dedupe } = revalidateOpts || {}
const shouldDeduping = !isUndefined(CONCURRENT_PROMISES[key]) && dedupe

// Do unmount check for callbacks:
Expand Down Expand Up @@ -329,9 +278,6 @@ export function useSWRHandler<Data = any, Error = any>(
)
}

let newData: Data
let startAt: number

if (shouldDeduping) {
// There's already an ongoing request, this one needs to be
// deduplicated.
Expand Down Expand Up @@ -362,8 +308,9 @@ export function useSWRHandler<Data = any, Error = any>(

// trigger the success event,
// only do this for the original request.
if (isCallbackSafe())
if (isCallbackSafe()) {
configRef.current.onSuccess(newData, key, config)
}
}

// if there're other ongoing request(s), started after the current one,
Expand Down Expand Up @@ -486,6 +433,16 @@ export function useSWRHandler<Data = any, Error = any>(
[key]
)

// `mutate`, but bound to the current key.
const boundMutate: SWRResponse<Data, Error>['mutate'] = useCallback(
(newData, shouldRevalidate) => {
return internalMutate(cache, keyRef.current, newData, shouldRevalidate)
},
// `cache` isn't allowed to change during the lifecycle
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)

// Always update config.
useIsomorphicLayoutEffect(() => {
configRef.current = config
Expand Down Expand Up @@ -525,13 +482,13 @@ export function useSWRHandler<Data = any, Error = any>(
}
}

const isVisible = () =>
const isActive = () =>
configRef.current.isDocumentVisible() && configRef.current.isOnline()

// Add event listeners.
let pending = false
const onFocus = () => {
if (configRef.current.revalidateOnFocus && !pending && isVisible()) {
if (configRef.current.revalidateOnFocus && !pending && isActive()) {
pending = true
softRevalidate()
setTimeout(
Expand All @@ -542,7 +499,7 @@ export function useSWRHandler<Data = any, Error = any>(
}

const onReconnect = () => {
if (configRef.current.revalidateOnReconnect && isVisible()) {
if (configRef.current.revalidateOnReconnect && isActive()) {
softRevalidate()
}
}
Expand Down Expand Up @@ -625,6 +582,9 @@ export function useSWRHandler<Data = any, Error = any>(
}
}, [refreshInterval, refreshWhenHidden, refreshWhenOffline, revalidate])

// Display debug info in React DevTools.
useDebugValue(data)

// In Suspense mode, we can't return the empty `data` state.
// If there is `error`, the `error` needs to be thrown to the error boundary.
// If there is no `error`, the `revalidation` promise needs to be thrown to
Expand All @@ -633,21 +593,6 @@ export function useSWRHandler<Data = any, Error = any>(
throw isUndefined(error) ? revalidate({ dedupe: true }) : error
}

// `mutate`, but bound to the current key.
const boundMutate: SWRResponse<Data, Error>['mutate'] = useCallback(
(newData, shouldRevalidate) => {
return internalMutate(cache, keyRef.current, newData, shouldRevalidate)
},
// `cache` isn't allowed to change during the lifecycle
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)

// Display debug info in React DevTools.
useDebugValue(data)

const currentStateDependencies = stateDependenciesRef.current

// Define the SWR state.
// `revalidate` will be deprecated in the 1.x release
// because `mutate()` covers the same use case of `revalidate()`.
Expand All @@ -660,21 +605,21 @@ export function useSWRHandler<Data = any, Error = any>(
{
data: {
get: function() {
currentStateDependencies.data = true
stateDependencies.data = true
return data
},
enumerable: true
},
error: {
get: function() {
currentStateDependencies.error = true
stateDependencies.error = true
return error
},
enumerable: true
},
isValidating: {
get: function() {
currentStateDependencies.isValidating = true
stateDependencies.isValidating = true
return isValidating
},
enumerable: true
Expand All @@ -690,7 +635,7 @@ export const SWRConfig = Object.defineProperty(ConfigProvider, 'default', {
}

export const mutate = internalMutate.bind(
null,
UNDEFINED,
defaultConfig.cache
) as ScopedMutator

Expand All @@ -701,11 +646,10 @@ export function createCache<Data>(
cache: Cache
mutate: ScopedMutator<Data>
} {
const cache = wrapCache<Data>(provider)
setupGlobalEvents(cache, options)
const cache = wrapCache<Data>(provider, options)
return {
cache,
mutate: internalMutate.bind(null, cache) as ScopedMutator<Data>
mutate: internalMutate.bind(UNDEFINED, cache) as ScopedMutator<Data>
}
}

Expand Down
Loading