Last active
November 5, 2018 22:46
-
-
Save mattmccray/45c3b9ef521b1ce7ec42f9a304826da2 to your computer and use it in GitHub Desktop.
Persist a (react-easy-state) store to localStorage
This file contains 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 { store } from "react-easy-state"; | |
interface PersistAPI<T = any> { | |
applySnapshot: (data: T) => void | |
getSnapshot: () => T | |
isLoaded: boolean | |
load: () => void | |
save: () => void | |
saveImmediately: () => void | |
} | |
interface PersistOptions<T> { | |
storageKey: string | |
autoLoad?: boolean | |
saveOnUnload?: boolean | |
delay?: number | |
serializer?: (store: T) => any | |
deserializer?: (store: any) => Partial<T> | |
} | |
export function persist<T extends object>(wrappedStore: T, options: PersistOptions<T>): [PersistAPI<T>, T] { | |
const api = store({ | |
isLoaded: false, | |
save: debounce( | |
() => saveStore(options.storageKey, wrappedStore as any, options), | |
{ delay: options.delay } | |
), | |
saveImmediately: () => saveStore(options.storageKey, wrappedStore as any, options), | |
load: () => loadStore(options.storageKey, wrappedStore as any, options, api), | |
getSnapshot: () => getSnapshot(wrappedStore, options), | |
applySnapshot: (data: T) => applySnapshot(wrappedStore, data, options) | |
}) | |
if (options.autoLoad !== false) { | |
api.load() | |
} | |
if (options.saveOnUnload !== false) { | |
window.addEventListener('unload', api.saveImmediately) | |
} | |
return [api, wrappedStore] | |
} | |
export default persist | |
const defaultSerializer = (data: any) => data | |
function saveStore(key: string, store: any, options: PersistOptions<any>) { | |
const data = getSnapshot(store, options); | |
localStorage.setItem(key, JSON.stringify(data)); | |
console.debug("Saved store to", key, { data }); | |
} | |
function getSnapshot<T = any>(sourceStore: T, options: PersistOptions<any>) { | |
const { serializer = defaultSerializer } = options | |
return deepCloneSync(serializer(sourceStore)) | |
} | |
function loadStore(key: string, store: any, options: PersistOptions<any>, apiStore: any) { | |
const json = localStorage.getItem(key); | |
if (json) { | |
const data = JSON.parse(json) | |
applySnapshot(store, data, options) | |
} | |
apiStore.isLoaded = true; | |
console.debug("Loaded store from", key, { json, store }); | |
} | |
function applySnapshot<T = any>(targetStore: T, snapshot: any, options: PersistOptions<any>) { | |
const { deserializer = defaultSerializer } = options | |
Object.assign(targetStore, deserializer(snapshot)) // A big dangerous but hell. Whatever. | |
return targetStore | |
} | |
function deepCloneSync<T = any>(target: T): T { | |
return JSON.parse(JSON.stringify(target)) as T; | |
} | |
interface DebounceOptions { | |
delay: number; | |
} | |
function debounce(callback: () => void, options: Partial<DebounceOptions> = {}) { | |
const { delay = 2000 } = options; | |
let timeout: any = null; | |
return () => { | |
if (timeout) { | |
clearTimeout(timeout); | |
} | |
timeout = setTimeout(_doCallback, delay); | |
}; | |
function _doCallback() { | |
timeout = null; | |
callback(); | |
} | |
} |
This file contains 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 persist from "./persist"; | |
import { store } from "react-easy-state"; | |
export interface Note { | |
id: string | |
title: string | |
body: string | |
created: number | |
updated: number | |
isDraft: boolean | |
} | |
const notes = store({ | |
list: [] as Note[], | |
get isLoaded() { | |
return storage.isLoaded | |
}, | |
get allNotes() { | |
return notes.list | |
}, | |
get newNotes() { | |
return notes.list.filter(note => note.updated == 0) | |
}, | |
get draftNotes() { | |
return notes.list.filter(note => note.isDraft) | |
}, | |
get publishedNotes() { | |
return notes.list.filter(note => note.isDraft) | |
}, | |
addNote: (noteData: Partial<Note> = {}) => { | |
const { | |
id = Date.now().toString(32), | |
title = "Untitled", | |
body = "", | |
created = Date.now(), | |
updated = 0, | |
isDraft = true | |
} = noteData | |
notes.list.push({ id, title, body, created, updated, isDraft }) | |
storage.save() | |
return notes.find(id)! | |
}, | |
find: (id: string) => { | |
return notes.list.find(note => note.id == id) | |
}, | |
}) | |
const [storage] = persist(notes, { | |
storageKey: 'note-store', | |
serializer: (store) => ({ | |
// Only persist the list... | |
list: store.list | |
}) | |
}) | |
export default notes | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment