Last active
May 24, 2021 13:57
-
-
Save mfbx9da4/1d213c6f34c4ff92cc32ed8d28d2d05b to your computer and use it in GitHub Desktop.
A wrapper around the local storage API which adds react hooks so that components can reactively update in response to storage changes.
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 { createObservable as createObservableInner } from 'onin-shared/build/createObservable' | |
import { useEffect, useState } from 'react' | |
import { isMainThread } from './isMainThread' | |
export type Key = string | |
export type Value = string | undefined | |
const createObservable = (x: Value) => createObservableInner(x) | |
type Observable = ReturnType<typeof createObservable> | |
const Observables = { | |
dict: new Map<Key, Observable>(), | |
getOrCreate: (k: Key) => { | |
if (!Observables.dict.has(k)) Observables.dict.set(k, createObservable(get(k))) | |
return Observables.dict.get(k) as Observable | |
}, | |
entries: () => Observables.dict.entries(), | |
} | |
export function get(key: Key): Value { | |
if (!isMainThread()) return | |
return localStorage.getItem(key) || undefined | |
} | |
export function set(key: Key, value: Value) { | |
if (!isMainThread()) return | |
if (value) { | |
localStorage.setItem(key, value) | |
} else { | |
localStorage.removeItem(key) | |
} | |
Observables.getOrCreate(key).set(value) | |
} | |
export function useKey(key: Key) { | |
const observable = Observables.getOrCreate(key) | |
const [val, setVal] = useState(observable.value) | |
useEffect(() => observable.subscribe(x => setVal(x)), [observable]) | |
return val | |
} | |
export const clear = () => { | |
if (!isMainThread()) return | |
localStorage.clear() | |
refresh() | |
} | |
export const refresh = () => { | |
for (const [key, observable] of Observables.entries()) { | |
const val = get(key) | |
if (val !== observable.value) set(key, val) | |
} | |
} | |
const setupListener = () => { | |
if (!isMainThread()) return | |
// we listen to local storage incase another tab sets a key we are listening to | |
window.addEventListener('storage', () => refresh()) | |
} | |
setupListener() |
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
export type Listener<T> = (val: T) => void | |
export type Observable<T> = ReturnType<typeof createObservable> | |
export const createObservable = <T>(value: T) => { | |
const listeners: Set<Listener<T>> = new Set() | |
const subscribe = (x: Listener<T>) => { | |
x(value) | |
listeners.add(x) | |
return () => void listeners.delete(x) | |
} | |
const set = (val: T) => { | |
value = val | |
for (let x of listeners.values()) x(val) | |
} | |
return { | |
subscribe, | |
set, | |
get value() { | |
return value | |
}, | |
} | |
} |
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
export const isMainThread = () => { | |
return typeof window !== 'undefined' && typeof importScripts !== 'function' | |
} |
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
function App () { | |
// Setting from a separate tab will cause this to fire also! | |
const token = useKey('my_token') | |
return <button onClick={() => set('my_token', 'asdf')}>Change my token</button> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment