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

View File

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

View File

@ -37,30 +37,53 @@ const form = {
describe("form util functions", () => { describe("form util functions", () => {
describe("getFormHelpers", () => { describe("getFormHelpers", () => {
const untouchedGoodResult = getFormHelpers<TestType>(form, "untouchedGoodField") describe("without API errors", () => {
const untouchedBadResult = getFormHelpers<TestType>(form, "untouchedBadField") const getFieldHelpers = getFormHelpers<TestType>(form)
const touchedGoodResult = getFormHelpers<TestType>(form, "touchedGoodField") const untouchedGoodResult = getFieldHelpers("untouchedGoodField")
const touchedBadResult = getFormHelpers<TestType>(form, "touchedBadField") const untouchedBadResult = getFieldHelpers("untouchedBadField")
it("populates the 'field props'", () => { const touchedGoodResult = getFieldHelpers("touchedGoodField")
expect(untouchedGoodResult.name).toEqual("untouchedGoodField") const touchedBadResult = getFieldHelpers("touchedBadField")
expect(untouchedGoodResult.onBlur).toBeDefined() it("populates the 'field props'", () => {
expect(untouchedGoodResult.onChange).toBeDefined() expect(untouchedGoodResult.name).toEqual("untouchedGoodField")
expect(untouchedGoodResult.value).toBeDefined() 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", () => { describe("with API errors", () => {
expect(untouchedGoodResult.id).toEqual("untouchedGoodField") it("shows an error if there is only an API error", () => {
}) const getFieldHelpers = getFormHelpers<TestType>(form, { touchedGoodField: "API error!" })
it("sets error to true if touched and invalid", () => { const result = getFieldHelpers("touchedGoodField")
expect(untouchedGoodResult.error).toBeFalsy expect(result.error).toBeTruthy
expect(untouchedBadResult.error).toBeFalsy expect(result.helperText).toEqual("API error!")
expect(touchedGoodResult.error).toBeFalsy })
expect(touchedBadResult.error).toBeTruthy it("shows an error if there is only a validation error", () => {
}) const getFieldHelpers = getFormHelpers<TestType>(form, {})
it("sets helperText to the error message if touched and invalid", () => { const result = getFieldHelpers("touchedBadField")
expect(untouchedGoodResult.helperText).toBeUndefined expect(result.error).toBeTruthy
expect(untouchedBadResult.helperText).toBeUndefined expect(result.helperText).toEqual("oops!")
expect(touchedGoodResult.helperText).toBeUndefined })
expect(touchedBadResult.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" import { ChangeEvent, ChangeEventHandler, FocusEventHandler } from "react"
interface FormHelpers { interface FormHelpers {
@ -11,22 +11,26 @@ interface FormHelpers {
helperText?: string helperText?: string
} }
export const getFormHelpers = <T>(form: FormikContextType<T>, name: keyof T, error?: string): FormHelpers => { export const getFormHelpers =
if (typeof name !== "string") { <T>(form: FormikContextType<T>, formErrors?: FormikErrors<T>) =>
throw new Error(`name must be type of string, instead received '${typeof name}'`) (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 // getIn is a util function from Formik that gets at any depth of nesting
// and is necessary for the types to work // and is necessary for the types to work
const touched = getIn(form.touched, name) const touched = getIn(form.touched, name)
const errors = error ?? getIn(form.errors, name) const apiError = getIn(formErrors, name)
return { const validationError = getIn(form.errors, name)
...form.getFieldProps(name), const error = apiError ?? validationError
id: name, return {
error: touched && Boolean(errors), ...form.getFieldProps(name),
helperText: touched && errors, id: name,
error: touched && Boolean(error),
helperText: touched && error,
}
} }
}
export const onChangeTrimmed = export const onChangeTrimmed =
<T>(form: FormikContextType<T>) => <T>(form: FormikContextType<T>) =>