Skip to content

Commit 169909c

Browse files
authored
Merge branch 'master' into patch-1
2 parents b9f7bed + 766718a commit 169909c

9 files changed

+217
-184
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "swr",
3-
"version": "0.5.1",
3+
"version": "0.5.3",
44
"description": "React Hooks library for remote data fetching",
55
"main": "./dist/index.js",
66
"module": "./esm/index.js",

src/cache.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,42 @@ import { Cache as CacheType, Key, CacheListener } from './types'
22
import hash from './libs/hash'
33

44
export default class Cache implements CacheType {
5-
private __cache: Map<string, any>
6-
private __listeners: CacheListener[]
5+
private cache: Map<string, any>
6+
private subs: CacheListener[]
77

88
constructor(initialData: any = {}) {
9-
this.__cache = new Map(Object.entries(initialData))
10-
this.__listeners = []
9+
this.cache = new Map(Object.entries(initialData))
10+
this.subs = []
1111
}
1212

1313
get(key: Key): any {
1414
const [_key] = this.serializeKey(key)
15-
return this.__cache.get(_key)
15+
return this.cache.get(_key)
1616
}
1717

1818
set(key: Key, value: any): any {
1919
const [_key] = this.serializeKey(key)
20-
this.__cache.set(_key, value)
20+
this.cache.set(_key, value)
2121
this.notify()
2222
}
2323

2424
keys() {
25-
return Array.from(this.__cache.keys())
25+
return Array.from(this.cache.keys())
2626
}
2727

2828
has(key: Key) {
2929
const [_key] = this.serializeKey(key)
30-
return this.__cache.has(_key)
30+
return this.cache.has(_key)
3131
}
3232

3333
clear() {
34-
this.__cache.clear()
34+
this.cache.clear()
3535
this.notify()
3636
}
3737

3838
delete(key: Key) {
3939
const [_key] = this.serializeKey(key)
40-
this.__cache.delete(_key)
40+
this.cache.delete(_key)
4141
this.notify()
4242
}
4343

@@ -74,22 +74,22 @@ export default class Cache implements CacheType {
7474
}
7575

7676
let isSubscribed = true
77-
this.__listeners.push(listener)
77+
this.subs.push(listener)
7878

7979
return () => {
8080
if (!isSubscribed) return
8181
isSubscribed = false
82-
const index = this.__listeners.indexOf(listener)
82+
const index = this.subs.indexOf(listener)
8383
if (index > -1) {
84-
this.__listeners[index] = this.__listeners[this.__listeners.length - 1]
85-
this.__listeners.length--
84+
this.subs[index] = this.subs[this.subs.length - 1]
85+
this.subs.length--
8686
}
8787
}
8888
}
8989

9090
// Notify Cache subscribers about a change in the cache
9191
private notify() {
92-
for (let listener of this.__listeners) {
92+
for (let listener of this.subs) {
9393
listener()
9494
}
9595
}

src/env.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useEffect, useLayoutEffect } from 'react'
2+
3+
export const IS_SERVER =
4+
typeof window === 'undefined' ||
5+
// @ts-ignore
6+
!!(typeof Deno !== 'undefined' && Deno && Deno.version && Deno.version.deno)
7+
8+
// polyfill for requestAnimationFrame
9+
export const rAF = IS_SERVER
10+
? null
11+
: window['requestAnimationFrame'] || (f => setTimeout(f, 1))
12+
13+
// React currently throws a warning when using useLayoutEffect on the server.
14+
// To get around it, we can conditionally useEffect on the server (no-op) and
15+
// useLayoutEffect in the browser.
16+
export const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect

src/use-swr-infinite.ts

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// TODO: use @ts-expect-error
2-
import { useContext, useRef, useState, useEffect, useCallback } from 'react'
2+
import { useContext, useRef, useState, useCallback } from 'react'
33

44
import defaultConfig, { cache } from './config'
5+
import { useIsomorphicLayoutEffect } from './env'
56
import SWRConfigContext from './swr-config-context'
67
import useSWR from './use-swr'
78

@@ -68,34 +69,40 @@ function useSWRInfinite<Data = any, Error = any>(
6869
// not ready
6970
}
7071

71-
const [, rerender] = useState<boolean>(false)
72+
const rerender = useState({})[1]
7273

7374
// we use cache to pass extra info (context) to fetcher so it can be globally shared
7475
// here we get the key of the fetcher context cache
7576
let contextCacheKey: string | null = null
7677
if (firstPageKey) {
77-
contextCacheKey = 'context@' + firstPageKey
78+
contextCacheKey = 'ctx@' + firstPageKey
7879
}
7980

80-
// page count is cached as well, so when navigating the list can be restored
81-
let pageCountCacheKey: string | null = null
82-
let cachedPageSize
81+
// page size is also cached to share the page data between hooks having the same key
82+
let pageSizeCacheKey: string | null = null
8383
if (firstPageKey) {
84-
pageCountCacheKey = 'size@' + firstPageKey
85-
cachedPageSize = cache.get(pageCountCacheKey)
84+
pageSizeCacheKey = 'len@' + firstPageKey
8685
}
87-
const pageCountRef = useRef<number>(cachedPageSize || initialSize)
8886
const didMountRef = useRef<boolean>(false)
8987

88+
const resolvePageSize = useCallback((): number => {
89+
const cachedPageSize = cache.get(pageSizeCacheKey)
90+
return typeof cachedPageSize !== 'undefined' ? cachedPageSize : initialSize
91+
}, [pageSizeCacheKey, initialSize])
92+
// keep the last page size to restore it with the persistSize option
93+
const lastPageSizeRef = useRef<number>(resolvePageSize())
94+
9095
// every time the key changes, we reset the page size if it's not persisted
91-
useEffect(() => {
92-
if (didMountRef.current) {
93-
if (!persistSize) {
94-
pageCountRef.current = initialSize
95-
}
96-
} else {
96+
useIsomorphicLayoutEffect(() => {
97+
if (!didMountRef.current) {
9798
didMountRef.current = true
99+
return
98100
}
101+
// If the key has been changed, we keep the current page size if persistSize is enabled
102+
cache.set(
103+
pageSizeCacheKey,
104+
persistSize ? lastPageSizeRef.current : initialSize
105+
)
99106
// initialSize isn't allowed to change during the lifecycle
100107
// eslint-disable-next-line react-hooks/exhaustive-deps
101108
}, [firstPageKey])
@@ -105,16 +112,17 @@ function useSWRInfinite<Data = any, Error = any>(
105112

106113
// actual swr of all pages
107114
const swr = useSWR<Data[], Error>(
108-
firstPageKey ? ['many', firstPageKey] : null,
115+
firstPageKey ? ['inf', firstPageKey] : null,
109116
async () => {
110117
// get the revalidate context
111-
const { originalData, force } = cache.get(contextCacheKey) || {}
118+
const { data: originalData, force } = cache.get(contextCacheKey) || {}
112119

113120
// return an array of page data
114121
const data: Data[] = []
115122

123+
const pageSize = resolvePageSize()
116124
let previousPageData = null
117-
for (let i = 0; i < pageCountRef.current; ++i) {
125+
for (let i = 0; i < pageSize; ++i) {
118126
const [pageKey, pageArgs] = cache.serializeKey(
119127
getKey(i, previousPageData)
120128
)
@@ -165,7 +173,7 @@ function useSWRInfinite<Data = any, Error = any>(
165173
)
166174

167175
// update dataRef
168-
useEffect(() => {
176+
useIsomorphicLayoutEffect(() => {
169177
dataRef.current = swr.data
170178
}, [swr.data])
171179

@@ -174,7 +182,7 @@ function useSWRInfinite<Data = any, Error = any>(
174182
if (shouldRevalidate && typeof data !== 'undefined') {
175183
// we only revalidate the pages that are changed
176184
const originalData = dataRef.current
177-
cache.set(contextCacheKey, { originalData, force: false })
185+
cache.set(contextCacheKey, { data: originalData, force: false })
178186
} else if (shouldRevalidate) {
179187
// calling `mutate()`, we revalidate all pages
180188
cache.set(contextCacheKey, { force: true })
@@ -188,23 +196,26 @@ function useSWRInfinite<Data = any, Error = any>(
188196
)
189197

190198
// extend the SWR API
191-
const size = pageCountRef.current
192199
const setSize = useCallback(
193-
arg => {
200+
(arg: number | ((size: number) => number)) => {
201+
let size
194202
if (typeof arg === 'function') {
195-
pageCountRef.current = arg(pageCountRef.current)
203+
size = arg(resolvePageSize())
196204
} else if (typeof arg === 'number') {
197-
pageCountRef.current = arg
205+
size = arg
206+
}
207+
if (typeof size === 'number') {
208+
cache.set(pageSizeCacheKey, size)
209+
lastPageSizeRef.current = size
198210
}
199-
cache.set(pageCountCacheKey, pageCountRef.current)
200-
rerender(v => !v)
211+
rerender({})
201212
return mutate(v => v)
202213
},
203-
[mutate, pageCountCacheKey]
214+
[pageSizeCacheKey, resolvePageSize, mutate]
204215
)
205216

206217
// Use getter functions to avoid unnecessary re-renders caused by triggering all the getters of the returned swr object
207-
const swrInfinite = { size, setSize, mutate }
218+
const swrInfinite = { size: resolvePageSize(), setSize, mutate }
208219
Object.defineProperties(swrInfinite, {
209220
error: {
210221
get: () => swr.error,

src/use-swr.ts

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
import {
33
useCallback,
44
useContext,
5-
useEffect,
6-
useLayoutEffect,
75
useState,
86
useRef,
97
useMemo,
108
useDebugValue
119
} from 'react'
1210

1311
import defaultConfig, { cache } from './config'
12+
import { IS_SERVER, rAF, useIsomorphicLayoutEffect } from './env'
1413
import SWRConfigContext from './swr-config-context'
1514
import {
1615
Action,
@@ -25,21 +24,6 @@ import {
2524
SWRConfiguration
2625
} from './types'
2726

28-
const IS_SERVER =
29-
typeof window === 'undefined' ||
30-
// @ts-ignore
31-
!!(typeof Deno !== 'undefined' && Deno && Deno.version && Deno.version.deno)
32-
33-
// polyfill for requestAnimationFrame
34-
const rAF = IS_SERVER
35-
? null
36-
: window['requestAnimationFrame'] || (f => setTimeout(f, 1))
37-
38-
// React currently throws a warning when using useLayoutEffect on the server.
39-
// To get around it, we can conditionally useEffect on the server (no-op) and
40-
// useLayoutEffect in the browser.
41-
const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect
42-
4327
type Revalidator = (...args: any[]) => void
4428

4529
// global state managers
@@ -292,7 +276,7 @@ function useSWR<Data = any, Error = any>(
292276
// display the data label in the React DevTools next to SWR hooks
293277
useDebugValue(stateRef.current.data)
294278

295-
const rerender = useState<unknown>(null)[1]
279+
const rerender = useState<unknown>({})[1]
296280

297281
let dispatch = useCallback(
298282
(payload: Action<Data, Error>) => {
@@ -352,26 +336,21 @@ function useSWR<Data = any, Error = any>(
352336
revalidators: Record<string, Revalidator[]>,
353337
callback: Revalidator
354338
) => {
355-
if (!callback) return
356339
if (!revalidators[key]) {
357340
revalidators[key] = [callback]
358341
} else {
359342
revalidators[key].push(callback)
360343
}
361-
}
362344

363-
const removeRevalidator = (
364-
revlidators: Record<string, Revalidator[]>,
365-
callback: Revalidator
366-
) => {
367-
if (revlidators[key]) {
368-
const revalidators = revlidators[key]
369-
const index = revalidators.indexOf(callback)
345+
return () => {
346+
const keyedRevalidators = revalidators[key]
347+
const index = keyedRevalidators.indexOf(callback)
348+
370349
if (index >= 0) {
371-
// 10x faster than splice
372-
// https://jsperf.com/array-remove-by-index
373-
revalidators[index] = revalidators[revalidators.length - 1]
374-
revalidators.pop()
350+
// O(1): faster than splice
351+
keyedRevalidators[index] =
352+
keyedRevalidators[keyedRevalidators.length - 1]
353+
keyedRevalidators.pop()
375354
}
376355
}
377356
}
@@ -573,9 +552,8 @@ function useSWR<Data = any, Error = any>(
573552
const latestKeyedData = resolveData()
574553

575554
// update the state if the key changed (not the inital render) or cache updated
576-
if (keyRef.current !== key) {
577-
keyRef.current = key
578-
}
555+
keyRef.current = key
556+
579557
if (!config.compare(currentHookData, latestKeyedData)) {
580558
dispatch({ data: latestKeyedData })
581559
}
@@ -662,9 +640,9 @@ function useSWR<Data = any, Error = any>(
662640
return false
663641
}
664642

665-
addRevalidator(FOCUS_REVALIDATORS, onFocus)
666-
addRevalidator(RECONNECT_REVALIDATORS, onReconnect)
667-
addRevalidator(CACHE_REVALIDATORS, onUpdate)
643+
const unsubFocus = addRevalidator(FOCUS_REVALIDATORS, onFocus)
644+
const unsubReconnect = addRevalidator(RECONNECT_REVALIDATORS, onReconnect)
645+
const unsubUpdate = addRevalidator(CACHE_REVALIDATORS, onUpdate)
668646

669647
return () => {
670648
// cleanup
@@ -673,9 +651,9 @@ function useSWR<Data = any, Error = any>(
673651
// mark it as unmounted
674652
unmountedRef.current = true
675653

676-
removeRevalidator(FOCUS_REVALIDATORS, onFocus)
677-
removeRevalidator(RECONNECT_REVALIDATORS, onReconnect)
678-
removeRevalidator(CACHE_REVALIDATORS, onUpdate)
654+
unsubFocus()
655+
unsubReconnect()
656+
unsubUpdate()
679657
}
680658
}, [key, revalidate])
681659

test/use-swr-config-callbacks.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ describe('useSWR - config callbacks', () => {
160160
expect(state).toEqual(null)
161161

162162
// should trigger a loading slow event
163-
await act(() => sleep(LOADING_TIMEOUT))
163+
await act(() => sleep(LOADING_TIMEOUT * 1.5))
164164
screen.getByText('hello, , a')
165165
expect(state).toEqual('a')
166166

test/use-swr-configs.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import defaultConfig from '../src/config'
77
describe('useSWR - configs', () => {
88
it('should read the config fallback from the context', async () => {
99
let value = 0
10-
const INTERVAL = 10
10+
const INTERVAL = 100
1111
const fetcher = () => value++
1212

1313
function Section() {

0 commit comments

Comments
 (0)