diff --git a/__tests__/atomWithHash_spec.tsx b/__tests__/atomWithHash_spec.tsx
index 1491fa9..34eb5ae 100644
--- a/__tests__/atomWithHash_spec.tsx
+++ b/__tests__/atomWithHash_spec.tsx
@@ -133,6 +133,74 @@ describe('atomWithHash', () => {
expect(window.location.search).toEqual('?q=foo');
expect(window.location.hash).toEqual('#count=2');
});
+ window.history.back();
+ await waitFor(() => {
+ expect(window.location.pathname).toEqual('/');
+ expect(window.location.search).toEqual('');
+ expect(window.location.hash).toEqual('');
+ });
+ });
+
+ it('keeping current path only for one set', async () => {
+ const countAtom = atomWithHash('count', 0);
+
+ const Counter = () => {
+ const [count, setCount] = useAtom(countAtom);
+ useEffect(() => {
+ setCount(1, { setHash: 'replaceState' });
+ }, []);
+ return (
+ <>
+
count: {count}
+
+ >
+ );
+ };
+
+ window.history.pushState(null, '', '/?q=foo');
+ const { findByText, getByText } = render(
+
+
+ ,
+ );
+
+ await findByText('count: 1');
+ await waitFor(() => {
+ expect(window.location.pathname).toEqual('/');
+ expect(window.location.search).toEqual('?q=foo');
+ expect(window.location.hash).toEqual('#count=1');
+ });
+ fireEvent.click(getByText('button'));
+ await findByText('count: 2');
+ expect(window.location.pathname).toEqual('/');
+ expect(window.location.search).toEqual('?q=foo');
+ expect(window.location.hash).toEqual('#count=2');
+
+ window.history.pushState(null, '', '/another');
+ await waitFor(() => {
+ expect(window.location.pathname).toEqual('/another');
+ });
+
+ window.history.back();
+ await waitFor(() => {
+ expect(window.location.pathname).toEqual('/');
+ expect(window.location.search).toEqual('?q=foo');
+ expect(window.location.hash).toEqual('#count=2');
+ });
+ window.history.back();
+ await waitFor(() => {
+ expect(window.location.pathname).toEqual('/');
+ expect(window.location.search).toEqual('?q=foo');
+ expect(window.location.hash).toEqual('#count=1');
+ });
+ window.history.back();
+ await waitFor(() => {
+ expect(window.location.pathname).toEqual('/');
+ expect(window.location.search).toEqual('');
+ expect(window.location.hash).toEqual('');
+ });
});
it('should optimize value to prevent unnecessary re-renders', async () => {
diff --git a/package.json b/package.json
index 06e1838..3d2edeb 100644
--- a/package.json
+++ b/package.json
@@ -29,8 +29,10 @@
"compile": "microbundle build -f modern,umd --globals react=React",
"postcompile": "cp dist/index.modern.mjs dist/index.modern.js && cp dist/index.modern.mjs.map dist/index.modern.js.map",
"test": "run-s eslint tsc-test jest",
+ "test:debug": "run-s eslint tsc-test jest:debug",
"eslint": "eslint --ext .js,.ts,.tsx .",
"jest": "jest",
+ "jest:debug" : "node --inspect ./node_modules/jest/bin/jest.js --runInBand",
"tsc-test": "tsc --project . --noEmit",
"examples:01_minimal": "DIR=01_minimal EXT=js webpack serve",
"examples:02_typescript": "DIR=02_typescript EXT=tsx webpack serve",
diff --git a/src/atomWithHash.ts b/src/atomWithHash.ts
index 76b5d58..5fa7fb4 100644
--- a/src/atomWithHash.ts
+++ b/src/atomWithHash.ts
@@ -15,6 +15,37 @@ const safeJSONParse = (initialValue: unknown) => (str: string) => {
}
};
+export type SetHashOption =
+ | 'default'
+ | 'replaceState'
+ | ((searchParams: string) => void);
+
+export type AtomWithHashSetOptions = {
+ setHash?: SetHashOption;
+};
+
+export const setHashWithPush = (searchParams: string) => {
+ window.location.hash = searchParams;
+};
+
+export const setHashWithReplace = (searchParams: string): void => {
+ window.history.replaceState(
+ window.history.state,
+ '',
+ `${window.location.pathname}${window.location.search}#${searchParams}`,
+ );
+};
+
+function getSetHashFn(setHashOption?: SetHashOption) {
+ if (setHashOption === 'replaceState') {
+ return setHashWithReplace;
+ }
+ if (typeof setHashOption === 'function') {
+ return setHashOption;
+ }
+ return setHashWithPush;
+}
+
export function atomWithHash(
key: string,
initialValue: Value,
@@ -22,9 +53,13 @@ export function atomWithHash(
serialize?: (val: Value) => string;
deserialize?: (str: string) => Value;
subscribe?: (callback: () => void) => () => void;
- setHash?: 'default' | 'replaceState' | ((searchParams: string) => void);
+ setHash?: SetHashOption;
},
-): WritableAtom], void> {
+): WritableAtom<
+ Value,
+ [SetStateActionWithReset, AtomWithHashSetOptions?],
+ void
+> {
const serialize = options?.serialize || JSON.stringify;
const deserialize = options?.deserialize || safeJSONParse(initialValue);
@@ -36,22 +71,7 @@ export function atomWithHash(
window.removeEventListener('hashchange', callback);
};
});
- const setHashOption = options?.setHash;
- let setHash = (searchParams: string) => {
- window.location.hash = searchParams;
- };
- if (setHashOption === 'replaceState') {
- setHash = (searchParams) => {
- window.history.replaceState(
- window.history.state,
- '',
- `${window.location.pathname}${window.location.search}#${searchParams}`,
- );
- };
- }
- if (typeof setHashOption === 'function') {
- setHash = setHashOption;
- }
+
const isLocationAvailable =
typeof window !== 'undefined' && !!window.location;
@@ -77,7 +97,12 @@ export function atomWithHash(
});
return atom(
(get) => get(valueAtom),
- (get, set, update: SetStateActionWithReset) => {
+ (
+ get,
+ set,
+ update: SetStateActionWithReset,
+ setOptions?: AtomWithHashSetOptions,
+ ) => {
const nextValue =
typeof update === 'function'
? (update as (prev: Value) => Value | typeof RESET)(get(valueAtom))
@@ -91,6 +116,7 @@ export function atomWithHash(
set(strAtom, str);
searchParams.set(key, str);
}
+ const setHash = getSetHashFn(setOptions?.setHash ?? options?.setHash);
setHash(searchParams.toString());
},
);