-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add navigation blocking when forms are dirty and not saved
* Add hooks to block and display prompt on navigation * Add components to indicate which form is blocking * Update connection replication and transformation components to block on edit * Update main page component to show prompt
- Loading branch information
Showing
9 changed files
with
143 additions
and
2 deletions.
There are no files selected for viewing
25 changes: 25 additions & 0 deletions
25
airbyte-webapp/src/components/FormNavigationBlocker/index.tsx
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 { useLayoutEffect, useMemo } from "react"; | ||
import { uniqueId } from "lodash"; | ||
import { useLocation } from "react-router-dom"; | ||
|
||
import { useBlockingFormsById } from "hooks/useFormNavigationBlocking"; | ||
|
||
interface Props { | ||
block: boolean; | ||
} | ||
|
||
const FormNavigationBlocker: React.FC<Props> = ({ block }) => { | ||
const location = useLocation(); | ||
const [blockingFormsById, setBlockingFormsById] = useBlockingFormsById(); | ||
const id = useMemo(() => `${location.pathname}__${uniqueId("form_")}`, [location.pathname]); | ||
|
||
useLayoutEffect(() => { | ||
if (!!blockingFormsById?.[id] !== block) { | ||
setBlockingFormsById({ ...blockingFormsById, [id]: block }); | ||
} | ||
}, [id, block, setBlockingFormsById, blockingFormsById]); | ||
|
||
return null; | ||
}; | ||
|
||
export default FormNavigationBlocker; |
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,40 @@ | ||
import type { Blocker, History, Transition } from "history"; | ||
|
||
import { ContextType, useContext, useEffect } from "react"; | ||
import { Navigator as BaseNavigator, UNSAFE_NavigationContext as NavigationContext } from "react-router-dom"; | ||
|
||
interface Navigator extends BaseNavigator { | ||
block: History["block"]; | ||
} | ||
|
||
type NavigationContextWithBlock = ContextType<typeof NavigationContext> & { navigator: Navigator }; | ||
|
||
/** | ||
* @source https://github.com/remix-run/react-router/commit/256cad70d3fd4500b1abcfea66f3ee622fb90874 | ||
*/ | ||
export const useBlocker = (blocker: Blocker, when = true) => { | ||
const { navigator } = useContext(NavigationContext) as NavigationContextWithBlock; | ||
|
||
useEffect(() => { | ||
if (!when) { | ||
return; | ||
} | ||
|
||
const unblock = navigator.block((tx: Transition) => { | ||
const autoUnblockingTx = { | ||
...tx, | ||
retry() { | ||
// Automatically unblock the transition so it can play all the way | ||
// through before retrying it. TODO: Figure out how to re-enable | ||
// this block if the transition is cancelled for some reason. | ||
unblock(); | ||
tx.retry(); | ||
}, | ||
}; | ||
|
||
blocker(autoUnblockingTx); | ||
}); | ||
|
||
return unblock; | ||
}, [navigator, blocker, when]); | ||
}; |
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,35 @@ | ||
import type { Transition } from "history"; | ||
|
||
import { useCallback } from "react"; | ||
|
||
import { useBlocker } from "./useBlocker"; | ||
|
||
/** | ||
* @source https://github.com/remix-run/react-router/issues/8139#issuecomment-1021457943 | ||
*/ | ||
export const usePrompt = ( | ||
message: string | ((location: Transition["location"], action: Transition["action"]) => string), | ||
when = true, | ||
onConfirm?: () => void | ||
) => { | ||
const blocker = useCallback( | ||
(tx: Transition) => { | ||
let response; | ||
if (typeof message === "function") { | ||
response = message(tx.location, tx.action); | ||
if (typeof response === "string") { | ||
response = window.confirm(response); | ||
} | ||
} else { | ||
response = window.confirm(message); | ||
} | ||
if (response) { | ||
onConfirm?.(); | ||
tx.retry(); | ||
} | ||
}, | ||
[message, onConfirm] | ||
); | ||
|
||
return useBlocker(blocker, when); | ||
}; |
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,23 @@ | ||
import { useCallback, useMemo } from "react"; | ||
import { createGlobalState } from "react-use"; | ||
|
||
import { usePrompt } from "./router/usePrompt"; | ||
|
||
export const useBlockingFormsById = createGlobalState<Record<string, boolean>>({}); | ||
|
||
const useFormNavigationBlockingPrompt = () => { | ||
const [blockingFormsById, setBlockingFormsById] = useBlockingFormsById(); | ||
|
||
const isFormBlocking = useMemo( | ||
() => Object.values(blockingFormsById ?? {}).reduce((acc, value) => acc || value, false), | ||
[blockingFormsById] | ||
); | ||
|
||
const onConfirm = useCallback(() => { | ||
setBlockingFormsById({}); | ||
}, [setBlockingFormsById]); | ||
|
||
usePrompt("Navigate to another page? Changes you made will not be saved.", isFormBlocking, onConfirm); | ||
}; | ||
|
||
export default useFormNavigationBlockingPrompt; |
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
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