Last active
August 18, 2021 23:47
-
-
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.
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 _ 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} | |
| /> | |
| ) | |
| } |
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 _ 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