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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions public/locales/bg/auth.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,7 @@
},
"pages": {
"forgotten-password": {
"instructions": "За да смените паролата си, моля въведете вашия email и ще ви изпратим потвърждение с инареукции."
"instructions": "За да смените паролата си, моля въведете вашия email и ще ви изпратим потвърждение с инструкции."
}
},
"validation":{
"email": "Невалиден email",
"required": "Задължително поле",
"password-min": "Паролата трябва да бъде поне {{count}} символа",
"password-match": "Паролата не съвпада"
}
}
10 changes: 10 additions & 0 deletions public/locales/bg/validation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"invalid": "Невалидно поле",
"required": "Задължително поле",
"email": "Невалиден email",
"password-min": "Паролата трябва да бъде поне {{min}} символа",
"password-match": "Паролата не съвпада",
"field-too-short": "Полето трябва да бъде поне {{min}} символа",
"field-too-long": "Полето трябва да бъде най-много {{max}} символа",
"one-of": "Невалидна стойност"
}
6 changes: 0 additions & 6 deletions public/locales/en/auth.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,5 @@
"forgotten-password": {
"instructions": "To reset your password, please type your email address below. We will then send you an email with instructions to follow."
}
},
"validation":{
"email": "Invalid email",
"required": "Required field",
"password-min": "Password should be at least {{count}} characters",
"password-match": "Password doesn't match"
}
}
10 changes: 10 additions & 0 deletions public/locales/en/validation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"invalid": "Field is invalid",
"required": "Required field",
"email": "Invalid email",
"password-min": "Password should be at least {{min}} characters",
"password-match": "Password doesn't match",
"field-too-short": "Field should be at least {{min}} symbols",
"field-too-long": "Field should be maximum {{max}} symbols",
"one-of": "Invalid value"
}
150 changes: 150 additions & 0 deletions src/common/form/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Forms and validation

## Form definition

```tsx
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

```tsx
<MyForm />

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

## Validation

### Default translations

Simple strings are mapped directly to their respective translation

```json
{
"invalid": "Field is invalid",
"required": "Required field"
}
```

```tsx
setLocale({
mixed: {
default: 'validation:invalid',
required: 'validation:required',
},
string: {
email: 'validation:email',
},
})
```

### Default translations with interpolation

Complex translation keys are being evaluated upon translation

```json
{
"field-too-short": "Field should be at least {{min}} symbols",
"field-too-long": "Field should be maximum {{max}} symbols"
}
```

```tsx
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

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

#### Inline translations in validation schema

Custom translations with keys defined right next to the form

```tsx
const validationSchema: yup.SchemaOf<FormData> = yup
.object()
.defined()
.shape({
password: yup.string().min(6, ({ min }) => ({
key: 'validation:password-min',
values: { min },
})),
})
```
24 changes: 0 additions & 24 deletions src/common/form/models.ts

This file was deleted.

37 changes: 0 additions & 37 deletions src/common/form/schemas.ts

This file was deleted.

13 changes: 13 additions & 0 deletions src/common/form/useForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { FormikConfig, useFormik } from 'formik'

export { translateError, customValidators } from 'common/form/validation'

export default function useForm<T>({
validateOnChange = false,
validateOnBlur = false,
...formikProps
}: FormikConfig<T>) {
const formik = useFormik({ validateOnChange, validateOnBlur, ...formikProps })

return { formik }
}
61 changes: 0 additions & 61 deletions src/common/form/useFormikHook.ts

This file was deleted.

53 changes: 53 additions & 0 deletions src/common/form/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { setLocale } from 'yup'
import { useTranslation } from 'react-i18next'

export const translateError = (
field: (string | undefined) | { key: string; values?: any },
): string | undefined => {
const { t } = useTranslation()
console.log(field)
if (!field) {
return undefined
}
if (typeof field === 'string') {
return t(field)
}
return t(field.key, field.values)
}

// Default translations:
// return 'validation:<key>'

// Default translations with interpolation:
// return { key: 'validation:<key>', values: { min, max } }

// Custom translations in validation schema:
// yup.string().min(6 customValidators.passwordMin)

// Inline translations in validation schema:
// yup.string().min(6, ({ min }) => ({ key: 'validation:password-min', values: { min } }))

export const customValidators = {
passwordMin: ({ min }: { min: number }) => ({
key: 'validation:password-min',
values: { min },
}),
}

setLocale({
mixed: {
default: 'validation:invalid',
required: 'validation:required',
},
string: {
min: ({ min }: { min: number }) => ({
key: 'validation:field-too-short',
values: { min },
}),
max: ({ max }: { max: number }) => ({
key: 'validation:field-too-long',
values: { max },
}),
email: 'validation:email',
},
})
Loading