Skip to content

Commit

Permalink
Merge branch 'master' into refactor-cache-test
Browse files Browse the repository at this point in the history
  • Loading branch information
koba04 committed Sep 11, 2021
2 parents 77dbc34 + 433a914 commit 759b885
Show file tree
Hide file tree
Showing 14 changed files with 103 additions and 108 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = {
globals: {
'ts-jest': {
tsconfig: 'test/tsconfig.json',
diagnostics: false,
diagnostics: process.env.CI,
}
},
}
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@
"devDependencies": {
"@testing-library/jest-dom": "5.14.1",
"@testing-library/react": "12.0.0",
"@types/jest": "25.2.3",
"@types/node": "11.12.0",
"@types/react": "16.9.11",
"@types/react": "17.0.20",
"@typescript-eslint/eslint-plugin": "4.24.0",
"@typescript-eslint/parser": "4.24.0",
"bunchee": "1.7.1",
Expand Down
10 changes: 5 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export interface PublicConfiguration<
export type FullConfiguration = InternalConfiguration & PublicConfiguration

export type ConfigOptions = {
initFocus: (callback: () => void) => void
initReconnect: (callback: () => void) => void
initFocus: (callback: () => void) => (() => void) | void
initReconnect: (callback: () => void) => (() => void) | void
}

export type SWRHook = <Data = any, Error = any>(
Expand Down Expand Up @@ -148,10 +148,10 @@ export type SWRConfiguration<
export type Key = ValueKey | (() => ValueKey)

export interface SWRResponse<Data, Error> {
data?: Readonly<Data>
error?: Readonly<Error>
data?: Data
error?: Error
mutate: KeyedMutator<Data>
isValidating: Readonly<boolean>
isValidating: boolean
}

export type KeyLoader<Data = any> =
Expand Down
4 changes: 3 additions & 1 deletion src/use-swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,13 @@ export const useSWRHandler = <Data = any, Error = any>(

// A revalidation must be triggered when mounted if:
// - `revalidateOnMount` is explicitly set to `true`.
// - `isPaused()` returns `false`, and:
// - Suspense mode and there's stale data for the initial render.
// - Not suspense mode and there is no fallback data and `revalidateIfStale` is enabled.
// - `revalidateIfStale` is enabled but `data` is not defined.
const shouldRevalidateOnMount = () => {
if (!isUndefined(revalidateOnMount)) return revalidateOnMount
if (configRef.current.isPaused()) return false

return suspense
? !initialMountedRef.current && !isUndefined(data)
Expand Down Expand Up @@ -273,7 +275,7 @@ export const useSWRHandler = <Data = any, Error = any>(
// Keep the stale data but update error.
setState({
isValidating: false,
error: err
error: err as Error
})
if (!shouldDedupe) {
// Broadcast to update the states of other hooks.
Expand Down
15 changes: 9 additions & 6 deletions src/utils/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const revalidateAllKeys = (
export const initCache = <Data = any>(
provider: Cache<Data>,
options?: Partial<ConfigOptions>
): [Cache<Data>, ScopedMutator<Data>] | undefined => {
): [Cache<Data>, ScopedMutator<Data>, () => void] | 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.
Expand Down Expand Up @@ -55,28 +55,31 @@ export const initCache = <Data = any>(

// This is a new provider, we need to initialize it and setup DOM events
// listeners for `focus` and `reconnect` actions.
let unscubscibe = () => {}
if (!IS_SERVER) {
opts.initFocus(
const releaseFocus = opts.initFocus(
revalidateAllKeys.bind(
UNDEFINED,
EVENT_REVALIDATORS,
revalidateEvents.FOCUS_EVENT
)
)
opts.initReconnect(
const releaseReconnect = opts.initReconnect(
revalidateAllKeys.bind(
UNDEFINED,
EVENT_REVALIDATORS,
revalidateEvents.RECONNECT_EVENT
)
)
unscubscibe = () => {
releaseFocus && releaseFocus()
releaseReconnect && releaseReconnect()
}
}

// 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]
return [provider, mutate, unscubscibe]
}

return
}
27 changes: 20 additions & 7 deletions src/utils/config-context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { createContext, createElement, useContext, useState, FC } from 'react'

import {
createContext,
createElement,
useContext,
useState,
FC,
useEffect
} from 'react'
import { cache as defaultCache } from './config'
import { initCache } from './cache'
import { mergeConfigs } from './merge-config'
import { UNDEFINED } from './helper'
import { isFunction, UNDEFINED } from './helper'
import {
SWRConfiguration,
FullConfiguration,
Expand All @@ -26,18 +32,25 @@ const SWRConfig: FC<{
const provider = value && value.provider

// Use a lazy initialized state to create the cache on first access.
const [cacheAndMutate] = useState(() =>
const [cacheHandle] = useState(() =>
provider
? initCache(provider(extendedConfig.cache || defaultCache), value)
: UNDEFINED
)

// Override the cache if a new provider is given.
if (cacheAndMutate) {
extendedConfig.cache = cacheAndMutate[0]
extendedConfig.mutate = cacheAndMutate[1]
if (cacheHandle) {
extendedConfig.cache = cacheHandle[0]
extendedConfig.mutate = cacheHandle[1]
}

useEffect(() => {
return () => {
const unsubscribe = cacheHandle ? cacheHandle[2] : UNDEFINED
isFunction(unsubscribe) && unsubscribe()
}
}, [])

return createElement(
SWRConfigContext.Provider,
{ value: extendedConfig },
Expand Down
6 changes: 5 additions & 1 deletion src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ const onErrorRetry = (
}

// Default cache provider
const [cache, mutate] = initCache(new Map()) as [Cache<any>, ScopedMutator<any>]
const [cache, mutate] = initCache(new Map()) as [
Cache<any>,
ScopedMutator<any>,
() => {}
]
export { cache, mutate }

// Default config
Expand Down
22 changes: 17 additions & 5 deletions src/utils/web-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const isOnline = () => online
// For node and React Native, `add/removeEventListener` doesn't exist on window.
const onWindowEvent = (hasWindow && addEventListener) || noop
const onDocumentEvent = (hasDocument && document.addEventListener) || noop
const offWindowEvent = (hasWindow && removeEventListener) || noop
const offDocumentEvent = (hasDocument && document.removeEventListener) || noop

const isVisible = () => {
const visibilityState = hasDocument && document.visibilityState
Expand All @@ -27,18 +29,28 @@ const initFocus = (cb: () => void) => {
// focus revalidate
onDocumentEvent('visibilitychange', cb)
onWindowEvent('focus', cb)
return () => {
offDocumentEvent('visibilitychange', cb)
offWindowEvent('focus', cb)
}
}

const initReconnect = (cb: () => void) => {
// reconnect revalidate
onWindowEvent('online', () => {
// revalidate on reconnected
const onOnline = () => {
online = true
cb()
})
}
// nothing to revalidate, just update the status
onWindowEvent('offline', () => {
const onOffline = () => {
online = false
})
}
onWindowEvent('online', onOnline)
onWindowEvent('offline', onOffline)
return () => {
offWindowEvent('online', onOnline)
offWindowEvent('offline', onOffline)
}
}

export const preset = {
Expand Down
10 changes: 8 additions & 2 deletions test/use-swr-cache.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ describe('useSWR - cache provider', () => {
it('should respect provider options', async () => {
const key = createKey()
const focusFn = jest.fn()
const unsubscribeFocusFn = jest.fn()
const unsubscribeReconnectFn = jest.fn()

let value = 1
function Page() {
Expand All @@ -139,14 +141,15 @@ describe('useSWR - cache provider', () => {
})
return <>{String(data)}</>
}

renderWithConfig(<Page />, {
const { unmount } = renderWithConfig(<Page />, {
provider: () => new Map([[key, 0]]),
initFocus() {
focusFn()
return unsubscribeFocusFn
},
initReconnect() {
/* do nothing */
return unsubscribeReconnectFn
}
})
screen.getByText('0')
Expand All @@ -158,7 +161,10 @@ describe('useSWR - cache provider', () => {
await focusOn(window)
// revalidateOnFocus won't work
screen.getByText('1')
unmount()
expect(focusFn).toBeCalled()
expect(unsubscribeFocusFn).toBeCalledTimes(1)
expect(unsubscribeReconnectFn).toBeCalledTimes(1)
})

it('should work with revalidateOnFocus', async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/use-swr-concurrent-rendering.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { createResponse, sleep } from './utils'
describe('useSWR - concurrent rendering', () => {
let React, ReactDOM, act, useSWR

beforeEach(() => {
beforeAll(() => {
jest.resetModules()
jest.mock('scheduler', () => require('scheduler/unstable_mock'))
jest.mock('react', () => require('react-experimental'))
Expand Down
5 changes: 1 addition & 4 deletions test/use-swr-immutable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,7 @@ describe('useSWR - immutable', () => {
})

it('should not revalidate with revalidateIfStale disabled when key changes', async () => {
const fetcher = jest.fn(v => {
console.log(v)
return v
})
const fetcher = jest.fn(v => v)

const key = createKey()
const useData = (id: string) =>
Expand Down
15 changes: 15 additions & 0 deletions test/use-swr-revalidate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,19 @@ describe('useSWR - revalidate', () => {
await act(() => sleep(70))
screen.getByText('count: 2')
})

it('should set initial isValidating be false when config.isPaused returns true', async () => {
function Page() {
const { isValidating } = useSWR(
'set isValidating for config.isPaused',
() => '123',
{ isPaused: () => true }
)

return <div>{isValidating ? 'true' : 'false'}</div>
}

render(<Page />)
screen.getByText('false')
})
})
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"noUnusedParameters": true,
"strictBindCallApply": true,
"outDir": "./dist",
"types": ["node", "jest"],
"rootDir": "src",
"strict": true,
"target": "es5",
"baseUrl": ".",
"noEmitOnError": true,
"paths": {
"swr": ["./src/index.ts"],
"swr/infinite": ["./infinite/index.ts"],
Expand Down
Loading

0 comments on commit 759b885

Please sign in to comment.