useSyncedLocalStorageState.tsx 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import {useCallback, useEffect} from 'react';
  2. import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
  3. type SyncedLocalStorageEvent<S> = CustomEvent<{key: string; value: S}>;
  4. const SYNCED_STORAGE_EVENT = 'synced-local-storage';
  5. function isCustomEvent(event: Event): event is CustomEvent {
  6. return 'detail' in event;
  7. }
  8. function isSyncedLocalStorageEvent<S>(
  9. event: Event,
  10. key: string
  11. ): event is SyncedLocalStorageEvent<S> {
  12. return (
  13. isCustomEvent(event) &&
  14. event.type === SYNCED_STORAGE_EVENT &&
  15. event.detail.key === key
  16. );
  17. }
  18. /**
  19. * Same as `useLocalStorageState`, but notifies and reacts to state changes.
  20. * Use this you want to access local storage state from multiple components
  21. * on the same page.
  22. */
  23. export function useSyncedLocalStorageState<S>(
  24. key: string,
  25. initialState: S | ((value?: unknown, rawValue?: unknown) => S)
  26. ): [S, (value: S) => void] {
  27. const [value, setValue] = useLocalStorageState(key, initialState);
  28. const setValueAndNotify = useCallback(
  29. newValue => {
  30. setValue(newValue);
  31. // We use a custom event to notify all consumers of this hook
  32. window.dispatchEvent(
  33. new CustomEvent(SYNCED_STORAGE_EVENT, {detail: {key, value: newValue}})
  34. );
  35. },
  36. [key, setValue]
  37. );
  38. useEffect(() => {
  39. const handleNewSyncedLocalStorageEvent = (event: Event) => {
  40. if (isSyncedLocalStorageEvent<S>(event, key)) {
  41. setValue(event.detail.value);
  42. }
  43. };
  44. window.addEventListener(SYNCED_STORAGE_EVENT, handleNewSyncedLocalStorageEvent);
  45. return () => {
  46. window.removeEventListener(SYNCED_STORAGE_EVENT, handleNewSyncedLocalStorageEvent);
  47. };
  48. }, [key, setValue, value]);
  49. return [value, setValueAndNotify];
  50. }