Skip to content

Commit 3dd635a

Browse files
authored
Bump React Hook Form to latest (#1933)
* notes on async form submit and nav block * version that seems to work * upgrade RHF * cleanup * delete sleep in the mock handler * bump RHF again * lil tweak
1 parent 6c8f7a9 commit 3dd635a

File tree

5 files changed

+39
-29
lines changed

5 files changed

+39
-29
lines changed

app/components/form/FullPageForm.tsx

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ interface FullPageFormProps<TFieldValues extends FieldValues> {
2525
error?: Error
2626
form: UseFormReturn<TFieldValues>
2727
loading?: boolean
28-
onSubmit: (values: TFieldValues) => void
28+
/**
29+
* Use await mutateAsync(), otherwise you'll break the logic below that relies
30+
* on knowing when the submit is done.
31+
*/
32+
onSubmit: (values: TFieldValues) => Promise<void>
2933
/** Error from the API call */
3034
submitError: ApiError | null
3135
/**
@@ -53,22 +57,25 @@ export function FullPageForm<TFieldValues extends FieldValues>({
5357
onSubmit,
5458
submitError,
5559
}: FullPageFormProps<TFieldValues>) {
56-
const { isSubmitting, isDirty } = form.formState
60+
const { isSubmitting, isDirty, isSubmitSuccessful } = form.formState
5761

58-
/*
59-
Confirms with the user if they want to navigate away
60-
if the form is dirty. Does not intercept everything e.g.
61-
refreshes or closing the tab but serves to reduce
62-
the possibility of a user accidentally losing their progress
63-
*/
64-
const blocker = useBlocker(isDirty)
62+
// Confirms with the user if they want to navigate away if the form is
63+
// dirty. Does not intercept everything e.g. refreshes or closing the tab
64+
// but serves to reduce the possibility of a user accidentally losing their
65+
// progress.
66+
const blocker = useBlocker(isDirty && !isSubmitSuccessful)
6567

66-
// Reset blocker if form is no longer dirty
68+
// Gating on !isSubmitSuccessful above makes the blocker stop blocking nav
69+
// after a successful submit. However, this can take a little time (there is a
70+
// render in between when isSubmitSuccessful is true but the blocker is still
71+
// ready to block), so we also have this useEffect that lets blocked requests
72+
// through if submit is succesful but the blocker hasn't gotten a chance to
73+
// stop blocking yet.
6774
useEffect(() => {
68-
if (blocker.state === 'blocked' && !isDirty) {
69-
blocker.reset()
75+
if (blocker.state === 'blocked' && isSubmitSuccessful) {
76+
blocker.proceed()
7077
}
71-
}, [blocker, isDirty])
78+
}, [blocker, isSubmitSuccessful])
7279

7380
const childArray = flattenChildren(children)
7481
const actions = pluckFirstOfType(childArray, Form.Actions)
@@ -81,24 +88,27 @@ export function FullPageForm<TFieldValues extends FieldValues>({
8188
<form
8289
className="ox-form pb-20"
8390
id={id}
84-
onSubmit={(e) => {
91+
onSubmit={async (e) => {
8592
// This modal being in a portal doesn't prevent the submit event
8693
// from bubbling up out of the portal. Normally that's not a
8794
// problem, but sometimes (e.g., instance create) we render the
8895
// SideModalForm from inside another form, in which case submitting
8996
// the inner form submits the outer form unless we stop propagation
9097
e.stopPropagation()
91-
// This resets `isDirty` whilst keeping the values meaning
92-
// we are not prevented from navigating away by the blocker
93-
form.reset({} as TFieldValues, { keepValues: true })
94-
form.handleSubmit(onSubmit)(e)
98+
// Important to await here so isSubmitSuccessful doesn't become true
99+
// until the submit is actually successful. Note you must use await
100+
// mutateAsync() inside onSubmit in order to make this wait
101+
await form.handleSubmit(onSubmit)(e)
95102
}}
96103
autoComplete="off"
97104
>
98105
{childArray}
99106
</form>
100107

101-
{blocker ? <ConfirmNavigation blocker={blocker} /> : null}
108+
{/* rendering of the modal must be gated on isSubmitSuccessful because
109+
there is a brief moment where isSubmitSuccessful is true but the proceed()
110+
hasn't fired yet, which means we get a brief flash of this modal */}
111+
{!isSubmitSuccessful && <ConfirmNavigation blocker={blocker} />}
102112

103113
{actions && (
104114
<PageActions>

app/forms/instance-create.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export function CreateInstanceForm() {
188188
? await readBlobAsBase64(values.userData)
189189
: undefined
190190

191-
createInstance.mutate({
191+
await createInstance.mutateAsync({
192192
query: projectSelector,
193193
body: {
194194
name: values.name,

libs/api-mocks/msw/handlers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ export const handlers = makeHandlers({
289289
const instances = db.instances.filter((i) => i.project_id === project.id)
290290
return paginated(query, instances)
291291
},
292-
instanceCreate({ body, query }) {
292+
async instanceCreate({ body, query }) {
293293
const project = lookup.project(query)
294294

295295
if (body.name === 'no-default-pool') {

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"react-aria": "^3.31.0",
5858
"react-dom": "^18.2.0",
5959
"react-error-boundary": "^4.0.12",
60-
"react-hook-form": "^7.47.0",
60+
"react-hook-form": "^7.50.1",
6161
"react-is": "^18.2.0",
6262
"react-merge-refs": "^2.1.1",
6363
"react-router-dom": "^6.21.1",

0 commit comments

Comments
 (0)