react-material-ui-form
Version:
State and validation management for Material-UI form components
553 lines (452 loc) • 20.1 kB
Markdown
### react-material-ui-form
[](https://opensource.org/licenses/MIT)
[](https://www.npmjs.com/package/react-material-ui-form)
[](https://travis-ci.com/voletiswaroop/react-material-ui-form)
[](https://www.npmjs.com/package/react-material-ui-form)
[](https://libraries.io/npm/react-material-ui-form)
1. [About](#about)
2. [Setup](#setup)
3. [Props](#props)
- [Form props](#form-props-optional)
- [Field props](#field-props)
- [Other props](#other-props)
4. [Examples](#examples)
- [Nested fields](#nested-fields)
- [Custom validation messages](#custom-validation-messages)
- [Custom validators](#custom-validators)
- [Custom validation logic](#custom-validation-logic)
- [Server validations](#server-validations)
- [Misc form settings](#form-autocomplete-and-on-error-submission)
- [Getting values on field update](#getting-form-values-on-field-update)
- [Stepper](#stepper)
- [Dynamic array fields](#dynamic-array-fields-notice-the-deletefieldrow-prop-on-the-remove-row-button)
- [Custom component handler](#custom-components-with-custom-handlers)
5. [Contributing](#contributing)
6. [License](#license)
## About
_react-material-ui-form_ is a React wrapper for [Material-UI](https://material-ui.com/getting-started/usage/) form components. Simply replace the `<form>` element with `<MaterialUIForm>` to get out-of-the-box state and validation support ***as-is***. There's no need to use any other components, alter your form's nesting structure, or write onChange handlers.
Validation is done with [validator.js](https://github.com/chriso/validator.js) but you can extend/customize validation messages, validators, and use your own validation logic too. Steppers, dynamic array fields and custom components are also supported.
#### use and requirements
- requires React 16.3.0 or newer
- supports official and unofficial Material-UI fields (other input elements are rendered without state/validation support)
- every input field must have `value` and `name` props
- every input field should NOT have `onChange` and `onBlur` props (unless you need custom field-specific logic)
- add a `data-validators` prop to any input field (or FormControl / FormControlLabel) to specify validation rules
#### extra validators
_react-material-ui-form_ extends [_validator.js_ validators](https://github.com/chriso/validator.js#validators) with the following validators:
- isAlias `/^[a-zA-Z0-9-_\.]*$/i`
- isDate
- isNumber `/^([,.\d]+)$/`
- isRequired `value.length !== 0`
- isSerial `/^([-\s\da-zA-Z]+)$/`
- isSize `value >= min && value <= max`
- isTime
- [isLength(min,max)](#custom-validation-messages) `[{ isLength: { min: 2, max: 50 } }]`
#### _Supported field components_
- TextField
- TextField { select }
- TextField { multiline, textarea }
- [Checkbox](#nested-fields)
- RadioGroup
- Radio
- FormControlLabel (control prop)
- FormLabel
- InputLabel
## Note
Currently this package will support only till **[Material-UI v3](https://v3.material-ui.com/getting-started/installation/)**
## Setup
#### install
```
npm install --save react-material-ui-form
```
#### demo
1. `$ git clone https://github.com/voletiswaroop/react-material-ui-form.git`
2. `$ cd react-material-ui-form`
3. `$ npm install && npm run dev`
## Props
#### Form props (optional):
Prop | Description | Default
------------------------------| --------------------------|------------
[***class***] _[string]_ | Sets `className` attribute to the form |
[***id***] _[string]_ | Sets `id` attribute to the form |
[***name***] _[string]_ | Sets `name` attribute to the form |
[***action***] _[string]_ | Sets `action` attribute to the form |
[***activeStep***](#stepper) _[number]_ | Use together with `onFieldValidation` for better Stepper support |
[***autoComplete***](#form-autocomplete-and-on-error-submission) _[string]_ | Sets form _autoComplete_ prop. Accepts one of ["on", "off"] | "off"
[***disableSubmitButtonOnError***](#form-autocomplete-and-on-error-submission) _[boolean]_ | Disables submit button if any errors exist | true
[***onFieldValidation***](#stepper) _[func]_ | Returns _@field_ and _@errorSteps_ (if `activeStep` prop is provided) on field validation |
[***onSubmit***](#nested-fields) _[func]_ | Returns _@values_ and _@pristineValues_ on form submission |
[***onValuesChange***](#getting-form-values-on-field-update) _[func]_ | Returns _@values_ and _@pristineValues_ on field value change |
***validation*** _[object]_ | Object specifing validation config options (prefixed below with ↳) |
↳ [***messageMap***](#custom-validation-messages) _[object]_ | A key-value list where the key is the validator name and the value is the error message. Is exposed as a _react-material-ui-form_ export parameter | _object_
↳ [***messageKeyPrefix***](#custom-validation-messages) _[string]_ | Optional prefix to apply to all messageMap keys. If specified, field validator names will automatically be appended the prefix | ""
↳ [***requiredValidatorName***](#custom-validation-logic) _[boolean, string]_ | Specifies the validator name and matching messegeMap key for required fields. To disable and rely on the native _required_ field prop, set to `false` | "isRequired"
↳ [***validate***](#custom-validation-logic) _[func]_ | Overrides the internal validate method. Receives the following parameters: _@fieldValue_, _@fieldValidators_, and _@...rest_ (where _@...rest_ is the **validation** prop object) | _func_
↳ [***validators***](#custom-validators) _[object]_ | Defaults to an extended validator.js object. Is exposed as a _react-material-ui-form_ export parameter | _object_
↳ ***validateInputOnBlur*** _[boolean]_ | Makes text input validations happen on blur instead of on change | false
[***validations***](#server-validations) _[object]_ | Validations to pass to the form (i.e. from the server). Should be an object with keys representing field _name_ props and values as arrays of field error messages. The first error message will be displayed per field |
#### Field props:
Prop | Description | Required
------------------------------| --------------------------|------------
***value*** _[any]_ | The value of the field. If empty set an empty string | Yes
***name*** _[string]_ | The name of the field | Yes
***data-validators*** _[string, array[object]]_ | Validators to apply to the field. Multiple validator names can be specified with a comma-delimited string |
***onBlur*** _[func]_ | A custom handler that will be called after the field's `onBlur` event. Provides _@value/checked_, _@field_ and _@event_ parameters |
***onChange*** _[func]_ | A custom handler that will be called after the field's `onChange` event. Provides _@value/checked_, _@field_ and _@event_ parameters |
#### Other props:
Prop | Value | Description
-------------------------| ------------------|------------------------
[***deletefieldrow***](#dynamic-array-fields-notice-the-deletefieldrow-prop-on-the-remove-row-button) _[string]_ | Field **name** prop up to and including the row index (i.e. _rooms[2]_) | Add to button components that use _onClick_ to remove any array field rows
## Material-UI form production build classnames conflict issues
To avoid default material-ui production build classnames conflict issues include your entire form inside <JssProvider>
Example: [Nested fields](#nested-fields)
## Examples
#### Nested fields:
```jsx
import MaterialUIForm from 'react-material-ui-form'
import JssProvider from 'react-jss/lib/JssProvider';
class MyForm extends React.Component {
submit = (values, pristineValues) => {
// get all values and pristineValues on form submission
}
customInputHandler = (value, { name }, event) => {
// the form will update the field as usual, and then call this handler
// if you want to have complete control of the field, change the "value" prop to "defaultValue"
}
customToggleHandler = (checked, { name, value }, event) => {
// the form will update the field as usual, and then call this handler
// if you want to have complete control of the field, change the "value" prop to "defaultValue"
}
render() {
return (
<JssProvider>
<MaterialUIForm onSubmit={this.submit}>
<TextField label="Name" type="text" name="name" value="" data-validators="isRequired,isAlpha" onChange={this.customInputHandler} />
<fieldset>
<FormControl>
{/* form label is required here to perform default validations */}
<FormLabel component="legend">I love React material UI form</FormLabel>
<FormGroup value=''>
<FormControlLabel control={<Checkbox value='yes' />}
label='I love React material UI form'/>
</FormGroup>
</FormControl>
<FormControl required>
<InputLabel>Age</InputLabel>
<Select value="" name="age">
<MenuItem value=""><em>Please select your age ...</em></MenuItem>
<MenuItem value={10}>Teens</MenuItem>
<MenuItem value={20}>Twenties</MenuItem>
<MenuItem value={30}>Thirties</MenuItem>
<MenuItem value="40+">Fourties +</MenuItem>
</Select>
<FormHelperText>Some important helper text</FormHelperText>
</FormControl>
</fieldset>
<FormControl>
<FormLabel component="legend">Gender</FormLabel>
<RadioGroup aria-label="Gender" name="gender" value="male">
<FormControlLabel value="female" control={<Radio />} label="Female" />
<FormControlLabel value="male" control={<Radio />} label="Male" />
</RadioGroup>
</FormControl>
<Button variant="raised" type="reset">Reset</Button>
<Button variant="raised" type="submit">Submit</Button>
</MaterialUIForm>
</JssProvider>
)
}
}
```
#### Custom validators:
```jsx
import Form, { messageMap, validators } from 'react-material-ui-form/dist/validation/index';
validators.isBorat = value => value === 'borat'
const customMessageMap = Object.assign(messageMap, {
isBorat: 'NAAAAAT! You can only write "borat" lol',
})
class MyForm extends React.Component {
submit = (values, pristineValues) => {
// get all values and pristineValues on form submission
}
render() {
return (
<MaterialUIForm onSubmit={this.submit} validation={{ messageMap: customMessageMap, validators, }}>
<TextField label="Write anything..." type="text" name="trickster" value="" helperText="this is not a trick" data-validators="isBorat" />
<Button variant="raised" type="submit">Submit</Button>
</MaterialUIForm>
)
}
}
```
#### Custom validation messages:
```jsx
const customFormMsg = Object.assign(messageMap, {
isEmail: 'Please enter a valid email address',
isLength:'Must be 2-50 characters',
})
class MyForm extends React.Component {
submit = (values, pristineValues) => {
// get all values and pristineValues on form submission
}
render() {
return (
<MaterialUIForm onSubmit={this.submit} validation={{ messageMap: customFormMsg}}>
<TextField label="Name" type="text" name="FirstName" value="Name" data-validators="isLength" />
<TextField label="Email" type="text" name="Email" value="abc@xyz.com" data-validators="isRequired,isEmail" />
<Button variant="raised" type="submit">Submit</Button>
</MaterialUIForm>
)
}
}
```
#### Custom validation logic:
```jsx
import MaterialUIForm from 'react-material-ui-form'
function validate(value, fieldValidators, options) {
const fieldValidations = []
fieldValidators.forEach((validator) => {
const validation = {
code: String(validator),
message: 'its invalid so maybe try harder...',
}
if (_.has(options, 'genericMessage')) {
validation.message = options.genericMessage
}
fieldValidations.push(validation)
})
return fieldValidations
}
const validationOptions = {
genericMessage: 'yeah... *tisk*',
}
class MyForm extends React.Component {
submit = (values, pristineValues) => {
// get all values and pristineValues on form submission
}
render() {
return (
<MaterialUIForm onSubmit={this.submit} validation={{ requiredValidatorName: false, validate, ...validationOptions, }}>
<TextField label="Whatever you write isn't gonna be good enough" type="text" name="test" value="" data-validators="whatever - our custom validator will ignore this" required />
<Button variant="raised" type="submit">Submit</Button>
</MaterialUIForm>
)
}
}
```
#### Server validations:
```jsx
import MaterialUIForm from 'react-material-ui-form'
const mockServerValidations = {
name: [{ code: 'isInvalid', message: 'such invalid...' }],
}
class MyForm extends React.Component {
state = {
mockServerValidations,
}
componentDidMount() {
let validations = {
name: [{ message: 'such WOOOOOOOOOW...' }],
}
setTimeout(() => {
this.setState({ mockServerValidations: validations })
}, 1500)
setTimeout(() => {
validations = {
name: [{ message: 'so still haven\'t watched Italian Spiderman?' }],
}
this.setState({ mockServerValidations: validations })
}, 3000)
}
submit = (values, pristineValues) => {
// get all values and pristineValues on form submission
}
render() {
return (
<MaterialUIForm onSubmit={this.submit} validations={this.state.mockServerValidations}>
<TextField label="Name" type="text" name="name" value="doge" />
<Button variant="raised" type="submit">Submit</Button>
</MaterialUIForm>
)
}
}
```
#### Form autoComplete and "on error" submission:
```jsx
import MaterialUIForm from 'react-material-ui-form'
class MyForm extends React.Component {
submit = (values, pristineValues) => {
// get all values and pristineValues on form submission
}
render() {
return (
<MaterialUIForm autoComplete="on" disableSubmitButtonOnError={false} onSubmit={this.submit}>
<TextField label="Name" type="text" name="name" value="doge" data-validators="isInt" />
<Button variant="raised" type="submit">Submit</Button>
</MaterialUIForm>
)
}
}
```
#### Getting form values on field update:
```jsx
import MaterialUIForm from 'react-material-ui-form'
class MyForm extends React.Component {
handleValuesChange = (values, pristineValues) => {
// get all values and pristineValues when any field updates
}
handleFieldValidations = (field) => {
// get field object when its validation status updates
}
submit = (values, pristineValues) => {
// get all values and pristineValues on form submission
}
render() {
return (
<MaterialUIForm onSubmit={this.submit} onValuesChange={this.handleValuesChange} onFieldValidation={this.handleFieldValidations}>
<TextField label="Name" name="name" value="doge" required />
<Button variant="raised" type="submit">Submit</Button>
</MaterialUIForm>
)
}
}
```
#### Stepper:
```jsx
import Stepper, { Step, StepLabel } from 'material-ui/Stepper'
import MaterialUIForm from 'react-material-ui-form'
function getSteps() {
return [
'Step 1',
'Step 2',
]
}
class MyForm extends React.Component {
state = {
activeStep: 0,
errorSteps: [],
}
clickNext = () => {
this.setState({
activeStep: this.state.activeStep + 1,
})
}
clickBack = () => {
this.setState({
activeStep: this.state.activeStep - 1,
})
}
submit = (values, pristineValues) => {
// get all values and pristineValues on form submission
}
updateErrorSteps = (field, errorSteps) => {
this.setState({ errorSteps })
}
render() {
const steps = getSteps()
const { activeStep } = this.state
return (
<div>
<Stepper activeStep={activeStep} alternativeLabel>
{steps.map((label, i) => (
<Step key={label}>
<StepLabel error={errorSteps.includes(i)}>
{label}
</StepLabel>
</Step>
))}
</Stepper>
<MaterialUIForm activeStep={activeStep} onFieldValidation={this.updateErrorSteps} onSubmit={this.submit}>
{activeStep === 0 &&
<React.Fragment>
<TextField label="Name" name="name" value="" required />
<Button variant="raised" onClick={this.clickNext}>Next</Button>
</React.Fragment>
}
{activeStep === 1 &&
<React.Fragment>
<TextField label="Address" name="address" value="" required />
<Button variant="raised" onClick={this.clickBack}>Back</Button>
<Button variant="raised" type="submit">Submit</Button>
</React.Fragment>
}
</MaterialUIForm>
</div>
)
}
}
```
#### Dynamic array fields (notice the `deletefieldrow` prop on the "Remove Row" button):
```jsx
import MaterialUIForm from 'react-material-ui-form'
import formData from 'form-data-to-object'
class MyForm extends React.Component {
state = {
rows: [{ _id: _.uniqueId() }],
onSubmitValues: null,
}
addRow = () => {
const { rows } = this.state
rows.push({ _id: _.uniqueId() })
this.setState({ rows })
}
removeRow = (index) => {
const { rows } = this.state
if (rows.length > 1) {
rows.splice(index, 1)
this.setState({ rows })
}
}
submit = (values, pristineValues) => {
// you can parse values to turn:
// rows[0][label]: "label"
// into:
// rows: [{ label: "label" }]
const parsedValues = formData.toObj(values)
}
render() {
const steps = getSteps()
return (
<MaterialUIForm onSubmit={this.submit}>
{this.state.rows.map((row, i) => (
<Fragment key={row._id}>
<TextField label="Label" name={`rows[${i}][label]`} value="" required />
<TextField label="Value" name={`rows[${i}][value]`} value="" />
{ this.state.rows.length > 1 &&
<Button onClick={()=> this.removeRow(i)} deletefieldrow={`rows[${i}]`}>Remove Row</Button>
}
</Fragment>
))}
<Button variant="raised" onClick={this.addRow}>Add row</Button>
<Button variant="raised" color="primary" type="submit">Submit</Button>
</MaterialUIForm>
)
}
}
```
#### Custom components with custom handlers:
```jsx
import MaterialUIForm from 'react-material-ui-form'
class MyForm extends React.Component {
uploadFile = (event) => {
console.log(event.target.files)
}
render() {
return (
<div>
<MaterialUIForm>
{'Upload file: '}
<input accept="image/*" style={{ display: 'none' }} id="raised-button-file" multiple type="file" onChange={this.uploadFile} />
<label htmlFor="raised-button-file">
<Button variant="raised" component="span">Upload</Button>
</label>
</MaterialUIForm>
</div>
)
}
}
```
## Contributing
This is a new project and contributions are welcome so feel free to [open an issue](https://github.com/voletiswaroop/react-material-ui-form/issues) or fork and create a pull request. Collaborators are also welcome - please send an email to swaroop.voleti@gmail.com.
## License
This project is licensed under the terms of the [MIT license](https://github.com/voletiswaroop/react-material-ui-form/blob/dev/LICENSE).