import { LocalStorage } from '@/utils'; import _ from 'lodash'; import { useEffect, useRef, useState } from 'react'; import * as yup from 'yup'; const localStorageInstance = new LocalStorage(); type ISetter = (newValue: unknown) => unknown; function buildSetter(key: string, currentValue: unknown, callback: ISetter): ISetter { return function (newValue: unknown) { const updatedValue = _.isFunction(newValue) ? newValue(currentValue) : newValue; localStorageInstance.set(key, updatedValue); callback(updatedValue); return updatedValue; }; } /** * React hook for synchronizing a state value with browser LocalStorage. * * This hook provides a stateful value and a setter function, similar to `useState`, * but persists the value in LocalStorage under the specified key. The value is * automatically updated when LocalStorage changes (including from other tabs/windows). * * Use this hook only when you need to persist state across sessions or tabs. * If you only need to share state within a single session, consider using `useSessionStorage` * * Example: * ```tsx * const [value, setValue] = useLocalStorage('myKey', 'defaultValue'); * * // Read the value * console.log(value); * * // Update the value (persists to LocalStorage) * setValue('newValue'); * ``` * * @param key - The LocalStorage key to store the value under. Must be a non-empty string. * @param defaultValue - The default value to use if the key does not exist in LocalStorage. * @returns A tuple containing the current value and a setter function to update it. * */ function useLocalStorage(key: string, defaultValue?: unknown): [unknown, (newValue: unknown) => unknown] { yup.string().required().validateSync(key); const [state, setState] = useState(localStorageInstance.get(key) ?? defaultValue); const defaultValueRef = useRef(defaultValue); const storedDefaultValue = _.isEqual(defaultValueRef.current, defaultValue) ? defaultValueRef.current : defaultValue; const setterRef = useRef<{ key: string; value: unknown; setter: ISetter } | undefined>(); const storedSetter = setterRef.current; useEffect(() => { setState(localStorageInstance.get(key) ?? storedDefaultValue); }, [key, storedDefaultValue, setState]); useEffect(() => { function listener(value: unknown) { setState(value); } localStorageInstance.watch(key, listener); return () => { localStorageInstance.unwatch(key, listener); }; }, [key, setState]); if (key !== storedSetter?.key || !_.isEqual(state, storedSetter?.value)) { setterRef.current = { key, value: state, setter: buildSetter(key, state, setState) }; } return [state, setterRef.current?.setter ?? _.noop]; } export { useLocalStorage };