UNPKG

@wener/console

Version:
256 lines (255 loc) 10.3 kB
// https://github.com/astoilkov/use-local-storage-state/blob/main/src/useLocalStorageState.ts function _array_like_to_array(arr, len) { if (len == null || len > arr.length) len = arr.length; for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; return arr2; } function _array_with_holes(arr) { if (Array.isArray(arr)) return arr; } function _array_without_holes(arr) { if (Array.isArray(arr)) return _array_like_to_array(arr); } function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _iterable_to_array(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _iterable_to_array_limit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){ _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally{ try { if (!_n && _i["return"] != null) _i["return"](); } finally{ if (_d) throw _e; } } return _arr; } function _non_iterable_rest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _non_iterable_spread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _sliced_to_array(arr, i) { return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest(); } function _to_consumable_array(arr) { return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread(); } function _unsupported_iterable_to_array(o, minLen) { if (!o) return; if (typeof o === "string") return _array_like_to_array(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen); } import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'; // in memory fallback used then `localStorage` throws an error export var inMemoryData = new Map(); export function useLocalStorageState(key, options) { var _useState = _sliced_to_array(useState(options === null || options === void 0 ? void 0 : options.defaultValue), 1), defaultValue = _useState[0]; // SSR support // - on the server, return a constant value // - this makes the implementation simpler and smaller because the `localStorage` object is // `undefined` on the server if (typeof window === 'undefined') { return [ defaultValue, function() {}, { isPersistent: true, removeItem: function() {} } ]; } var serializer = options === null || options === void 0 ? void 0 : options.serializer; // disabling ESLint because the above if statement can be executed only on the server. the value // of `window` can't change between calls. // eslint-disable-next-line react-hooks/rules-of-hooks return useBrowserLocalStorageState(key, defaultValue, options === null || options === void 0 ? void 0 : options.storageSync, serializer === null || serializer === void 0 ? void 0 : serializer.parse, serializer === null || serializer === void 0 ? void 0 : serializer.stringify); } function useBrowserLocalStorageState(key, defaultValue) { var storageSync = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : true, parse = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : parseJSON, stringify = arguments.length > 4 && arguments[4] !== void 0 ? arguments[4] : JSON.stringify; // store default value in localStorage: // - initial issue: https://github.com/astoilkov/use-local-storage-state/issues/26 // issues that were caused by incorrect initial and secondary implementations: // - https://github.com/astoilkov/use-local-storage-state/issues/30 // - https://github.com/astoilkov/use-local-storage-state/issues/33 if (!inMemoryData.has(key) && defaultValue !== undefined && goodTry(function() { return localStorage.getItem(key); }) === null) { // reasons for `localStorage` to throw an error: // - maximum quota is exceeded // - under Mobile Safari (since iOS 5) when the user enters private mode // `localStorage.setItem()` will throw // - trying to access localStorage object when cookies are disabled in Safari throws // "SecurityError: The operation is insecure." goodTry(function() { return localStorage.setItem(key, stringify(defaultValue)); }); } // we keep the `parsed` value in a ref because `useSyncExternalStore` requires a cached version var storageValue = useRef({ item: null, parsed: defaultValue }); var value = useSyncExternalStore(useCallback(function(onStoreChange) { var onChange = function(localKey) { if (key === localKey) { onStoreChange(); } }; callbacks.add(onChange); return function() { callbacks.delete(onChange); }; }, [ key ]), // eslint-disable-next-line react-hooks/exhaustive-deps function() { var _goodTry; var item = (_goodTry = goodTry(function() { return localStorage.getItem(key); })) !== null && _goodTry !== void 0 ? _goodTry : null; if (inMemoryData.has(key)) { storageValue.current = { item: item, parsed: inMemoryData.get(key) }; } else if (item !== storageValue.current.item) { var parsed; try { parsed = item === null ? defaultValue : parse(item); } catch (unused) { parsed = defaultValue; } storageValue.current = { item: item, parsed: parsed }; } return storageValue.current.parsed; }, // istanbul ignore next function() { return defaultValue; }); var setState = useCallback(function(newValue) { var value = _instanceof(newValue, Function) ? newValue(storageValue.current.parsed) : newValue; // reasons for `localStorage` to throw an error: // - maximum quota is exceeded // - under Mobile Safari (since iOS 5) when the user enters private mode // `localStorage.setItem()` will throw // - trying to access `localStorage` object when cookies are disabled in Safari throws // "SecurityError: The operation is insecure." try { localStorage.setItem(key, stringify(value)); inMemoryData.delete(key); } catch (unused) { inMemoryData.set(key, value); } triggerCallbacks(key); }, [ key, stringify ]); // - syncs change across tabs, windows, iframes // - the `storage` event is called only in all tabs, windows, iframe's except the one that // triggered the change useEffect(function() { if (!storageSync) { return undefined; } var onStorage = function(e) { if (e.storageArea === goodTry(function() { return localStorage; }) && e.key === key) { triggerCallbacks(key); } }; window.addEventListener('storage', onStorage); return function() { return window.removeEventListener('storage', onStorage); }; }, [ key, storageSync ]); return useMemo(function() { return [ value, setState, { isPersistent: value === defaultValue || !inMemoryData.has(key), removeItem: function removeItem() { goodTry(function() { return localStorage.removeItem(key); }); inMemoryData.delete(key); triggerCallbacks(key); } } ]; }, [ key, setState, value, defaultValue ]); } // notifies all instances using the same `key` to update var callbacks = new Set(); function triggerCallbacks(key) { var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined; try { for(var _iterator = _to_consumable_array(callbacks)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){ var callback = _step.value; callback(key); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally{ try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally{ if (_didIteratorError) { throw _iteratorError; } } } } // a wrapper for `JSON.parse()` that supports "undefined" value. otherwise, // `JSON.parse(JSON.stringify(undefined))` returns the string "undefined" not the value `undefined` function parseJSON(value) { return value === 'undefined' ? undefined : JSON.parse(value); } function goodTry(tryFn) { try { return tryFn(); } catch (unused) { return undefined; } }