Skip to content
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

Generic form updates #32

Merged
merged 5 commits into from
Feb 11, 2021
Merged

Generic form updates #32

merged 5 commits into from
Feb 11, 2021

Conversation

kachar
Copy link
Member

@kachar kachar commented Feb 11, 2021

Motivation and context

  • Extracted <form /> elements in dedicated component to decouple from the page component
  • Make the form and validation schema use the same data types and trigger errors if they don't match
  • Create generic reusable form binding between MUI text fields and Formik fields
  • Move form data type and validation schema right next to the form component
    export type LoginFormData = {
      email: string
      password: string
    }
    
    const validationSchema: yup.SchemaOf<LoginFormData> = yup
      .object()
      .defined()
      .shape({
        email: yup.string().email().required(),
        password: yup.string().min(6, customValidators.passwordMin).required(),
      })
    
    const defaults: LoginFormData = {
      email: '',
      password: '',
    }
  • Provide a way to prefill the form with data externally
    <ForgottenPasswordForm />
    <ForgottenPasswordForm initialValues={{ email: '[email protected]' }} />
  • Used common validation namespace
  • Provide a way to translate default validations right before rendering
  • Provide a way to customize the translation message in place

The following chapter can be found in the Form Guidelines

Form definition

import React from 'react'
import * as yup from 'yup'
import { useTranslation } from 'react-i18next'
import { Grid, TextField, Button } from '@material-ui/core'

import { AlertStore } from 'stores/AlertStore'
import useForm, { translateError, customValidators } from 'common/form/useForm'

export type FormData = {
  email: string
}

const validationSchema: yup.SchemaOf<FormData> = yup.object().defined().shape({
  email: yup.string().email().required(),
})

const defaults: FormData = {
  email: '',
}

export type MyFormProps = { initialValues?: FormData }

export default function MyForm({ initialValues = defaults }: MyFormProps) {
  const { t } = useTranslation()

  const { formik } = useForm({
    initialValues,
    validationSchema,
    onSubmit: (values) => {
      console.log(values)
    },
  })

  return (
    <form onSubmit={formik.handleSubmit}>
      <Grid container spacing={3}>
        <Grid item xs={12}>
          <TextField
            type="text"
            fullWidth
            label={t('auth:fields.email')}
            name="email"
            size="small"
            variant="outlined"
            autoFocus
            error={Boolean(formik.errors.email)}
            helperText={translateError(formik.errors.email)}
            value={formik.values.email}
            onBlur={formik.handleBlur}
            onChange={formik.handleChange}
          />
        </Grid>
        <Grid item xs={12}>
          <Button fullWidth type="submit" color="primary" variant="contained">
            {t('auth:cta.login')}
          </Button>
        </Grid>
      </Grid>
    </form>
  )
}

Form usage

<MyForm />

<MyForm initailValues={{email: '[email protected]'}} />

Validation

Default translations

Simple strings are mapped directly to their respective translation

{
  "invalid": "Field is invalid",
  "required": "Required field"
}
setLocale({
  mixed: {
    default: 'validation:invalid',
    required: 'validation:required',
  },
  string: {
    email: 'validation:email',
  },
})

Default translations with interpolation

Complex translation keys are being evaluated upon translation

{
  "field-too-short": "Field should be at least {{min}} symbols",
  "field-too-long": "Field should be maximum {{max}} symbols"
}
setLocale({
  string: {
    min: ({ min }: { min: number }) => ({
      key: 'validation:field-too-short',
      values: { min },
    }),
    max: ({ max }: { max: number }) => ({
      key: 'validation:field-too-long',
      values: { max },
    }),
  },
})

Custom translations in validation schema

Commonly used translations with the same translation key

yup.string().min(6 customValidators.passwordMin)

Inline translations in validation schema

Custom translations with keys defined right next to the form

const validationSchema: yup.SchemaOf<FormData> = yup
  .object()
  .defined()
  .shape({
    password: yup.string().min(6, ({ min }) => ({
      key: 'validation:password-min',
      values: { min },
    })),
  })

@kachar kachar requested review from uzuntonev and a team February 11, 2021 13:30
@kachar kachar linked an issue Feb 11, 2021 that may be closed by this pull request
6 tasks
Copy link
Contributor

@uzuntonev uzuntonev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, form handling looks completed. Extracted form component is easy to maintenance and page component looks more clear.

@kachar
Copy link
Member Author

kachar commented Feb 11, 2021

Awesome! Thanks for the review

@kachar kachar merged commit 450fbc3 into master Feb 11, 2021
@kachar kachar deleted the generic-form-updates branch February 11, 2021 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Integrate form library
2 participants