mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-03-24 17:41:27 -04:00
Add TTL support to persistent cache hook
This commit is contained in:
@@ -6,26 +6,63 @@ import {isFunction} from 'web/hooks/use-persistent-in-memory-state'
|
||||
import {useStateCheckEquality} from 'web/hooks/use-state-check-equality'
|
||||
import {safeLocalStorage} from 'web/lib/util/local'
|
||||
|
||||
export const usePersistentLocalState = <T>(initialValue: T, key: string) => {
|
||||
type StoredEnvelope<T> = {
|
||||
value: T
|
||||
expiresAt: number | null // null = never expires
|
||||
}
|
||||
|
||||
const wrapValue = <T>(value: T, ttlMs: number | null): StoredEnvelope<T> => ({
|
||||
value,
|
||||
expiresAt: ttlMs != null ? Date.now() + ttlMs : null,
|
||||
})
|
||||
|
||||
const unwrapValue = <T>(envelope: unknown, fallback: T): {value: T; expired: boolean} => {
|
||||
if (envelope == null || typeof envelope !== 'object' || !('value' in envelope)) {
|
||||
return {value: fallback, expired: false}
|
||||
}
|
||||
|
||||
const {value, expiresAt} = envelope as StoredEnvelope<T>
|
||||
|
||||
if (expiresAt != null && Date.now() > expiresAt) {
|
||||
return {value: fallback, expired: true}
|
||||
}
|
||||
|
||||
return {value, expired: false}
|
||||
}
|
||||
|
||||
export const usePersistentLocalState = <T>(
|
||||
initialValue: T,
|
||||
key: string,
|
||||
ttl: number | null = null,
|
||||
) => {
|
||||
// Note: use a version (like "-v1") in the key to increment after backwards-incompatible changes
|
||||
const isClient = useIsClient()
|
||||
const [state, setState] = useStateCheckEquality<T>(
|
||||
(isClient && safeJsonParse(safeLocalStorage?.getItem(key))) || initialValue,
|
||||
)
|
||||
|
||||
const readFromStorage = (): T => {
|
||||
const raw = safeLocalStorage?.getItem(key)
|
||||
const parsed = safeJsonParse(raw)
|
||||
const {value, expired} = unwrapValue<T>(parsed, initialValue)
|
||||
if (expired) safeLocalStorage?.removeItem(key)
|
||||
return value
|
||||
}
|
||||
|
||||
const [state, setState] = useStateCheckEquality<T>(isClient ? readFromStorage() : initialValue)
|
||||
|
||||
const saveState = useEvent((newState: T | ((prevState: T) => T)) => {
|
||||
setState((prevState: T) => {
|
||||
const updatedState = isFunction(newState) ? newState(prevState) : newState
|
||||
safeLocalStorage?.setItem(key, JSON.stringify(updatedState))
|
||||
safeLocalStorage?.setItem(key, JSON.stringify(wrapValue(updatedState, ttl)))
|
||||
return updatedState
|
||||
})
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (safeLocalStorage) {
|
||||
const storedJson = safeJsonParse(safeLocalStorage.getItem(key))
|
||||
const storedValue = storedJson ?? initialValue
|
||||
if (storedJson === null && initialValue === undefined) return
|
||||
saveState(storedValue as T)
|
||||
const raw = safeLocalStorage.getItem(key)
|
||||
const parsed = safeJsonParse(raw)
|
||||
const {value, expired} = unwrapValue<T>(parsed, initialValue)
|
||||
if (expired || (parsed === null && initialValue === undefined)) return
|
||||
saveState(value as T)
|
||||
}
|
||||
}, [key])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user