Created
August 25, 2023 08:16
-
-
Save martinpi/d5398265352a1167089ae1eeb120fce6 to your computer and use it in GitHub Desktop.
A persistent React hook that supports update functions. Couldn't find any on the interwebs so I had to roll my own.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useRef, useState } from 'react'; | |
// This hook receives three parameters: | |
// storageKey: This is the name of our storage that gets used when we retrieve/save our persistent data. | |
// ttl (seconds): How long it should be kept in browser storage | |
// initialState: This is our default value, but only if the store doesn't exist, otherwise it gets overwritten by the store. | |
// Example: | |
// Store a state that can be any or null and keep it cached for 15 minutes. | |
// [state, setState] = usePersistentState<any | null>("project.key", 15 * 60, null); | |
function setWithExpiry(key: string, value: any, ttl: number) { | |
const now = new Date() | |
// `item` is an object which contains the original value | |
// as well as the time when it's supposed to expire | |
const item = { | |
value: value, | |
expiry: now.getTime() + ttl * 1000, | |
} | |
// console.log("Writing to persistent storage: ") | |
// console.log(item); | |
sessionStorage.setItem(key, JSON.stringify(item)) | |
} | |
function getWithExpiry(key: string) { | |
const itemStr = sessionStorage.getItem(key) | |
// if the item doesn't exist, return null | |
if (!itemStr) { | |
return null | |
} | |
const item = JSON.parse(itemStr) | |
const now = new Date() | |
// compare the expiry time of the item with the current time | |
if (now.getTime() > item.expiry) { | |
// If the item is expired, delete the item from storage | |
// and return null | |
sessionStorage.removeItem(key) | |
// console.log("Cache expired!") | |
return null | |
} | |
// console.log("Found in cache!") | |
return item.value | |
} | |
function isValidWithExpiry(key: string) { | |
const itemStr = sessionStorage.getItem(key) | |
// if the item doesn't exist, return null | |
if (!itemStr) { | |
// console.log("Cache not found") | |
return false; | |
} | |
const item = JSON.parse(itemStr) | |
const now = new Date() | |
// compare the expiry time of the item with the current time | |
return (now.getTime() <= item.expiry); | |
} | |
type SetStateUpdaterCallback<T> = (s: T) => T; | |
type SetStateAction<T> = (newState: T | SetStateUpdaterCallback<T>) => void; | |
export function usePersistentState<T>(storageKey: string, ttl: number, init: T): [T, SetStateAction<T>]; | |
export function usePersistentState<T = undefined>(storageKey: string, ttl: number, init?: T): [T | undefined, SetStateAction<T | undefined>]; | |
export function usePersistentState<T>(storageKey: string, ttl: number, init: T): [T, SetStateAction<T>] { | |
const [state, setState] = useState<T>(isValidWithExpiry(storageKey) ? getWithExpiry(storageKey) : init); | |
const setPersistentState: SetStateAction<T> = (newState): void => { | |
setState(newState); | |
}; | |
useEffect(() => { | |
setWithExpiry(storageKey, state, ttl); | |
}, [state]) | |
useEffect(() => { | |
const storageInBrowser = getWithExpiry(storageKey); | |
if (storageInBrowser) { | |
setState(storageInBrowser); | |
} | |
}, []); | |
return [state, setPersistentState]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment