From fcd93eb30ca5d449da2b7d40b2fc46860902960a Mon Sep 17 00:00:00 2001 From: koichik Date: Sat, 23 Jul 2022 17:57:23 +0900 Subject: [PATCH] fix: avoid lost the state when navigate to the same URL --- .../RecoilHistorySyncJSONNext.test.tsx | 109 ++++++++++----- .../RecoilHistorySyncTransitNext.test.tsx | 130 ++++++++++++------ src/history/useSyncHistory.ts | 17 ++- 3 files changed, 176 insertions(+), 80 deletions(-) diff --git a/src/history/RecoilHistorySyncJSONNext.test.tsx b/src/history/RecoilHistorySyncJSONNext.test.tsx index 3b294545..108a406f 100644 --- a/src/history/RecoilHistorySyncJSONNext.test.tsx +++ b/src/history/RecoilHistorySyncJSONNext.test.tsx @@ -82,28 +82,40 @@ describe('', () => { ) }) - describe('then, navigate (pushstate)', () => { + describe('then, navigate to the same URL (replacestate)', () => { beforeEach(() => { act(() => { - mockRouter.push('/next') - global.history.state.key = 'test2' + mockRouter.replace('/') }) }) - it('should be restored default value', async () => { - expect(getByTestId('foo').textContent).toBe('Foo') + it('should be rendered with same value', async () => { + expect(getByTestId('foo').textContent).toBe('FooFoo') }) - describe('then, backward', () => { + describe('then, navigate to the new URL (pushstate)', () => { beforeEach(() => { act(() => { - global.history.state.key = 'test1' - mockRouter.push('/') // back() is not supported yet + mockRouter.push('/next') + global.history.state.key = 'test2' }) }) - it('should be restored updated value', async () => { - expect(getByTestId('foo').textContent).toBe('FooFoo') + it('should be restored default value', async () => { + expect(getByTestId('foo').textContent).toBe('Foo') + }) + + describe('then, backward', () => { + beforeEach(() => { + act(() => { + global.history.state.key = 'test1' + mockRouter.push('/') // back() is not supported yet + }) + }) + + it('should be restored updated value', async () => { + expect(getByTestId('foo').textContent).toBe('FooFoo') + }) }) }) }) @@ -178,30 +190,43 @@ describe('', () => { expect(getByTestId('bar').textContent).toBe('FooBar') }) - describe('then, navigate (pushstate)', () => { + describe('then, navigate to the same URL (replacestate)', () => { beforeEach(() => { act(() => { - mockRouter.push('/next') - global.history.state.key = 'test2' + mockRouter.replace('/') }) }) - it('should be restored default values', async () => { - expect(getByTestId('bar').textContent).toBe('Foo') - expect(getByTestId('baz').textContent).toBe('Foo') + it('should be rendered with same values', async () => { + expect(getByTestId('baz').textContent).toBe('FooBaz') + expect(getByTestId('bar').textContent).toBe('FooBar') }) - describe('then, backward', () => { + describe('then, navigate to the new URL (pushstate)', () => { beforeEach(() => { act(() => { - global.history.state.key = 'test1' - mockRouter.push('/') // back() is not supported yet + mockRouter.push('/next') + global.history.state.key = 'test2' }) }) - it('should be restored updated values', async () => { - expect(getByTestId('bar').textContent).toBe('FooBar') - expect(getByTestId('baz').textContent).toBe('FooBaz') + it('should be restored default values', async () => { + expect(getByTestId('bar').textContent).toBe('Foo') + expect(getByTestId('baz').textContent).toBe('Foo') + }) + + describe('then, backward', () => { + beforeEach(() => { + act(() => { + global.history.state.key = 'test1' + mockRouter.push('/') // back() is not supported yet + }) + }) + + it('should be restored updated values', async () => { + expect(getByTestId('bar').textContent).toBe('FooBar') + expect(getByTestId('baz').textContent).toBe('FooBaz') + }) }) }) }) @@ -296,32 +321,46 @@ describe('', () => { expect(getByTestId('bar').textContent).toBe('BarBar') }) - describe('then, navigate (pushstate)', () => { + describe('then, navigate to the same URL (replacestate)', () => { beforeEach(() => { act(() => { - mockRouter.push('/next') - global.history.state.key = 'test2' + mockRouter.replace('/') }) }) - it('should be restored initial values', async () => { - expect(getByTestId('foo').textContent).toBe('Foo') - expect(getByTestId('bar').textContent).toBe('Bar') - expect(getByTestId('baz').textContent).toBe('Baz') + it('should be rendered with same values', async () => { + expect(getByTestId('foo').textContent).toBe('FooFoo') + expect(getByTestId('bar').textContent).toBe('BarBar') + expect(getByTestId('baz').textContent).toBe('BazBaz') }) - describe('then, backward', () => { + describe('then, navigate to the new URL (pushstate)', () => { beforeEach(() => { act(() => { - global.history.state.key = 'test1' - mockRouter.push('/') // back() is not supported yet + mockRouter.push('/next') + global.history.state.key = 'test2' }) }) it('should be restored initial values', async () => { - expect(getByTestId('foo').textContent).toBe('FooFoo') - expect(getByTestId('bar').textContent).toBe('BarBar') - expect(getByTestId('baz').textContent).toBe('BazBaz') + expect(getByTestId('foo').textContent).toBe('Foo') + expect(getByTestId('bar').textContent).toBe('Bar') + expect(getByTestId('baz').textContent).toBe('Baz') + }) + + describe('then, backward', () => { + beforeEach(() => { + act(() => { + global.history.state.key = 'test1' + mockRouter.push('/') // back() is not supported yet + }) + }) + + it('should be restored initial values', async () => { + expect(getByTestId('foo').textContent).toBe('FooFoo') + expect(getByTestId('bar').textContent).toBe('BarBar') + expect(getByTestId('baz').textContent).toBe('BazBaz') + }) }) }) }) diff --git a/src/history/RecoilHistorySyncTransitNext.test.tsx b/src/history/RecoilHistorySyncTransitNext.test.tsx index dd41b4f5..88e633c0 100644 --- a/src/history/RecoilHistorySyncTransitNext.test.tsx +++ b/src/history/RecoilHistorySyncTransitNext.test.tsx @@ -80,28 +80,40 @@ describe('', () => { ) }) - describe('then, navigate (pushstate)', () => { + describe('then, navigate to the same URL (replacestate)', () => { beforeEach(() => { act(() => { - mockRouter.push('/next') - global.history.state.key = 'test2' + mockRouter.replace('/') }) }) - it('should be restored default value', async () => { - expect(getByTestId('foo').textContent).toBe('Foo') + it('should be rendered with same value', async () => { + expect(getByTestId('foo').textContent).toBe('FooFoo') }) - describe('then, backward', () => { + describe('then, navigate to the new URL (pushstate)', () => { beforeEach(() => { act(() => { - global.history.state.key = 'test1' - mockRouter.push('/') // back() is not supported yet + mockRouter.push('/next') + global.history.state.key = 'test2' }) }) - it('should be restored updated value', async () => { - expect(getByTestId('foo').textContent).toBe('FooFoo') + it('should be restored default value', async () => { + expect(getByTestId('foo').textContent).toBe('Foo') + }) + + describe('then, backward', () => { + beforeEach(() => { + act(() => { + global.history.state.key = 'test1' + mockRouter.push('/') // back() is not supported yet + }) + }) + + it('should be restored updated value', async () => { + expect(getByTestId('foo').textContent).toBe('FooFoo') + }) }) }) }) @@ -161,28 +173,42 @@ describe('', () => { ) }) - describe('then, navigate (pushstate)', () => { + describe('then, navigate to the same URL (replacestate)', () => { beforeEach(() => { act(() => { - mockRouter.push('/next') - global.history.state.key = 'test2' + mockRouter.replace('/') }) }) - it('should be restored default value', async () => { - expect(getByTestId('foo').textContent).toBe(date1.toISOString()) + it('should be rendered with same value', async () => { + expect(getByTestId('foo').textContent).toBe(date2.toISOString()) }) - describe('then, backward', () => { + describe('then, navigate the new URL (pushstate)', () => { beforeEach(() => { act(() => { - global.history.state.key = 'test1' - mockRouter.push('/') // back() is not supported yet + mockRouter.push('/next') + global.history.state.key = 'test2' }) }) - it('should be restored updated value', async () => { - expect(getByTestId('foo').textContent).toBe(date2.toISOString()) + it('should be restored default value', async () => { + expect(getByTestId('foo').textContent).toBe(date1.toISOString()) + }) + + describe('then, backward', () => { + beforeEach(() => { + act(() => { + global.history.state.key = 'test1' + mockRouter.push('/') // back() is not supported yet + }) + }) + + it('should be restored updated value', async () => { + expect(getByTestId('foo').textContent).toBe( + date2.toISOString() + ) + }) }) }) }) @@ -241,28 +267,40 @@ describe('', () => { ) }) - describe('then, navigate (pushstate)', () => { + describe('then, navigate to the same URL (replacestate)', () => { beforeEach(() => { act(() => { - mockRouter.push('/next') - global.history.state.key = 'test2' + mockRouter.replace('/') }) }) - it('should be restored default value', async () => { - expect(getByTestId('foo').textContent).toBe('Foo') + it('should be rendered with same value', async () => { + expect(getByTestId('foo').textContent).toBe('Bar,Baz') }) - describe('then, backward', () => { + describe('then, navigate to the new URL (pushstate)', () => { beforeEach(() => { act(() => { - global.history.state.key = 'test1' - mockRouter.push('/') // back() is not supported yet + mockRouter.push('/next') + global.history.state.key = 'test2' }) }) - it('should be restored updated value', async () => { - expect(getByTestId('foo').textContent).toBe('Bar,Baz') + it('should be restored default value', async () => { + expect(getByTestId('foo').textContent).toBe('Foo') + }) + + describe('then, backward', () => { + beforeEach(() => { + act(() => { + global.history.state.key = 'test1' + mockRouter.push('/') // back() is not supported yet + }) + }) + + it('should be restored updated value', async () => { + expect(getByTestId('foo').textContent).toBe('Bar,Baz') + }) }) }) }) @@ -324,28 +362,40 @@ describe('', () => { ) }) - describe('then, navigate (pushstate)', () => { + describe('then, navigate to the same URL (replacestate)', () => { beforeEach(() => { act(() => { - mockRouter.push('/next') - global.history.state.key = 'test2' + mockRouter.replace('/') }) }) - it('should be restored default value', async () => { - expect(getByTestId('foo').textContent).toBe('foo,Foo') + it('should be rendered with same value', async () => { + expect(getByTestId('foo').textContent).toBe('bar,Bar,baz,Baz') }) - describe('then, backward', () => { + describe('then, navigate to the new URL (pushstate)', () => { beforeEach(() => { act(() => { - global.history.state.key = 'test1' - mockRouter.push('/') // back() is not supported yet + mockRouter.push('/next') + global.history.state.key = 'test2' }) }) - it('should be restored updated value', async () => { - expect(getByTestId('foo').textContent).toBe('bar,Bar,baz,Baz') + it('should be restored default value', async () => { + expect(getByTestId('foo').textContent).toBe('foo,Foo') + }) + + describe('then, backward', () => { + beforeEach(() => { + act(() => { + global.history.state.key = 'test1' + mockRouter.push('/') // back() is not supported yet + }) + }) + + it('should be restored updated value', async () => { + expect(getByTestId('foo').textContent).toBe('bar,Bar,baz,Baz') + }) }) }) }) diff --git a/src/history/useSyncHistory.ts b/src/history/useSyncHistory.ts index d3c3c6be..0b56a097 100644 --- a/src/history/useSyncHistory.ts +++ b/src/history/useSyncHistory.ts @@ -17,7 +17,7 @@ export type SaveItems = ( export type LoadItems = (historyKey: string) => Record -export type ListenChangeHistory = (handler: () => void) => () => void +export type ListenChangeHistory = (handler: (url: string) => void) => () => void export type RecoilHistorySyncOptions = { storeKey?: StoreKey @@ -70,8 +70,8 @@ export function useSyncHistory({ const listen: ListenToItems = useCallback( ({ updateAllKnownItems }) => { - return listenChangeHistory(() => { - // saving previouse history associated items + return listenChangeHistory((url) => { + // saving previouse history associated items. if (historyInfo.current) { const { historyKey, historyItems } = historyInfo.current saveItems(historyKey, historyItems) @@ -80,14 +80,21 @@ export function useSyncHistory({ const historyKey = getHistoryKey() assertString(historyKey) - // clear history associated items (by navigation) + // history has changed by navigation. if (historyKey === historyInfo.current?.historyKey) { + // nothing to do if URL wasn't changed. + if (url === globalThis.location?.pathname) { + return + } + + // clear history associated items from Recoil store. historyInfo.current = undefined updateAllKnownItems(new Map()) return } - // loading history associated items (by forward/back) + // history has changed by forward/backward. + // loading next history associated items from SessionStorage. const historyItems = loadItems(historyKey) historyInfo.current = { historyKey, historyItems } updateAllKnownItems(new Map(Object.entries(historyItems)))