UNPKG

@redocly/theme

Version:

Shared UI components lib

96 lines (79 loc) 2.19 kB
import { useSyncExternalStore } from 'react'; import { isBrowser } from '../utils/js-utils'; type Store<T> = { getValue: (defaultValue: T) => T; setValue: (next: T) => void; subscribe: (callback: () => void) => Unsubscribe; }; type Unsubscribe = () => void; type CreateStoreProps<T> = { storageKey: string; storageType?: 'localStorage' | 'sessionStorage'; serializer?: { parse: (value: string) => T; serialize: (value: T) => string; }; }; export function createStore<T>({ storageKey, storageType = 'localStorage', serializer = { parse: (value: string) => JSON.parse(value) as T, serialize: (value: T) => JSON.stringify(value), }, }: CreateStoreProps<T>): Store<T> { const subscribers = new Set<() => void>(); let cachedValue: T; const shouldSerialize = (value: unknown) => typeof value !== 'string'; const getValue = (defaultValue: T): T => { if (!isBrowser()) return defaultValue; if (cachedValue !== undefined) return cachedValue; const value = window[storageType].getItem(storageKey); if (!value) { cachedValue = defaultValue; return cachedValue; } if (!shouldSerialize(defaultValue)) { cachedValue = value as T; return cachedValue; } try { cachedValue = JSON.parse(value) as T; } catch { cachedValue = defaultValue; } return cachedValue; }; const setValue = (next: T): void => { if (!isBrowser()) return; try { window[storageType].setItem( storageKey, shouldSerialize(next) ? serializer.serialize(next) : (next as string), ); cachedValue = next; subscribers.forEach((callback) => callback()); } catch { return; } }; const subscribe = (callback: () => void): Unsubscribe => { subscribers.add(callback); return () => { subscribers.delete(callback); }; }; return { getValue, setValue, subscribe, }; } export function useStore<T>(store: Store<T>, defaultValue: T) { const value = useSyncExternalStore( store.subscribe, () => store.getValue(defaultValue), () => defaultValue, ); return [value, store.setValue] as const; }