Skip to content

Commit

Permalink
refactor: atomWithHash (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
dai-shi authored Mar 10, 2023
1 parent 6dfa57e commit 35e9053
Showing 1 changed file with 38 additions and 70 deletions.
108 changes: 38 additions & 70 deletions src/atomWithHash.ts
Original file line number Diff line number Diff line change
@@ -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> =
| Value
Expand All @@ -17,35 +12,13 @@ export function atomWithHash<Value>(
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<Value, [SetStateActionWithReset<Value>], 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) => {
Expand All @@ -54,15 +27,11 @@ export function atomWithHash<Value>(
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,
Expand All @@ -74,42 +43,41 @@ export function atomWithHash<Value>(
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<string | null>(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<Value>) => {
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);
);
}

0 comments on commit 35e9053

Please sign in to comment.