Skip to content

Instantly share code, notes, and snippets.

@martinpi
Created August 25, 2023 08:16
Show Gist options
  • Save martinpi/d5398265352a1167089ae1eeb120fce6 to your computer and use it in GitHub Desktop.
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.
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