From 35e9053c5dd419fcd091eb12857ec819292ffe71 Mon Sep 17 00:00:00 2001 From: Daishi Kato Date: Sat, 11 Mar 2023 00:25:36 +0900 Subject: [PATCH] refactor: atomWithHash (#13) --- src/atomWithHash.ts | 108 ++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 70 deletions(-) diff --git a/src/atomWithHash.ts b/src/atomWithHash.ts index a3d02ba..6081098 100644 --- a/src/atomWithHash.ts +++ b/src/atomWithHash.ts @@ -1,11 +1,6 @@ -// TODO consider refactoring without atomWithStorage - +import { atom } from 'jotai/vanilla'; import type { WritableAtom } from 'jotai/vanilla'; -import { - atomWithStorage, - unstable_NO_STORAGE_VALUE as NO_STORAGE_VALUE, - RESET, -} from 'jotai/vanilla/utils'; +import { RESET } from 'jotai/vanilla/utils'; type SetStateActionWithReset = | Value @@ -17,35 +12,13 @@ export function atomWithHash( initialValue: Value, options?: { serialize?: (val: Value) => string; - deserialize?: (str: string | null) => Value | typeof NO_STORAGE_VALUE; - /** - * @deprecated Use {@link options.setHash} with 'replaceState' instead - */ - replaceState?: boolean; + deserialize?: (str: string) => Value; subscribe?: (callback: () => void) => () => void; setHash?: 'default' | 'replaceState' | ((searchParams: string) => void); }, ): WritableAtom], void> { const serialize = options?.serialize || JSON.stringify; - - let cachedStr: string | undefined = serialize(initialValue); - let cachedValue: any = initialValue; - - const deserialize = - options?.deserialize || - ((str) => { - str = str || ''; - if (cachedStr !== str) { - try { - cachedValue = JSON.parse(str); - } catch { - return NO_STORAGE_VALUE; - } - cachedStr = str; - } - return cachedValue; - }); - + const deserialize = options?.deserialize || JSON.parse; const subscribe = options?.subscribe || ((callback) => { @@ -54,15 +27,11 @@ export function atomWithHash( window.removeEventListener('hashchange', callback); }; }); - if (options?.replaceState) { - // eslint-disable-next-line no-console - console.warn('[DEPRECATED] Use setHash=replaceState instead'); - } const setHashOption = options?.setHash; let setHash = (searchParams: string) => { window.location.hash = searchParams; }; - if (setHashOption === 'replaceState' || options?.replaceState) { + if (setHashOption === 'replaceState') { setHash = (searchParams) => { window.history.replaceState( null, @@ -74,42 +43,41 @@ export function atomWithHash( if (typeof setHashOption === 'function') { setHash = setHashOption; } - const hashStorage = { - getItem: (k: string) => { - if (typeof window === 'undefined' || !window.location) { - return NO_STORAGE_VALUE; - } - const searchParams = new URLSearchParams(window.location.hash.slice(1)); - const storedValue = searchParams.get(k); - return deserialize(storedValue); - }, - setItem: (k: string, newValue: Value) => { + const strAtom = atom(null); + strAtom.onMount = (setAtom) => { + if (typeof window === 'undefined' || !window.location) { + return undefined; + } + const callback = () => { const searchParams = new URLSearchParams(window.location.hash.slice(1)); - const serializedParamValue = serialize(newValue); - searchParams.set(k, serializedParamValue); - setHash(searchParams.toString()); - // Update local cache when setItem is called directly - cachedStr = serializedParamValue; - cachedValue = newValue; - }, - removeItem: (k: string) => { + const str = searchParams.get(key); + setAtom(str); + }; + const unsubscribe = subscribe(callback); + callback(); + return unsubscribe; + }; + const valueAtom = atom((get) => { + const str = get(strAtom); + return str === null ? initialValue : deserialize(str); + }); + return atom( + (get) => get(valueAtom), + (get, set, update: SetStateActionWithReset) => { + const nextValue = + typeof update === 'function' + ? (update as (prev: Value) => Value | typeof RESET)(get(valueAtom)) + : update; const searchParams = new URLSearchParams(window.location.hash.slice(1)); - searchParams.delete(k); + if (nextValue === RESET) { + set(strAtom, null); + searchParams.delete(key); + } else { + const str = serialize(nextValue); + set(strAtom, str); + searchParams.set(key, str); + } setHash(searchParams.toString()); }, - subscribe: (k: string, setValue: (v: Value) => void) => { - const callback = () => { - const searchParams = new URLSearchParams(window.location.hash.slice(1)); - const str = searchParams.get(k); - if (str !== null) { - setValue(deserialize(str)); - } else { - setValue(initialValue); - } - }; - return subscribe(callback); - }, - }; - - return atomWithStorage(key, initialValue, hashStorage); + ); }