Last active
May 24, 2020 02:17
-
-
Save benhorst/aa04c48b6934f94730b0716675f28329 to your computer and use it in GitHub Desktop.
simple localstorage sync script with comments and notes
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 { useEffect, useState } from "react"; | |
// a quick example of "enhancing" the localstorage sync | |
// in order to use it like a specialized store. | |
// you're obviously going to get worse perf than a regular React.createContext, but you get some benefits. | |
const validDetails = ["name", "favColor"]; | |
export const usePlayerDetail = (key) => { | |
// check for valid inputs, ensure we're only using keys we care about. | |
// potential migration concerns | |
if (!validDetails.includes(key)) { | |
console.error( | |
`Invalid key provided to usePlayerDetail: ${key}. Valid keys: ${validDetails}.` | |
); | |
} | |
const playerKey = "player." + key; | |
return useLocalStorage(playerKey, ""); | |
}; | |
const identity = (x) => x; | |
const defaultTransform = { serialize: identity, deserialize: identity }; | |
// if you need to save an object or array, you want to use this. perf hit depending on object size. | |
export const objectTransform = { | |
serialize: (ob) => JSON.stringify(ob), | |
deserialize: (s) => JSON.parse(s), | |
}; | |
// this hook assumes you want to store a string. if you need more than that, use transforms for json or look elsewhere | |
// the only required parameter in that case is "key". You'll get a standard "useState" interface from there. | |
export const useLocalStorage = ( | |
key, | |
defaultValue = "", | |
transform = defaultTransform | |
) => { | |
// TODO: assert(transform.serialize is a function) && assert(transform.deserialize is a function); | |
// keep things safe for server. | |
// since this uses localstorage, we need to useEffect to get a client-side state only | |
const [initialized, setInitialized] = useState(false); | |
useEffect(() => { | |
setInitialized(true); | |
// once we're initialized and on the client, we can set up the state. | |
setValue( | |
transform.serialize(window.localStorage.getItem(key)) || defaultValue | |
); | |
}, [typeof window]); | |
// ok now that we're safe from server bs, do the thing. | |
const [value, setValue] = useState(defaultValue); | |
useEffect(() => { | |
// check on storage event if this is what we care about. | |
const updateFromStorage = (ev) => { | |
// ev = { newValue, oldValue, key, ... }; reference: https://developer.mozilla.org/en-US/docs/Web/API/StorageEvent | |
if ( | |
// is this a key I care about | |
key === ev.key && | |
// prevent infinite loops | |
ev.newValue !== value | |
) { | |
setValue(transform.deserialize(ev.newValue)); | |
} | |
}; | |
// subscribe to localstorage events | |
window.addEventListener("storage", updateFromStorage); | |
return () => window.removeEventListener("storage", updateFromStorage); | |
}); | |
// to make sure we're not double-binding to the "value" | |
// we need to utilize an internal update pattern | |
const externalSetValue = (v) => { | |
// update storage | |
window.localStorage.setItem(key, v); | |
// update our version | |
setValue(v); | |
}; | |
// expose the state+setter pair the same as a "useState" | |
return [value, externalSetValue]; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Butts. This one doesn't quite work without a Provider. These hooks do a good job syncing cross tab through storage events, but you cannot have 2 on the same page -- they won't sync. If you use it ONCE and send the value to a provider, that'll do it.
EXAMPLE -- wrap your components in this ONCE: