Created
July 4, 2022 22:44
-
-
Save JohnBra/c81451ea7bc9e77f8021beb4f198ab96 to your computer and use it in GitHub Desktop.
Typed React useStorage hook for chrome extensions
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 { Dispatch, SetStateAction, useState, useEffect, useCallback, useRef } from 'react'; | |
export type StorageArea = 'sync' | 'local'; | |
// custom hook to set chrome local/sync storage | |
// should also set a listener on this specific key | |
type SetValue<T> = Dispatch<SetStateAction<T>>; | |
/** | |
* Returns a stateful value from storage, and a function to update it. | |
*/ | |
export function useStorage<T>(key: string, initialValue: T, area: StorageArea = 'local'): [T, SetValue<T>] { | |
const [storedValue, setStoredValue] = useState<T>(initialValue); | |
useEffect(() => { | |
readStorage<T>(key, area).then(res => { | |
if (res) setStoredValue(res); | |
}); | |
chrome.storage.onChanged.addListener((changes, namespace) => { | |
if (namespace === area && changes.hasOwnProperty(key)) { | |
if (changes[key].newValue) setStoredValue(changes[key].newValue); | |
} | |
}); | |
}, []); | |
const setValueRef = useRef<SetValue<T>>(); | |
setValueRef.current = value => { | |
// Allow value to be a function, so we have the same API as useState | |
const newValue = value instanceof Function ? value(storedValue) : value; | |
// Save to storage | |
setStoredValue(prevState => { | |
setStorage<T>(key, newValue, area) | |
.then((success) => { | |
if (!success) setStoredValue(prevState); | |
}); | |
return newValue; | |
}); | |
} | |
// Return a wrapped version of useState's setter function that ... | |
// ... persists the new value to storage. | |
const setValue: SetValue<T> = useCallback( | |
value => setValueRef.current?.(value), | |
[], | |
) | |
return [storedValue, setValue]; | |
} | |
/** | |
* Retrieves value from chrome storage area | |
* | |
* @param key | |
* @param area - defaults to local | |
*/ | |
export async function readStorage<T>(key: string, area: StorageArea = 'local'): Promise<T | undefined> { | |
try { | |
const result = await chrome.storage[area].get(key); | |
return result?.[key]; | |
} catch (error) { | |
console.warn(`Error reading ${area} storage key "${key}":`, error); | |
return undefined; | |
} | |
} | |
/** | |
* Sets object in chrome storage area | |
* | |
* @param key | |
* @param value - value to be saved | |
* @param area - defaults to local | |
*/ | |
export async function setStorage<T>(key: string, value: T, area: StorageArea = 'local'): Promise<boolean> { | |
try { | |
await chrome.storage[area].set({ [key]: value }); | |
return true; | |
} catch (error) { | |
console.warn(`Error setting ${area} storage key "${key}":`, error); | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A usage example is wrapping the hook again for settings in sync storage like so:
From there you can just import it in any of your react pages. E.g. Options, Popup, etc.