refactor: curry GetFormHelpers (#1156)

This commit is contained in:
Presley Pizzo
2022-04-25 15:45:33 -04:00
committed by GitHub
parent 33b58a0363
commit bdc17f49e4
4 changed files with 72 additions and 43 deletions

View File

@ -49,13 +49,14 @@ export const AccountForm: React.FC<AccountFormProps> = ({
validationSchema,
onSubmit,
})
const getFieldHelpers = getFormHelpers<AccountFormValues>(form, formErrors)
return (
<>
<form onSubmit={form.handleSubmit}>
<Stack>
<TextField
{...getFormHelpers<AccountFormValues>(form, "name")}
{...getFieldHelpers("name")}
autoFocus
autoComplete="name"
fullWidth
@ -63,7 +64,7 @@ export const AccountForm: React.FC<AccountFormProps> = ({
variant="outlined"
/>
<TextField
{...getFormHelpers<AccountFormValues>(form, "email", formErrors.email)}
{...getFieldHelpers("email")}
onChange={onChangeTrimmed(form)}
autoComplete="email"
fullWidth
@ -71,7 +72,7 @@ export const AccountForm: React.FC<AccountFormProps> = ({
variant="outlined"
/>
<TextField
{...getFormHelpers<AccountFormValues>(form, "username", formErrors.username)}
{...getFieldHelpers("username")}
onChange={onChangeTrimmed(form)}
autoComplete="username"
fullWidth

View File

@ -76,13 +76,14 @@ export const SignInForm: React.FC<SignInFormProps> = ({
validationSchema,
onSubmit,
})
const getFieldHelpers = getFormHelpers<BuiltInAuthFormValues>(form)
return (
<>
<Welcome />
<form onSubmit={form.handleSubmit}>
<TextField
{...getFormHelpers<BuiltInAuthFormValues>(form, "email")}
{...getFieldHelpers("email")}
onChange={onChangeTrimmed(form)}
autoFocus
autoComplete="email"
@ -93,7 +94,7 @@ export const SignInForm: React.FC<SignInFormProps> = ({
variant="outlined"
/>
<TextField
{...getFormHelpers<BuiltInAuthFormValues>(form, "password")}
{...getFieldHelpers("password")}
autoComplete="current-password"
className={styles.loginTextField}
fullWidth

View File

@ -37,30 +37,53 @@ const form = {
describe("form util functions", () => {
describe("getFormHelpers", () => {
const untouchedGoodResult = getFormHelpers<TestType>(form, "untouchedGoodField")
const untouchedBadResult = getFormHelpers<TestType>(form, "untouchedBadField")
const touchedGoodResult = getFormHelpers<TestType>(form, "touchedGoodField")
const touchedBadResult = getFormHelpers<TestType>(form, "touchedBadField")
it("populates the 'field props'", () => {
expect(untouchedGoodResult.name).toEqual("untouchedGoodField")
expect(untouchedGoodResult.onBlur).toBeDefined()
expect(untouchedGoodResult.onChange).toBeDefined()
expect(untouchedGoodResult.value).toBeDefined()
describe("without API errors", () => {
const getFieldHelpers = getFormHelpers<TestType>(form)
const untouchedGoodResult = getFieldHelpers("untouchedGoodField")
const untouchedBadResult = getFieldHelpers("untouchedBadField")
const touchedGoodResult = getFieldHelpers("touchedGoodField")
const touchedBadResult = getFieldHelpers("touchedBadField")
it("populates the 'field props'", () => {
expect(untouchedGoodResult.name).toEqual("untouchedGoodField")
expect(untouchedGoodResult.onBlur).toBeDefined()
expect(untouchedGoodResult.onChange).toBeDefined()
expect(untouchedGoodResult.value).toBeDefined()
})
it("sets the id to the name", () => {
expect(untouchedGoodResult.id).toEqual("untouchedGoodField")
})
it("sets error to true if touched and invalid", () => {
expect(untouchedGoodResult.error).toBeFalsy
expect(untouchedBadResult.error).toBeFalsy
expect(touchedGoodResult.error).toBeFalsy
expect(touchedBadResult.error).toBeTruthy
})
it("sets helperText to the error message if touched and invalid", () => {
expect(untouchedGoodResult.helperText).toBeUndefined
expect(untouchedBadResult.helperText).toBeUndefined
expect(touchedGoodResult.helperText).toBeUndefined
expect(touchedBadResult.helperText).toEqual("oops!")
})
})
it("sets the id to the name", () => {
expect(untouchedGoodResult.id).toEqual("untouchedGoodField")
})
it("sets error to true if touched and invalid", () => {
expect(untouchedGoodResult.error).toBeFalsy
expect(untouchedBadResult.error).toBeFalsy
expect(touchedGoodResult.error).toBeFalsy
expect(touchedBadResult.error).toBeTruthy
})
it("sets helperText to the error message if touched and invalid", () => {
expect(untouchedGoodResult.helperText).toBeUndefined
expect(untouchedBadResult.helperText).toBeUndefined
expect(touchedGoodResult.helperText).toBeUndefined
expect(touchedBadResult.helperText).toEqual("oops!")
describe("with API errors", () => {
it("shows an error if there is only an API error", () => {
const getFieldHelpers = getFormHelpers<TestType>(form, { touchedGoodField: "API error!" })
const result = getFieldHelpers("touchedGoodField")
expect(result.error).toBeTruthy
expect(result.helperText).toEqual("API error!")
})
it("shows an error if there is only a validation error", () => {
const getFieldHelpers = getFormHelpers<TestType>(form, {})
const result = getFieldHelpers("touchedBadField")
expect(result.error).toBeTruthy
expect(result.helperText).toEqual("oops!")
})
it("shows the API error if both are present", () => {
const getFieldHelpers = getFormHelpers<TestType>(form, { touchedBadField: "API error!" })
const result = getFieldHelpers("touchedBadField")
expect(result.error).toBeTruthy
expect(result.helperText).toEqual("API error!")
})
})
})

View File

@ -1,4 +1,4 @@
import { FormikContextType, getIn } from "formik"
import { FormikContextType, FormikErrors, getIn } from "formik"
import { ChangeEvent, ChangeEventHandler, FocusEventHandler } from "react"
interface FormHelpers {
@ -11,22 +11,26 @@ interface FormHelpers {
helperText?: string
}
export const getFormHelpers = <T>(form: FormikContextType<T>, name: keyof T, error?: string): FormHelpers => {
if (typeof name !== "string") {
throw new Error(`name must be type of string, instead received '${typeof name}'`)
}
export const getFormHelpers =
<T>(form: FormikContextType<T>, formErrors?: FormikErrors<T>) =>
(name: keyof T): FormHelpers => {
if (typeof name !== "string") {
throw new Error(`name must be type of string, instead received '${typeof name}'`)
}
// getIn is a util function from Formik that gets at any depth of nesting
// and is necessary for the types to work
const touched = getIn(form.touched, name)
const errors = error ?? getIn(form.errors, name)
return {
...form.getFieldProps(name),
id: name,
error: touched && Boolean(errors),
helperText: touched && errors,
// getIn is a util function from Formik that gets at any depth of nesting
// and is necessary for the types to work
const touched = getIn(form.touched, name)
const apiError = getIn(formErrors, name)
const validationError = getIn(form.errors, name)
const error = apiError ?? validationError
return {
...form.getFieldProps(name),
id: name,
error: touched && Boolean(error),
helperText: touched && error,
}
}
}
export const onChangeTrimmed =
<T>(form: FormikContextType<T>) =>