Skip to content

Commit 3adc288

Browse files
committed
feat(persist-localstorage-experimental): temporarily persist cache to disk
1 parent ced9a9b commit 3adc288

File tree

5 files changed

+92
-1
lines changed

5 files changed

+92
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"es",
5454
"hydration",
5555
"devtools",
56+
"persist-localstorage-experimental",
5657
"lib",
5758
"react",
5859
"scripts",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"internal": true,
3+
"main": "../lib/persist-localstorage-experimental/index.js",
4+
"module": "../es/persist-localstorage-experimental/index.js",
5+
"types": "../types/persist-localstorage-experimental/index.d.ts"
6+
}

rollup.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ const inputSrcs = [
1919
['src/core/index.ts', 'ReactQueryCore', 'react-query-core'],
2020
['src/devtools/index.ts', 'ReactQueryDevtools', 'react-query-devtools'],
2121
['src/hydration/index.ts', 'ReactQueryHydration', 'react-query-hydration'],
22+
[
23+
'src/persist-localstorage-experimental/index.ts',
24+
'ReactQueryPersistLocalStorageExperimental',
25+
'persist-localstorage-experimental',
26+
],
2227
]
2328

2429
const extensions = ['.js', '.jsx', '.es6', '.es', '.mjs', '.ts', '.tsx']
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { QueryClient } from '../core'
2+
import { dehydrate, hydrate } from '../hydration'
3+
4+
interface LocalStorageCache {
5+
timestamp: number
6+
buster: string
7+
cacheState: any
8+
}
9+
10+
interface Options {
11+
/** The key to use when storing the cache to localstorage */
12+
localStorageKey?: string
13+
/** To avoid localstorage spamming,
14+
* pass a time in ms to throttle saving the cache to disk */
15+
throttleTime?: number
16+
/** The max-allowed age of the cache.
17+
* If a persisted cache is found that is older than this
18+
* time, it will be discarded */
19+
maxAge?: number
20+
/** A unique string that can be used to forcefully
21+
* invalidate existing caches if they do not share the same buster string */
22+
buster?: string
23+
}
24+
25+
export function persistWithLocalStorage(
26+
queryClient: QueryClient,
27+
{
28+
localStorageKey = `REACT_QUERY_OFFLINE_CACHE`,
29+
throttleTime = 1000,
30+
maxAge = 1000 * 60 * 60 * 24,
31+
buster = '',
32+
}: Options = {}
33+
) {
34+
const saveCache = throttle(() => {
35+
const storageCache: LocalStorageCache = {
36+
buster,
37+
timestamp: Date.now(),
38+
cacheState: dehydrate(queryClient),
39+
}
40+
41+
localStorage.setItem(localStorageKey, JSON.stringify(storageCache))
42+
}, throttleTime)
43+
44+
if (typeof localStorage !== 'undefined') {
45+
const cacheStorage = localStorage.getItem(localStorageKey)
46+
47+
if (!cacheStorage) {
48+
return
49+
}
50+
51+
const cache: LocalStorageCache = JSON.parse(cacheStorage)
52+
53+
if (cache.timestamp) {
54+
const expired = Date.now() - cache.timestamp > maxAge
55+
const busted = cache.buster !== buster
56+
if (expired || busted) {
57+
localStorage.removeItem(localStorageKey)
58+
} else {
59+
hydrate(queryClient, cache.cacheState)
60+
}
61+
}
62+
63+
queryClient.getQueryCache().subscribe(saveCache)
64+
}
65+
}
66+
67+
function throttle(func: (...args: any[]) => any, wait = 100) {
68+
let timer: number | null = null
69+
70+
return function (...args: any[]) {
71+
if (timer === null) {
72+
timer = setTimeout(() => {
73+
func(...args)
74+
timer = null
75+
}, wait)
76+
}
77+
}
78+
}

tsconfig.types.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"files": [
1010
"./src/index.ts",
1111
"./src/hydration/index.ts",
12-
"./src/devtools/index.ts"
12+
"./src/devtools/index.ts",
13+
"./src/persist-localstorage-experimental/index.ts"
1314
],
1415
"exclude": ["./src/**/*"]
1516
}

0 commit comments

Comments
 (0)