86 lines
2.3 KiB
TypeScript
86 lines
2.3 KiB
TypeScript
import { useCallback, ReactNode } from "react";
|
|
import { DefaultValue } from "recoil";
|
|
import { ReadItem, WriteItems, ListenToItems, RecoilSync } from "recoil-sync";
|
|
import { STORAGE_KEY } from ".";
|
|
|
|
const DEFAULT_VALUE = new DefaultValue();
|
|
|
|
export default ({ children }: { children: ReactNode }) => {
|
|
const read: ReadItem = useCallback((itemKey) => {
|
|
if (typeof document === "undefined") return DEFAULT_VALUE; // SSR
|
|
|
|
const item = localStorage.getItem(itemKey);
|
|
|
|
let parsed: unknown;
|
|
|
|
try {
|
|
parsed = item === null ? DEFAULT_VALUE : parseJSON(item);
|
|
} catch {
|
|
parsed = DEFAULT_VALUE;
|
|
console.warn({ itemKey, item }, "parseJSON failed");
|
|
}
|
|
|
|
return parsed;
|
|
}, []);
|
|
|
|
const write: WriteItems = useCallback(({ diff }) => {
|
|
if (typeof document === "undefined") return; // SSR
|
|
|
|
for (const [key, value] of diff) {
|
|
if (value instanceof DefaultValue) {
|
|
localStorage.removeItem(key);
|
|
} else {
|
|
// reasons for setItem to fail: https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#exceptions
|
|
try {
|
|
localStorage.setItem(key, JSON.stringify(value));
|
|
} catch (err) {
|
|
console.warn({ err, key, value }, "localStorage.setItem failed");
|
|
}
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
const listen: ListenToItems = useCallback(
|
|
({ updateItem, updateAllKnownItems }) => {
|
|
void updateAllKnownItems;
|
|
const onStorage = (event: StorageEvent) => {
|
|
// ignore clear() calls
|
|
if (event.storageArea === localStorage && event.key !== null) {
|
|
let parsed: unknown;
|
|
try {
|
|
parsed =
|
|
event.newValue === null
|
|
? DEFAULT_VALUE
|
|
: parseJSON(event.newValue);
|
|
} catch {
|
|
parsed = DEFAULT_VALUE;
|
|
console.warn({ event }, "parseJSON failed");
|
|
}
|
|
|
|
updateItem(event.key, parsed);
|
|
}
|
|
};
|
|
|
|
window.addEventListener("storage", onStorage);
|
|
|
|
return () => window.removeEventListener("storage", onStorage);
|
|
},
|
|
[]
|
|
);
|
|
|
|
return (
|
|
<RecoilSync
|
|
storeKey={STORAGE_KEY}
|
|
read={read}
|
|
write={write}
|
|
listen={listen}
|
|
>
|
|
{children}
|
|
</RecoilSync>
|
|
);
|
|
};
|
|
|
|
function parseJSON(value: string): unknown {
|
|
return value === "undefined" ? undefined : JSON.parse(value);
|
|
}
|