Skip to content

Commit bdc17f4

Browse files
authored
refactor: curry GetFormHelpers (#1156)
1 parent 33b58a0 commit bdc17f4

File tree

4 files changed

+72
-43
lines changed

4 files changed

+72
-43
lines changed

site/src/components/PreferencesAccountForm/PreferencesAccountForm.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -49,29 +49,30 @@ export const AccountForm: React.FC<AccountFormProps> = ({
4949
validationSchema,
5050
onSubmit,
5151
})
52+
const getFieldHelpers = getFormHelpers<AccountFormValues>(form, formErrors)
5253

5354
return (
5455
<>
5556
<form onSubmit={form.handleSubmit}>
5657
<Stack>
5758
<TextField
58-
{...getFormHelpers<AccountFormValues>(form, "name")}
59+
{...getFieldHelpers("name")}
5960
autoFocus
6061
autoComplete="name"
6162
fullWidth
6263
label={Language.nameLabel}
6364
variant="outlined"
6465
/>
6566
<TextField
66-
{...getFormHelpers<AccountFormValues>(form, "email", formErrors.email)}
67+
{...getFieldHelpers("email")}
6768
onChange={onChangeTrimmed(form)}
6869
autoComplete="email"
6970
fullWidth
7071
label={Language.emailLabel}
7172
variant="outlined"
7273
/>
7374
<TextField
74-
{...getFormHelpers<AccountFormValues>(form, "username", formErrors.username)}
75+
{...getFieldHelpers("username")}
7576
onChange={onChangeTrimmed(form)}
7677
autoComplete="username"
7778
fullWidth

site/src/components/SignInForm/SignInForm.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,14 @@ export const SignInForm: React.FC<SignInFormProps> = ({
7676
validationSchema,
7777
onSubmit,
7878
})
79+
const getFieldHelpers = getFormHelpers<BuiltInAuthFormValues>(form)
7980

8081
return (
8182
<>
8283
<Welcome />
8384
<form onSubmit={form.handleSubmit}>
8485
<TextField
85-
{...getFormHelpers<BuiltInAuthFormValues>(form, "email")}
86+
{...getFieldHelpers("email")}
8687
onChange={onChangeTrimmed(form)}
8788
autoFocus
8889
autoComplete="email"
@@ -93,7 +94,7 @@ export const SignInForm: React.FC<SignInFormProps> = ({
9394
variant="outlined"
9495
/>
9596
<TextField
96-
{...getFormHelpers<BuiltInAuthFormValues>(form, "password")}
97+
{...getFieldHelpers("password")}
9798
autoComplete="current-password"
9899
className={styles.loginTextField}
99100
fullWidth

site/src/util/formUtils.test.ts

+46-23
Original file line numberDiff line numberDiff line change
@@ -37,30 +37,53 @@ const form = {
3737

3838
describe("form util functions", () => {
3939
describe("getFormHelpers", () => {
40-
const untouchedGoodResult = getFormHelpers<TestType>(form, "untouchedGoodField")
41-
const untouchedBadResult = getFormHelpers<TestType>(form, "untouchedBadField")
42-
const touchedGoodResult = getFormHelpers<TestType>(form, "touchedGoodField")
43-
const touchedBadResult = getFormHelpers<TestType>(form, "touchedBadField")
44-
it("populates the 'field props'", () => {
45-
expect(untouchedGoodResult.name).toEqual("untouchedGoodField")
46-
expect(untouchedGoodResult.onBlur).toBeDefined()
47-
expect(untouchedGoodResult.onChange).toBeDefined()
48-
expect(untouchedGoodResult.value).toBeDefined()
40+
describe("without API errors", () => {
41+
const getFieldHelpers = getFormHelpers<TestType>(form)
42+
const untouchedGoodResult = getFieldHelpers("untouchedGoodField")
43+
const untouchedBadResult = getFieldHelpers("untouchedBadField")
44+
const touchedGoodResult = getFieldHelpers("touchedGoodField")
45+
const touchedBadResult = getFieldHelpers("touchedBadField")
46+
it("populates the 'field props'", () => {
47+
expect(untouchedGoodResult.name).toEqual("untouchedGoodField")
48+
expect(untouchedGoodResult.onBlur).toBeDefined()
49+
expect(untouchedGoodResult.onChange).toBeDefined()
50+
expect(untouchedGoodResult.value).toBeDefined()
51+
})
52+
it("sets the id to the name", () => {
53+
expect(untouchedGoodResult.id).toEqual("untouchedGoodField")
54+
})
55+
it("sets error to true if touched and invalid", () => {
56+
expect(untouchedGoodResult.error).toBeFalsy
57+
expect(untouchedBadResult.error).toBeFalsy
58+
expect(touchedGoodResult.error).toBeFalsy
59+
expect(touchedBadResult.error).toBeTruthy
60+
})
61+
it("sets helperText to the error message if touched and invalid", () => {
62+
expect(untouchedGoodResult.helperText).toBeUndefined
63+
expect(untouchedBadResult.helperText).toBeUndefined
64+
expect(touchedGoodResult.helperText).toBeUndefined
65+
expect(touchedBadResult.helperText).toEqual("oops!")
66+
})
4967
})
50-
it("sets the id to the name", () => {
51-
expect(untouchedGoodResult.id).toEqual("untouchedGoodField")
52-
})
53-
it("sets error to true if touched and invalid", () => {
54-
expect(untouchedGoodResult.error).toBeFalsy
55-
expect(untouchedBadResult.error).toBeFalsy
56-
expect(touchedGoodResult.error).toBeFalsy
57-
expect(touchedBadResult.error).toBeTruthy
58-
})
59-
it("sets helperText to the error message if touched and invalid", () => {
60-
expect(untouchedGoodResult.helperText).toBeUndefined
61-
expect(untouchedBadResult.helperText).toBeUndefined
62-
expect(touchedGoodResult.helperText).toBeUndefined
63-
expect(touchedBadResult.helperText).toEqual("oops!")
68+
describe("with API errors", () => {
69+
it("shows an error if there is only an API error", () => {
70+
const getFieldHelpers = getFormHelpers<TestType>(form, { touchedGoodField: "API error!" })
71+
const result = getFieldHelpers("touchedGoodField")
72+
expect(result.error).toBeTruthy
73+
expect(result.helperText).toEqual("API error!")
74+
})
75+
it("shows an error if there is only a validation error", () => {
76+
const getFieldHelpers = getFormHelpers<TestType>(form, {})
77+
const result = getFieldHelpers("touchedBadField")
78+
expect(result.error).toBeTruthy
79+
expect(result.helperText).toEqual("oops!")
80+
})
81+
it("shows the API error if both are present", () => {
82+
const getFieldHelpers = getFormHelpers<TestType>(form, { touchedBadField: "API error!" })
83+
const result = getFieldHelpers("touchedBadField")
84+
expect(result.error).toBeTruthy
85+
expect(result.helperText).toEqual("API error!")
86+
})
6487
})
6588
})
6689

site/src/util/formUtils.ts

+19-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FormikContextType, getIn } from "formik"
1+
import { FormikContextType, FormikErrors, getIn } from "formik"
22
import { ChangeEvent, ChangeEventHandler, FocusEventHandler } from "react"
33

44
interface FormHelpers {
@@ -11,22 +11,26 @@ interface FormHelpers {
1111
helperText?: string
1212
}
1313

14-
export const getFormHelpers = <T>(form: FormikContextType<T>, name: keyof T, error?: string): FormHelpers => {
15-
if (typeof name !== "string") {
16-
throw new Error(`name must be type of string, instead received '${typeof name}'`)
17-
}
14+
export const getFormHelpers =
15+
<T>(form: FormikContextType<T>, formErrors?: FormikErrors<T>) =>
16+
(name: keyof T): FormHelpers => {
17+
if (typeof name !== "string") {
18+
throw new Error(`name must be type of string, instead received '${typeof name}'`)
19+
}
1820

19-
// getIn is a util function from Formik that gets at any depth of nesting
20-
// and is necessary for the types to work
21-
const touched = getIn(form.touched, name)
22-
const errors = error ?? getIn(form.errors, name)
23-
return {
24-
...form.getFieldProps(name),
25-
id: name,
26-
error: touched && Boolean(errors),
27-
helperText: touched && errors,
21+
// getIn is a util function from Formik that gets at any depth of nesting
22+
// and is necessary for the types to work
23+
const touched = getIn(form.touched, name)
24+
const apiError = getIn(formErrors, name)
25+
const validationError = getIn(form.errors, name)
26+
const error = apiError ?? validationError
27+
return {
28+
...form.getFieldProps(name),
29+
id: name,
30+
error: touched && Boolean(error),
31+
helperText: touched && error,
32+
}
2833
}
29-
}
3034

3135
export const onChangeTrimmed =
3236
<T>(form: FormikContextType<T>) =>

0 commit comments

Comments
 (0)