Skip to content

Instantly share code, notes, and snippets.

@JohnBra
Created July 4, 2022 22:44
Embed
What would you like to do?
Typed React useStorage hook for chrome extensions
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;
}
}
@JohnBra
Copy link
Author

JohnBra commented Jul 5, 2022

A usage example is wrapping the hook again for settings in sync storage like so:

import { Dispatch, SetStateAction } from 'react';
import { useStorage } from './useStorage';

type Settings = {
  darkMode: boolean;
}

/**
 * Returns a stateful settings value from sync storage, and a function to update it.
 */
export function useSettings(): [Settings, Dispatch<SetStateAction<Settings>>] {
  return useStorage<Settings>('userSettings', 'sync');
}

From there you can just import it in any of your react pages. E.g. Options, Popup, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment