Skip to content

Commit 7e273b5

Browse files
committed
Add use-unsaved
1 parent b28088f commit 7e273b5

File tree

6 files changed

+765
-9
lines changed

6 files changed

+765
-9
lines changed

bun.lock

Lines changed: 711 additions & 3 deletions
Large diffs are not rendered by default.

eslint.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { config } from 'eslint-config'
2+
3+
export default config()

hooks/use-unsaved.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useEffect } from 'react'
2+
import type { Router } from 'next/router'
3+
4+
/**
5+
* Don't let a user navigate or close the page if changes aren't saved
6+
* @param unsaved Are there unsaved changes?
7+
* @param nextRouter If using NextJS, the next router (prevents NextJS navigation)
8+
*/
9+
export function useUnsaved (unsaved?: boolean, nextRouter?: Router): void {
10+
useEffect(() => {
11+
if (unsaved) {
12+
/**
13+
* If the tab is closed with unsaved changes
14+
*/
15+
function preventUnsavedClose (e: Event): void {
16+
e.preventDefault()
17+
}
18+
19+
/**
20+
* If a Next.JS SPA nav is started with unsaved changes
21+
* @param url The new URL (to check if simply changing page props rather than a page)
22+
* @throws {Error} To prevent page navigation
23+
*/
24+
function preventUnsavedNav (url: string): void {
25+
const urlSplit = url.split('?')[0]!.split('/')
26+
if (nextRouter?.pathname.split('/').every((subroute, i) => (subroute.startsWith('[') && subroute.endsWith(']')) || subroute === urlSplit[i])) return
27+
28+
if (!confirm('Changes you made may not be saved.')) throw new Error('Navigation Canceled')
29+
}
30+
31+
nextRouter?.events.on('routeChangeStart', preventUnsavedNav)
32+
window.addEventListener('beforeunload', preventUnsavedClose)
33+
return () => {
34+
nextRouter?.events.off('routeChangeStart', preventUnsavedNav)
35+
window.removeEventListener('beforeunload', preventUnsavedClose)
36+
}
37+
}
38+
}, [unsaved])
39+
}

index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ export {
1111
export {
1212
useDebouncedState
1313
} from './hooks/use-debounced-state'
14+
15+
export {
16+
useUnsaved
17+
} from './hooks/use-unsaved'

package.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
{
22
"name": "react-exo-hooks",
33
"module": "index.ts",
4-
"type": "module",
54
"devDependencies": {
6-
"@types/bun": "latest"
5+
"@types/bun": "latest",
6+
"@types/next": "^9.0.0",
7+
"eslint": "^9.30.1",
8+
"eslint-config": "github:exoRift/eslint-config",
9+
"eslint-plugin-jsdoc": "^51.3.4"
710
},
811
"peerDependencies": {
12+
"react": "^19.1.0",
913
"typescript": "^5"
1014
},
11-
"dependencies": {
12-
"react": "^19.1.0"
13-
}
15+
"type": "module"
1416
}

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"compilerOptions": {
33
// Environment setup & latest features
4-
"lib": ["ESNext"],
4+
"lib": ["ESNext", "DOM"],
55
"target": "ESNext",
66
"module": "Preserve",
77
"moduleDetection": "force",

0 commit comments

Comments
 (0)