Skip to content

Instantly share code, notes, and snippets.

@sodiray
Last active August 18, 2021 23:47
Show Gist options
  • Select an option

  • Save sodiray/b7f72605b11cea628a93390015f24647 to your computer and use it in GitHub Desktop.

Select an option

Save sodiray/b7f72605b11cea628a93390015f24647 to your computer and use it in GitHub Desktop.
A React hook that makes storing data in local storage simple, easy, and helpfully typed.
import _ from 'radash'
import { useStoredData } from './useStoredData'
interface StoredUser {
email: string
}
export default function App () {
const storedUsers = useStoredData({
name: 'login-users',
key: (u: StoredUser) => u.email
})
const handleSubmit = (formData: { email: string, password: string }) => {
storedUsers.addItem({ email: formData.email })
}
return (
<LoginForm
onSubmit={handleSubmit}
initEmail={_.first(storedUsers.list)?.email}
/>
)
}
import _ from 'radash'
interface Item <T> {
object: T
timestamp: number
}
/**
* Object responsible for interacting with the local storage
* system. Serailizes and deserializes the data as needed.
*/
const createStore = <T> (name: string, ttlDays: number) => {
const key = `use-stored-data:${name}`
const ttlMs = ttlDays * 24 /** hours **/ * 60 /** minutes **/ * 60 /** seconds **/ * 1000 /** milliseconds **/
return {
get: (): Record<string, Item<T>> => {
const storedString = window.localStorage.getItem(key) ?? '{}'
const storedItems: Record<string, Item<T>> = JSON.parse(storedString)
return storedItems
},
set: (items: Record<string, Item<T>>) => {
const now = new Date().getTime()
const liveItems = _.objectFilter(items, (_k, v) => v.timestamp + ttlMs > now)
window.localStorage.setItem(key, JSON.stringify(liveItems))
}
}
}
export function useStoredData <T> ({
name,
key,
ttlDays = 10,
max = 10
}: {
name: string
key: (item: T) => string
ttlDays?: number
max?: number
}) {
const store = createStore<T>(name, ttlDays)
const records: Record<string, Item<T>> = store.get()
const items: Record<string, T> = _.objectMapValues(records, r => r.object)
const list: T[] = _.sort(Object.values(records), x => x.timestamp, true).map(x => x.object)
/**
* Adding an item
* - ensuring that the max is not reached, if it is then
* trim the oldest record from local storage
* - append the new item and save
*/
const addItem = (item: T) => {
// Add the new item into the map of records
const newRecords: Record<string, Item<T>> = {
...records,
[key(item)]: {
object: item,
timestamp: new Date().getTime()
}
}
if (Object.keys(newRecords).length <= max) {
store.set(newRecords)
return
}
// conver to [{ timestamp, id }] so we can easily
// find the oldest item
const timeAndId = Object.keys(newRecords).reduce((acc, id) => {
return [ ...acc, {
id,
timestamp: newRecords[id].timestamp
}]
}, [] as { id: string, timestamp: number }[])
// Sort timestamps ascending so the first item
// is the oldest timestamp
const [oldest] = _.sort(timeAndId, x => x.timestamp)
// Filter the records to remove this oldest item
const trimmedRecords = _.objectFilter(newRecords, r => r !== oldest.id)
store.set(trimmedRecords)
}
/**
* Override all items in the store
*/
const setItems = (items: Record<string, T>) => {
const newRecords: Record<string, Item<T>> = _.objectMapValues(items, (item) => ({
object: item,
timestamp: new Date().getTime()
}))
store.set(newRecords)
}
/**
* Empty the store of all items
*/
const clearItems = () => {
store.set({})
}
/**
* Remove a single item from the store
*/
const removeItem = (item: T) => {
const newRecords = _.objectFilter(records, r => r === key(item))
store.set(newRecords)
}
return {
list,
items,
addItem,
setItems,
clearItems,
removeItem
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment