Skip to content

Commit

Permalink
fix: fix bug with state out of sync with useHistory: true
Browse files Browse the repository at this point in the history
  • Loading branch information
asmyshlyaev177 committed Dec 10, 2024
1 parent 171bcf9 commit 072712f
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 13 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "state-in-url",
"version": "4.1.7",
"version": "4.2.0",
"description": "Store state in URL as in object, types and structure are preserved, with TS validation. Same API as React.useState, wthout any hasssle or boilerplate. Next.js@14-15 and react-router@6-7.",
"homepage": "https://state-in-url.dev",
"repository": {
Expand Down
29 changes: 24 additions & 5 deletions packages/urlstate/next/useUrlState/useUrlState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import { useRouter } from "next/navigation";
import { useSearchParams } from "next/navigation";
import React from "react";

import { parseSPObj } from "../../parseSPObj";
import { useUrlStateBase } from "../../useUrlStateBase";
Expand Down Expand Up @@ -147,9 +147,28 @@ export function useUrlState<T extends JSONCompatible>(
}
: { scroll: params?.scroll, replace: params?.replace };

const router = (_useHistory === undefined ? true : !!_useHistory)
? routerHistory
: useRouter();
const nextRouter = useRouter();

const router = React.useMemo(
() => ({
push: (...args: Parameters<typeof nextRouter.push>) => {
if (_useHistory === undefined ? true : !!_useHistory) {
routerHistory.push(...args);
} else {
nextRouter.push(...args);
}
},
replace: (...args: Parameters<typeof nextRouter.replace>) => {
if (_useHistory === undefined ? true : !!_useHistory) {
routerHistory.replace(...args);
} else {
nextRouter.replace(...args);
}
},
}),
[nextRouter],
);

const {
state,
updateState,
Expand All @@ -174,7 +193,7 @@ export function useUrlState<T extends JSONCompatible>(
);

const sp = useSearchParams();
React.useEffect(() => {
React.useLayoutEffect(() => {
updateState(
filterUnknownParams(
_defaultState,
Expand Down
10 changes: 10 additions & 0 deletions packages/urlstate/useUrlStateBase/useUrlStateBase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ describe('useUrlStateBase', () => {
result.current.updateUrl({ ...shape, num: 50 });
});

await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(result.current.state).toStrictEqual({ ...shape, num: 50 });
Expand All @@ -258,6 +259,7 @@ describe('useUrlStateBase', () => {
result.current.updateUrl({ num: 50 });
});

await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(result.current.state).toStrictEqual({ ...shape, num: 50 });
Expand All @@ -279,6 +281,7 @@ describe('useUrlStateBase', () => {
result.current.updateUrl();
});

await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(result.current.state).toStrictEqual({ ...shape, num: 50 });
Expand All @@ -298,6 +301,7 @@ describe('useUrlStateBase', () => {
result.current.updateUrl((curr) => ({ ...curr, num: 50 }));
});

await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(result.current.state).toStrictEqual({ ...shape, num: 50 });
Expand Down Expand Up @@ -337,6 +341,7 @@ describe('useUrlStateBase', () => {
result.current.updateUrl(newState);
});

await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(router.push).toHaveBeenCalledTimes(1);
Expand All @@ -359,6 +364,7 @@ describe('useUrlStateBase', () => {
});
});

await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(result.current.state).toStrictEqual(newState);
Expand Down Expand Up @@ -439,6 +445,7 @@ describe('useUrlStateBase', () => {
result.current.updateUrl({ num: 50 });
});
await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(router.push).toHaveBeenCalledTimes(1)
expect(result.current.state).toStrictEqual({ ...shape, num: 50 });
Expand All @@ -455,6 +462,7 @@ describe('useUrlStateBase', () => {
result.current.reset();
})
await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(router.replace).not.toHaveBeenCalled()
expect(router.push).toHaveBeenCalledTimes(2)
Expand Down Expand Up @@ -483,6 +491,7 @@ describe('useUrlStateBase', () => {
result.current.reset();
})
await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(router.replace).not.toHaveBeenCalled()
expect(router.push).toHaveBeenCalledTimes(1)
Expand Down Expand Up @@ -511,6 +520,7 @@ describe('useUrlStateBase', () => {
result.current.reset({ replace: true });
})
await vi.advanceTimersByTime(700);
await vi.advanceTimersByTime(700);

expect(router.push).not.toHaveBeenCalled()
expect(router.replace).toHaveBeenCalledTimes(1)
Expand Down
16 changes: 9 additions & 7 deletions packages/urlstate/useUrlStateBase/useUrlStateBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,19 @@ export function useUrlStateBase<T extends JSONCompatible>(
const { replace, ..._rest } = options || {};
queue.current.push([replace ? "replace" : "push", newUrl, _rest]);

// TODO: run instantly first time OR run leading calls first and wait after
// can try to use Promise instead
clearTimeout(timer.current);
timer.current = setTimeout(() => {
queueMicrotask(() => {
if (!queue.current.length) return;

queueMicrotask(() => {
if (!queue.current.length) return;
timer.current = setTimeout(() => {
const upd = queue.current.at(-1);
queue.current = [];

const [method, url, opts] = upd || [];
router[method!](url!, opts);
});
}, TIMEOUT);
router[upd![0]](upd![1], upd![2]);
}, TIMEOUT);
});
},
[router, stringify, getState],
);
Expand Down

0 comments on commit 072712f

Please sign in to comment.