Wondering what’s next for npm?Check out our public roadmap! »

    material-ui-form

    1.0.0-alpha.26 • Public • Published

    material-ui-form

    npm David Build Status Codecov

    1. About
    2. Setup
    3. Props
    4. Examples
    5. Contributing
    6. License

    About

    material-ui-form is a React wrapper for Material-UI 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 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

    material-ui-form extends 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

    NOTE!

    While most Material-UI field components are supported there may be some that are not. Support for Material-UI field component props is another issue. Please check here to see what is currently tested to be working.

    Setup

    install

    npm install --save material-ui-form
    

    demo

    1. $ git clone https://github.com/unitedhubs/material-ui-form.git
    2. $ cd material-ui-form
    3. $ npm install && npm run dev

    Props

    Form props (optional):

    Prop Description Default
    activeStep [number] Use together with onFieldValidation for better Stepper support
    autoComplete [string] Sets form autoComplete prop. Accepts one of ["on", "off"] "off"
    disableSubmitButtonOnError [boolean] Disables submit button if any errors exist true
    onFieldValidation [func] Returns @field and @errorSteps (if activeStep prop is provided) on field validation
    onSubmit [func] Returns @values and @pristineValues on form submission
    onValuesChange [func] Returns @values and @pristineValues on field value change
    validation [object] Object specifying validation config options (prefixed below with ↳)
    messageMap [object] A key-value list where the key is the validator name and the value is the error message. Is exposed as a material-ui-form export parameter object
    messageKeyPrefix [string] Optional prefix to apply to all messageMap keys. If specified, field validator names will automatically be appended the prefix ""
    requiredValidatorName [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 [func] Overrides the internal validate method. Receives the following parameters: @fieldValue, @fieldValidators, and @...rest (where @...rest is the validation prop object) func
    validators [object] Defaults to an extended validator.js object. Is exposed as a material-ui-form export parameter object
    validateInputOnBlur [boolean] Makes text input validations happen on blur instead of on change false
    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 [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

    Examples

    Nested fields:

    import MaterialUIForm from 'material-ui-form'
     
     
    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 (
          <MaterialUIForm onSubmit={this.submit}>
            <TextField
              label="Name"
              type="text"
              name="name"
              value=""
              data-validators="isRequired,isAlpha"
              onChange={this.customInputHandler}
            />
     
            <fieldset>
              <legend>Nested</legend>
              <Checkbox
                checked
                name="love"
                value="yes"
                onChange={this.customToggleHandler}
              />
              <span>I love it</span>
     
              <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>
            <Button variant="raised" type="reset">Reset</Button>
            <Button variant="raised" type="submit">Submit</Button>
          </MaterialUIForm>
        )
      }
    }

    Custom validation messages:

    import Form, { messageMap } from '../../src/index'
     
     
    const customMessageMap = Object.assign(messageMap, {
      myCustomPrefix_isInt: 'Invalid integer',
      myCustomPrefix_isEmail: 'メールアドレスが無効です',
      myCustomPrefix_isIn: '「{0}」のいずれかを記入してください',
      myCustomPrefix_isWhitelisted: '文字は「{0}」から選択してください',
      myCustomPrefix_isLength: '文字数は{0}以上{1}以下であることは条件',
    })
     
    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,
              messageKeyPrefix: 'myCustomPrefix_',
            }}
          >
            <TextField
              label="Email"
              type="text"
              name="email"
              value="invalid@email."
              data-validators="isEmail"
            />
     
            <TextField
              label="Inclusion"
              type="number"
              name="number"
              value="3"
              data-validators={[{ isIn: [1, 2, 4] }]}
            />
     
            <TextField
              label="Whitelisted characters"
              type="text"
              name="whitelisted"
              value="abc1234"
              data-validators={[{ isWhitelisted: 'abc123' }]}
            />
     
            <TextField
              label="Lenght test"
              type="text"
              name="length"
              value="123"
              data-validators={[{ isLength: { min: 4, max: 5 } }]}
            />
     
            <Button variant="raised" type="submit">Submit</Button>
          </MaterialUIForm>
        )
      }
    }

    Custom validators:

    import Form, { messageMap, validators } from '../../src/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 logic:

    import MaterialUIForm from '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:

    import MaterialUIForm from '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:

    import MaterialUIForm from '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:

    import MaterialUIForm from '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:

    import Stepper, { Step, StepLabel } from 'material-ui/Stepper'
    import MaterialUIForm from '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):

    import MaterialUIForm from '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:

    import MaterialUIForm from '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 or fork and create a pull request.

    Now looking for maintainers! If interested please communicate so in this issue.

    License

    This project is licensed under the terms of the MIT license.

    Install

    npm i material-ui-form

    DownloadsWeekly Downloads

    828

    Version

    1.0.0-alpha.26

    License

    MIT

    Unpacked Size

    79.4 kB

    Total Files

    22

    Last publish

    Collaborators

    • avatar
    • avatar