-
Notifications
You must be signed in to change notification settings - Fork 13
Add form navigation guard to prevent accidental form content loss #2315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
e770781
Add blocker to SideModalForm to protect from some accidental data loss
charliepark e3480d2
Extract FormNavGuard to its own component
charliepark 5ad50c3
formatting
charliepark 0af518d
Merge branch 'main' into form-navigation-guard
charliepark f1b6807
include optional field in defaultValues; probably will need more of t…
charliepark 23926db
defaultValue must be undefined or zod complains on submit; refactor
charliepark 08005aa
Merge branch 'main' into form-navigation-guard
charliepark 41722b6
Merge branch 'main' into form-navigation-guard
charliepark 2d54b5c
Merge branch 'main' into form-navigation-guard
charliepark 83897db
Switch to async on form create/edits
charliepark 450ee02
add test
charliepark 546636b
Bot commit: format with prettier
github-actions[bot] 86e9bd3
clean up test
charliepark 9efa3dc
add important comment about async/await
charliepark f6222cf
Remove test from unrelated suite
charliepark File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,56 @@ | ||
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, you can obtain one at https://mozilla.org/MPL/2.0/. | ||
* | ||
* Copyright Oxide Computer Company | ||
*/ | ||
import { useEffect } from 'react' | ||
import { type FieldValues, type UseFormReturn } from 'react-hook-form' | ||
import { useBlocker } from 'react-router-dom' | ||
|
||
import { Modal } from '~/ui/lib/Modal' | ||
|
||
export function FormNavGuard<TFieldValues extends FieldValues>({ | ||
form, | ||
}: { | ||
form: UseFormReturn<TFieldValues> | ||
}) { | ||
const { isDirty, isSubmitting, isSubmitSuccessful } = form.formState | ||
// Confirms with the user if they want to navigate away if the form is | ||
// dirty. Does not intercept everything e.g. refreshes or closing the tab | ||
// but serves to reduce the possibility of a user accidentally losing their | ||
// progress. | ||
const blocker = useBlocker(isDirty && !isSubmitSuccessful) | ||
|
||
// Gating on !isSubmitSuccessful above makes the blocker stop blocking nav | ||
// after a successful submit. However, this can take a little time (there is a | ||
// render in between when isSubmitSuccessful is true but the blocker is still | ||
// ready to block), so we also have this useEffect that lets blocked requests | ||
// through if submit is succesful but the blocker hasn't gotten a chance to | ||
// stop blocking yet. | ||
useEffect(() => { | ||
if (blocker.state === 'blocked' && isSubmitSuccessful) { | ||
blocker.proceed() | ||
} | ||
}, [blocker, isSubmitSuccessful]) | ||
|
||
return isSubmitting || isSubmitSuccessful ? null : ( | ||
<Modal | ||
isOpen={blocker.state === 'blocked'} | ||
onDismiss={() => blocker.reset?.()} | ||
title="Confirm navigation" | ||
> | ||
<Modal.Section> | ||
Are you sure you want to leave this form? Your progress will be lost. | ||
</Modal.Section> | ||
<Modal.Footer | ||
onDismiss={() => blocker.reset?.()} | ||
onAction={() => blocker.proceed?.()} | ||
cancelText="Keep editing" | ||
actionText="Leave form" | ||
actionType="danger" | ||
/> | ||
</Modal> | ||
) | ||
} |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't need to be generic since we're not actually using the form values. I think you can do
form: UseFormReturn
with no param and it'll work.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both of the callsites for this have
form
as aUseFormReturn<TFieldValues>
. I think it'd be easier to just pass it in as-is and set the type within the new component to also have the generic? (TS complains at the callsites if I strip it from the new component's type) Or is there an aspect to generics that I'm missing?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I was wrong. I thought that leaving it off like this
would be more like
UseFormReturn<any>
(which does work btw because it doesn't care about types anymore after), but it's actuallyUseFormReturn<FieldValues>
(the default value for that generic param) and surprisingly, that is incompatible with ourTFieldValues extends FieldValues
. I think you're right, the best thing is to leave it as-is.