Skip to content

Commit

Permalink
Adds all input type Field form helpers (redwoodjs#511)
Browse files Browse the repository at this point in the history
* Adds all input typesCloses redwoodjs#510

* Adds comments and docs

* More docs

* Simplify function creation

* Disable eslint on useFormContext call

* Fixes exports

Co-authored-by: Rob Cameron <[email protected]>
Co-authored-by: Peter Pistorius <[email protected]>
  • Loading branch information
3 people authored May 19, 2020
1 parent 7e73c45 commit 5447b76
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 75 deletions.
40 changes: 30 additions & 10 deletions packages/web/src/form/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,39 @@ Redwood currently provides the following form components:
* `<Form>` surrounds all form elements and provides contexts for errors and form submission
* `<FormError>` displays an error message, typically at the top of your form, containing error messages from the server
* `<Label>` is used in place of the HTML `<label>` tag and can respond to errors with different styling
* `<TextField>` is used in place of the HTML `<input type="text">` tag and can accept validation options and be styled differently in the presence of an error
* `<TextAreaField>` is used in place of the HTML `<textarea>` tag and can accept validation options and be styled differently in the presence of an error
* `<RadioField>` is used in place of the HTML `<input type="radio">` tag and can accept validation options.
The default validation for `required` is `false` for this field, To make it required, please pass the prop `validation={{ required: true }}` for all the `<RadioField>`.
* `<CheckBox>` is used in place of the HTML `<input type="checkbox">` tag. If it needs to be required to be checked before the form submission, please pass the prop `validation={{ required: true }}` in the `<CheckBox>` component.
* `<FieldError>` will display error messages from form validation and server errors
* `<Submit>` is used in place of `<button type="submit">` and will trigger a validation check and "submission" of the form (actually executes the function given to the `onSubmit` attribute on `<Form>`)
* HTML `<input>` types are available as a component `<TypeField>` where `Type` is one of the official [HTML types](https://www.w3schools.com/html/html_form_input_types.asp). They can accept validation options and be styled differently in the presence of an error. We'll refer to these collectively as "InputFields" below. The full list is:
* `<ButtonField>`
* `<CheckboxField>`
* `<ColorField>`
* `<DateField>`
* `<DatetimeLocalField>`
* `<EmailField>`
* `<FileField>`
* `<HiddenField>`
* `<ImageField>`
* `<MonthField>`
* `<NumberField>`
* `<PasswordField>`
* `<RadioField>`
* `<RangeField>`
* `<ResetField>`
* `<SearchField>`
* `<SubmitField>`
* `<TelField>`
* `<TextField>`
* `<TimeField>`
* `<UrlField>`
* `<WeekField>`

Some fields share options:

`<Label>`, `<TextField>` and `<TextAreaField>` take similar options for styling in the presence of an error.
`<Label>`, `<TextAreaField>` and all InputFields take similar options for styling in the presence of an error.

`<TextField>` and `<TextAreaField>` accept the same options for validation.
The `<TextAreaField>` and all InputFields accept the same options for validation.

`<FieldError>` only takes styling for errors and is only rendered if there is an error on the associated field.

Expand Down Expand Up @@ -240,7 +260,7 @@ The name of the field that this label is connected to. This should be the same a

The `style` and `className` that should be passed to the HTML `<label>` tag that is generated *if* the field with the same `name` has a validation error.

## `<TextField>`
## InputFields

Inputs are the backbone of most forms. `<TextField>` renders an HTML `<input type="text">` field, but is registered with `react-hook-form` to provide some validation and error handling.

Expand All @@ -250,7 +270,7 @@ Inputs are the backbone of most forms. `<TextField>` renders an HTML `<input typ
<!-- Renders <input type="text" name="name" class="input" /> -->
```

### `<TextField>` Attributes
### InputFields Attributes

Besides the attributes listed below, any additional attributes are passed on as props to the underlying `<input>` tag which is rendered.

Expand Down Expand Up @@ -292,15 +312,15 @@ Besides the attributes listed below, any additional attributes are passed on as

#### name

See `<TextField>` [name](#textfield-attributes)
See InputFields [name](#inputfields-attributes)

#### validation

See `<TextField>` [validation](#textfield-attributes)
See InputFields [validation](#inputfields-attributes)

#### errorStyle / errorClassName

See `<TextField>` [errorStyle](#textfield-attributes)
See InputFields [errorStyle](#inputfields-attributes)

## `<FieldError>`

Expand Down
144 changes: 79 additions & 65 deletions packages/web/src/form/form.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useForm, FormContext, useFormContext } from 'react-hook-form'
import { useContext, useEffect } from 'react'
import pascalcase from 'pascalcase'

const DEFAULT_MESSAGES = {
required: 'is required',
Expand All @@ -10,6 +11,30 @@ const DEFAULT_MESSAGES = {
max: 'is too low',
validate: 'is not valid',
}
const INPUT_TYPES = [
'button',
'checkbox',
'color',
'date',
'datetime-local',
'email',
'file',
'hidden',
'image',
'month',
'number',
'password',
'radio',
'range',
'reset',
'search',
'submit',
'tel',
'text',
'time',
'url',
'week',
]

// Massages a hash of props depending on whether the given named field has
// any errors on it
Expand Down Expand Up @@ -165,21 +190,6 @@ const FieldError = (props) => {
return validationError ? <span {...props}>{errorMessage}</span> : null
}

// Renders an <input type="hidden"> field

const HiddenField = (props) => {
const { register } = useFormContext()

return (
<input
{...props}
type="hidden"
id={props.id || props.name}
ref={register(props.validation || { required: false })}
/>
)
}

// Renders a <textarea> field

const TextAreaField = (props) => {
Expand All @@ -195,52 +205,6 @@ const TextAreaField = (props) => {
)
}

// Renders an <input type="text"> field

const TextField = (props) => {
const { register } = useFormContext()
const tagProps = inputTagProps(props)

return (
<input
{...tagProps}
type={props.type || 'text'}
id={props.id || props.name}
ref={register(props.validation || { required: false })}
/>
)
}

// Renders an <input type="radio"> field
const RadioField = (props) => {
const { register } = useFormContext()
const tagProps = inputTagProps(props)

return (
<input
{...tagProps}
type="radio"
id={props.id || props.name}
ref={register(props.validation || { required: false })}
/>
)
}

// Renders an <input type="checkbox"> field
const CheckBox = (props) => {
const { register } = useFormContext()
const tagProps = inputTagProps(props)

return (
<input
{...tagProps}
type="checkbox"
id={props.id || props.name}
ref={register(props.validation || { required: false })}
/>
)
}

// Renders a <select> field

const SelectField = (props) => {
Expand All @@ -257,21 +221,71 @@ const SelectField = (props) => {
}

// Renders a <button type="submit">

const Submit = React.forwardRef((props, ref) => (
<button ref={ref} type="submit" {...props} />
))

// Create a component for each type of Input.
//
// Uses a bit of Javascript metaprogramming to create the functions with a dynamic
// name rather than having to write out each and every component definition. In
// simple terms it creates an object with the key being the current value of `type`
// and then immediately returns the value, which is the component function definition.
//
// In the end we end up with `inputComponents.TextField` and all the others. Export those
// and we're good to go.

let inputComponents = {}
INPUT_TYPES.forEach((type) => {
inputComponents[`${pascalcase(type)}Field`] = (props) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { register } = useFormContext()
const tagProps = inputTagProps(props)

return (
<input
{...tagProps}
type={type}
id={props.id || props.name}
ref={register(props.validation || { required: false })}
/>
)
}
})

export {
Form,
FieldErrorContext,
FormError,
FieldError,
Label,
HiddenField,
TextAreaField,
TextField,
RadioField,
CheckBox,
SelectField,
Submit,
}

export const {
ButtonField,
CheckboxField,
ColorField,
DateField,
DatetimeLocalField,
EmailField,
FileField,
HiddenField,
ImageField,
MonthField,
NumberField,
PasswordField,
RadioField,
RangeField,
ResetField,
SearchField,
SubmitField,
TelField,
TextField,
TimeField,
UrlField,
WeekField,
} = inputComponents

0 comments on commit 5447b76

Please sign in to comment.