-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ to handle multiple prevent exit (#805)
* ✨ to handle multiple prevent exit * 👌 match in mode standard * 👌 fix comments * 👌 add PreventExitListenerResult to export * 👌 some types and extras * 👌 add some more
- Loading branch information
1 parent
59a0597
commit c7562a4
Showing
13 changed files
with
206 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { useEffect, useMemo } from 'react'; | ||
import { PreventExitListenerResult, createPreventExitListener } from './store'; | ||
import { useObjectMemo } from '../hooks'; | ||
|
||
/** | ||
* Custom hook that allows preventing the user from exiting the page or navigating away. | ||
* | ||
* @param preventExit - A boolean value indicating whether to prevent the user from exiting the page or not. | ||
* | ||
* @example | ||
* ```tsx | ||
* function MyComponent() { | ||
* const { allowRedirect } = usePreventExit(true); | ||
* return <div onClick={allowRedirect}>My Component</div>; | ||
* } | ||
* ``` | ||
*/ | ||
export function usePreventExit( | ||
preventExit: boolean, | ||
): Pick<PreventExitListenerResult, 'allowRedirect'> { | ||
const { cleanup, setPreventExit, allowRedirect } = useMemo(createPreventExitListener, []); | ||
useEffect(() => setPreventExit(preventExit), [preventExit]); | ||
useEffect(() => cleanup, []); | ||
return useObjectMemo({ allowRedirect }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './hooks'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
const keys: Array<symbol> = []; | ||
const allPreventExitState: Record<symbol, boolean> = {}; | ||
|
||
function arePreventExitRemaining(): boolean { | ||
if (keys.map((key) => allPreventExitState[key]).every((i) => i === false)) { | ||
window.onbeforeunload = null; | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
function publish(id: symbol, preventExit: boolean): void { | ||
allPreventExitState[id] = preventExit; | ||
if (!arePreventExitRemaining()) | ||
window.onbeforeunload = (e) => { | ||
e.preventDefault(); | ||
return 'prevent-exit'; | ||
}; | ||
} | ||
|
||
/** | ||
* Returns a listener which can used to calculate the state of prevent exit. | ||
*/ | ||
export interface PreventExitListenerResult { | ||
/** | ||
* Callback used to set the value indicating if direct exit of the page is currently allowed or not. | ||
*/ | ||
setPreventExit: (preventExit: boolean) => void; | ||
/** | ||
* Allows the user to leave the page without confirmation temporarily. | ||
* This should be used when the developer wants to explicitly allow navigation. | ||
*/ | ||
allowRedirect: () => void; | ||
/** | ||
* Performs garbage collection by removing the preventExit state associated with the component. | ||
* This should be used when the component is unmounted. | ||
*/ | ||
cleanup: () => void; | ||
} | ||
|
||
/** | ||
* Creates a listener function that manages the preventExit state of a component. | ||
*/ | ||
export function createPreventExitListener(): PreventExitListenerResult { | ||
const key = Symbol('PreventExitListener'); | ||
allPreventExitState[key] = true; | ||
keys.push(key); | ||
|
||
return { | ||
setPreventExit: (preventExit) => publish(key, preventExit), | ||
allowRedirect: () => { | ||
window.onbeforeunload = null; | ||
}, | ||
cleanup: () => { | ||
delete allPreventExitState[key]; | ||
arePreventExitRemaining(); | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
export * from './PreventExit'; | ||
export * from './apps'; | ||
export * from './hooks'; | ||
export * from './i18n'; | ||
export * from './state'; | ||
export * from './theme'; | ||
export * from './utils'; | ||
export * from './hooks'; | ||
export * from './apps'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import { usePreventExit } from '../../src/PreventExit/hooks'; | ||
import { createPreventExitListener } from '../../src/PreventExit/store'; | ||
|
||
jest.mock('../../src/PreventExit/store', () => ({ | ||
createPreventExitListener: jest.fn(() => ({ | ||
cleanup: jest.fn(), | ||
setPreventExit: jest.fn(), | ||
allowRedirect: jest.fn(), | ||
})), | ||
})); | ||
describe('PreventExit hook usePreventExit', () => { | ||
let spyCreatePreventExitListener: jest.SpyInstance<ReturnType<typeof createPreventExitListener>>; | ||
beforeEach(() => { | ||
spyCreatePreventExitListener = jest.spyOn( | ||
{ createPreventExitListener }, | ||
'createPreventExitListener', | ||
); | ||
}); | ||
afterEach(() => jest.clearAllMocks()); | ||
|
||
it('should clean up when unmount', () => { | ||
const { unmount } = renderHook(() => usePreventExit(true)); | ||
unmount(); | ||
expect(spyCreatePreventExitListener.mock.results.at(-1)?.value.cleanup).toHaveBeenCalledTimes( | ||
1, | ||
); | ||
}); | ||
|
||
it('should set preventExit when value changes', async () => { | ||
const { rerender } = renderHook((props) => usePreventExit(props), { | ||
initialProps: true, | ||
}); | ||
expect( | ||
spyCreatePreventExitListener.mock.results.at(-1)?.value.setPreventExit, | ||
).toHaveBeenCalledTimes(1); | ||
rerender(false); | ||
expect( | ||
spyCreatePreventExitListener.mock.results.at(-1)?.value.setPreventExit, | ||
).toHaveBeenCalledTimes(2); | ||
rerender(false); | ||
expect( | ||
spyCreatePreventExitListener.mock.results.at(-1)?.value.setPreventExit, | ||
).toHaveBeenCalledTimes(2); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { createPreventExitListener } from '../../src/PreventExit/store'; | ||
|
||
describe('preventExitStore', () => { | ||
let listener1: ReturnType<typeof createPreventExitListener>; | ||
let listener2: ReturnType<typeof createPreventExitListener>; | ||
|
||
beforeEach(() => { | ||
listener1 = createPreventExitListener(); | ||
listener2 = createPreventExitListener(); | ||
}); | ||
|
||
afterEach(() => { | ||
listener1.cleanup(); | ||
listener2.cleanup(); | ||
}); | ||
|
||
it('should prevent exit: listener 1', () => { | ||
listener1.setPreventExit(true); | ||
listener2.setPreventExit(true); | ||
expect(window.onbeforeunload).not.toBe(null); | ||
listener1.setPreventExit(true); | ||
listener2.setPreventExit(false); | ||
expect(window.onbeforeunload).not.toBe(null); | ||
listener1.setPreventExit(false); | ||
listener2.setPreventExit(true); | ||
expect(window.onbeforeunload).not.toBe(null); | ||
listener1.setPreventExit(false); | ||
listener2.setPreventExit(false); | ||
expect(window.onbeforeunload).toBe(null); | ||
}); | ||
|
||
it('should allow redirect: listener 1', () => { | ||
const preventExit = [true, false]; | ||
preventExit.forEach((i) => { | ||
preventExit.forEach((j) => { | ||
listener1.setPreventExit(i); | ||
listener2.setPreventExit(j); | ||
listener1.allowRedirect(); | ||
expect(window.onbeforeunload).toBe(null); | ||
}); | ||
}); | ||
}); | ||
|
||
it('should allow redirect: lister 2', () => { | ||
listener2.cleanup(); | ||
listener1.cleanup(); | ||
expect(window.onbeforeunload).toBe(null); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters