Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Last active November 5, 2018 22:46
Show Gist options
  • Save mattmccray/45c3b9ef521b1ce7ec42f9a304826da2 to your computer and use it in GitHub Desktop.
Save mattmccray/45c3b9ef521b1ce7ec42f9a304826da2 to your computer and use it in GitHub Desktop.
Persist a (react-easy-state) store to localStorage
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();
}
}
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