Skip to content

Instantly share code, notes, and snippets.

@nfarina
Last active December 24, 2022 15:04
Show Gist options
  • Save nfarina/807fdb7f63e4f010c198c446591db3e8 to your computer and use it in GitHub Desktop.
Save nfarina/807fdb7f63e4f010c198c446591db3e8 to your computer and use it in GitHub Desktop.
Fix for Safari
import { useCallback, useEffect, useState } from "react";
// Adapted from:
// https://github.com/rehooks/local-storage/blob/master/src/use-localstorage.ts
type Setter<S> = (value: S) => any;
/**
* React hook to enable updates to state via localStorage.
* This updates when the {writeLocalStorage} function is used, when the returned function
* is called, or when the "storage" event is fired from another tab in the browser.
*
* @example
* ```js
* const MyComponent = () => {
* const [myStoredItem, setMyStoredItem] = useLocalStorage('myStoredItem');
* return (
* <p>{myStoredItem}</p>
* );
* };
* ```
*
* @export
* @param {string} key The key in the localStorage that you wish to watch.
* @returns An array containing the value associated with the key in position 0,
* and a function to set the value in position 1.
*/
export function useLocalStorage<S>(
key: string,
initialValue: S | (() => S),
): [S, Setter<S>] {
// The initialValue arg is only used if there is nothing in localStorage,
// otherwise we use the value in localStorage so state persists through a
// page refresh. We pass a function to useState so localStorage lookup only
// happens once.
const [item, setInnerItem] = useState<S>(() => {
const existingValue = localStorage.getItem(key);
if (existingValue != null) {
return JSON.parse(existingValue);
}
if (initialValue instanceof Function) {
return initialValue();
}
return initialValue;
});
// Create an event listener so we can be notified of changes to local state
// (only works if the other person is using useLocalStorage() as well).
const onLocalStorageChange = useCallback((event: Event) => {
if (event.type === LocalStorageChanged.eventName) {
const { detail } = event as LocalStorageChanged;
if (detail.key === key) {
const { value } = detail;
setInnerItem(value != null ? JSON.parse(value) : null);
}
} else if (event instanceof StorageEvent) {
if (event.key === key) {
const { newValue } = event;
setInnerItem(newValue != null ? JSON.parse(newValue) : null);
}
}
}, []);
// Return a wrapped version of useState's setter function that persists the
// new value to localStorage.
const setItem: Setter<S> = useCallback(value => {
setInnerItem(value);
writeLocalStorage(key, value != null ? JSON.stringify(value) : null);
}, []);
useEffect(() => {
// The custom storage event allows us to update our component
// when a change occurs in localStorage outside of our component
window.addEventListener(
LocalStorageChanged.eventName,
onLocalStorageChange,
);
// The storage event only works in the context of other documents (eg. other browser tabs)
window.addEventListener("storage", onLocalStorageChange);
return () => {
window.removeEventListener(
LocalStorageChanged.eventName,
onLocalStorageChange,
);
window.removeEventListener("storage", onLocalStorageChange);
};
}, []);
return [item, setItem];
}
//
// Low-level methods for modifying localStorage in a way that keeps our hook
// working.
//
export interface LocalStorageEventDetail {
key: string;
value: string | null;
}
/**
* Used for creating new events for LocalStorage. This enables us to
* have the ability of updating the LocalStorage from outside of the component,
* but still update the component without prop drilling or creating a dependency
* on a large library such as Redux.
*
* @class LocalStorageChanged
* @extends {CustomEvent<LocalStorageEventDetail>}
*/
export class LocalStorageChanged extends CustomEvent<LocalStorageEventDetail> {
public static eventName = "onLocalStorageChange";
constructor(detail: LocalStorageEventDetail) {
super(LocalStorageChanged.eventName, { detail });
}
}
/**
* Use this instead of directly using localStorage.setItem
* in order to correctly send events within the same window.
*
* @example
* ```js
* writeLocalStorage('hello', JSON.stringify({ name: 'world' }));
* const { name } = JSON.parse(localStorage.getItem('hello'));
* ```
*
* @export
* @param {string} key The key to write to in the localStorage.
* @param {string} value The value to write to in the localStorage.
*/
export function writeLocalStorage(key: string, value: string | null) {
if (value != null) {
localStorage.setItem(key, value);
} else {
localStorage.removeItem(key);
}
window.dispatchEvent(new LocalStorageChanged({ key, value }));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment