Skip to content

Latest commit

 

History

History
1484 lines (1145 loc) · 31.3 KB

File metadata and controls

1484 lines (1145 loc) · 31.3 KB

FormManager API Reference

Last update: 2026-04-24

⚠️
Breaking Changes

Some method names changed in v7.x
This includes field-prop helpers and 'singular' vs 'plural' name variations. (See Singular vs Plural Method-names)

Contents

API Overview & Conventions

FormManager ("FM") has an extensive API. Everything can be done programmatically, providing control of data, form-state, events, and form fields.

FM exposes only 'methods' — it has no 'properties'.
All data, state and configuration caches are private so they cannot be directly mutated. All modifications are performed with getter and setter methods so everything can be kept synchronized.

You can also attach custom methods or properties to a FM object. For example, adding a form.submit() method is simpler and more semantic than passing extra callback props…​

// Create a form-manager instance and load the data
this.form = FormManager(this, formConfig, props.data)

// Attach form.submit() and form.refresh() methods
this.form.submit = this.submitChanges
this.form.refresh = this.fetchLatestData

// Pass only a 'form' prop to all subcomponents
<FormSectionOne form={this.form} />
<FormSectionTwo form={this.form} />

Dynamic Configuration

FM obeys the configuration rules specified. This can be updated on-the-fly using configuration API methods. FM can also integrate seamlessly with custom logic or helpers.

Singular vs Plural Method-names

Many getter & setter methods have similar names, except that one is 'singular' (eg: getValue) and one is 'plural' (eg: getValues). These are different methods with different argument signatures.

  • Singular method-names usually apply to one specific field, so the first argument is always a fieldname.

  • Plural method-names usually apply to all fields -or- multiple fields, so the first argument is an object, an array, or nothing!

// GETTERS
const username = form.getValue('username') // One value
// OR
const fieldValues = form.getValues()  // All field values
const username = fieldValues.username

// SETTERS
form.setValue('username', 'john-smith')
form.setValue('email', 'john-smith@gmail.com')
// OR
form.setValues({
    username: 'john-smith',
    email: 'john-smith@gmail.com'
})

Abbreviated Method-names

Some methods have alternate, shorter alias names to aid code brevity, like:

  • isFieldDisabledisDisabled

  • getMuiFieldPropsallMuiProps

  • getValuevalue

  • getErrorerror

These are especially useful when used repeatedly in markup, like:

<TextField label="First Name"  {...form.allMuiProps("firstName")} />
<TextField label="Middle Name" {...form.allMuiProps("middleName")} />
<TextField label="Last Name"   {...form.allMuiProps("lastName")} />

Chaining Commands

All setter methods return the FormManager object so multiple commands can be chained. This is just a convenience — it does not change how the methods work.

form.setFieldRequired('phone')
form.validate('phone')

// Is the same as...
form.setFieldRequired('phone')
    .validate('phone')

Fieldname Aliases

An alias-name can be specified for any field. This helps normalize fieldnames across different datasets, and simplifies the concatenated names required for nested data fields.

An alias can be passed to any FM method. In the examples below, each pair of commands are equivalent — one using the full fielname and one the aliasName. Note how the alias makes code easier to write and read.

// Set an aliasName for a deeply nested field
const formConfig = {
    fields: {
        'profile.contacts.primaryPhone': {
            aliasName: 'phone'
        }
    }
}

const phone = form.getValue('profile.contacts.primaryPhone')
const phone = form.getValue('phone')

form.setFieldDisabled('profile.contacts.primaryPhone')
form.setFieldDisabled('phone')

<TextField
    label="Phone"
    {...form.allMuiProps('profile.contacts.primaryPhone')}
/>
<TextField label="Phone" {...form.allMuiProps('phone')} />

Configuration Methods

Every aspect of FM is controlled by the form-configuration. Normally you provide a set of configuration options when you create a FM instance for a form. However all configuration is dynamic, and can be changed at any time. The most common configuration changes have special methods to simplify things. For example, changing field validation rules, disabling one or all fields, changing the error-messages (eg: a different language), etc.

getConfig()

@returns: Deep-clone of the entire form-configuration {object}

const formConfig = form.getConfig()

setConfig( config, options )

  • config*   {object}
    An object containing some form configuration.

  • options   {object}

    • options.replace   {boolean}   @default: false
      By default the passed config data is merged with the existing form configuration.
      Pass { replace: true } to replace the entire form config.

@returns: FormManager {object}

Modifies the form configuration.
The config structure is the same as the initial form configuration. See FormManager Configuration for details.


setFieldsConfig( config, options )

  • config*   {object|array<object>}
    An object containing some fields configuration.

  • options   {object}

    • options.replace   {boolean}   @default: false
      By default the supplied config data is merged with the existing field config.
      Pass { replace: true } to replace the config of each field specified.

@returns: FormManager {object}

Modifies the configuration of multiple form fields.
The config structure is the same as the initial form fields configuration. See FormManager Configuration for details.

form.setFieldConfig(
    'password',
    { disabled: true }
)

getFieldsConfig()

@returns: Fields Config {object}

💡
Use getFieldConfig(fieldname) to get a field-config using an alias-name.
const allConfig = getFieldsConfig()

// Fields config is keyed by REAL fieldnames, not aliases
const emailConfig = allConfig['profile.primaryEmail']

// Read field data
const emailAlias = emailConfig.aliasName // "email"
const emailRequired = emailConfig.validation.required

setFieldDefaults( config )

  • config*   {object}
    An object containing some fieldDefaults configuration.

@returns: FormManager {object}

Modifies the configuration for field-defaults.
The config structure is the same as the initial form fieldDefaults configuration. See FormManager Configuration for details.

form.setFieldDefaults({
    validateOnChange: true,
    cleaning: {
        trim: true,
        trimInner: true
    }
})

getFieldDefaults( key )

  • key*   {string}
    A simple key or a concatenated path like "cleaning.trim"

@returns: A fields-default value {any}

const allFieldsDisabled = form.getFieldDefaults('disabled')

setFieldConfig( fieldname, config, options )

  • fieldname*   {string}

  • config*   {object}
    An object containing some field configuration.

  • options   {object}  

    • options.replace   {boolean}   @default: false
      By default config data is merged with existing configuration.
      Pass { replace: true } to replace the entire field config.

@returns: FormManager {object}

Modifies the configuration of a single form field.
The config structure is the same as the initial configuration for a field. See FormManager Configuration for details.

form.setFieldConfig('password', { disabled: true })

getFieldConfig( fieldname )

  • fieldname*   {string}

@returns: Field Configuration {(object|undefined)}

Returns a copy of current field configuration.
Can be used to inspect the current settings, or as a starting point for creating an updated config.

// Get the field configuration
const emailConfig = form.getFieldConfig('email')

// Read some values from the config data
const { dataType, inputType, inputFormat } = emailConfig
const { required, minLength } = emailConfig.validation

setFieldValidation( fieldname, config )

  • fieldname*   {string}

  • config*   {object}
    A validation configuration — the same as for the initial form-config.
    See FormManager Configuration for details.

@returns: FormManager {object}

setFieldValidation(
    'username',
    { required: true, minLength: 8, maxLength: 24 }
)

getFieldValidation( fieldname )

  • fieldname*   {string}

@returns: Field Validation Configuration {object}

Returns a copy of current field validation configuration.
Can be used to inspect the current settings, or as a starting point for creating an updated config.

const passwordValidationConfig = getFieldValidation('password')

isRequired | isFieldRequired( fieldname )

  • fieldname*   {string}

@returns: true|false {boolean}

const isEmailRequired = form.isRequired('email')

setRequired | setFieldRequired( fieldname, enable )

  • fieldname*   {string}

  • enable   {boolean}   @default: true
    Pass a falsey value to make field not required

@returns: FormManager {object}

// Set Email field to be required
form.setRequired('email')
form.setRequired('email', true)

// Set Email field to be not-required
form.setRequired('email', false)

isDisabled | isFieldDisabled( fieldname )

  • fieldname*   {string}

@returns: true|false {boolean}

Returns the derived state of field-disabled.
Returns fieldDefaults.disabled value if fieldConfig.disabled is not explicitly set.

const isEmailDisabled = form.isDisabled('email')

setDisabled | setFieldDisabled( fieldname, enable )

  • fieldname*   {string}

  • enable   {boolean}   @default: true
    Pass a falsey value to make field not disabled

@returns: FormManager {object}

// Set Email field to be disabled
form.setDisabled('email')
form.setDisabled('email', true)

// Un-set Email disabled - will now follow fieldsDefault
form.setDisabled('email', false)

setDefaultDisabled( enable )

  • enable   {boolean}   @default: true
    Pass a falsey value to make fields default not disabled.

@returns: FormManager {object}

handleSubmit() {
    const { form } = this
    // Disable ALL form fields while submitting
    form.setDefaultDisabled(true)

    form.validateAll().then(isValid => {
        if (isValid) {
            // post the data...
        } else {
            // Re-enable all form fields
            form.setDefaultDisabled(false)
        }
    }
}

isReadOnly | isFieldReadOnly( fieldname )

  • fieldname*   {string}

@returns: true|false {boolean}

Returns the derived state of field-readOnly.
Returns fieldDefaults.readOnly value if fieldConfig.readOnly is not explicitly set.

const isEmailReadOnly = form.isReadOnly('email')

setReadOnly | setFieldReadOnly( fieldname, enable )

  • fieldname*   {string}

  • enable   {boolean}   @default: true
    Pass a falsey value to make field not readOnly

@returns: FormManager {object}

// Set Email field to be read-only
form.setReadOnly('email')
form.setReadOnly('email', true)

// Un-set Email read-only - will now follow fieldsDefault
form.setReadOnly('email', false)

setDefaultReadOnly( enable )

  • enable   [.small]#{boolean}   @default: true
    Pass a falsey value to make field default not readOnly

@returns: FormManager {object}

💡
Material-UI < 4.x does not pass-through the readOnly attribute from props, so do this explicitly if you use readOnly.
See the example below.
// Set all fields to be read-only
setDefaultReadOnly()
setDefaultReadOnly(true)

// Remove default readOnly state for all fields
setDefaultReadOnly(false)

// Explicitly set input.readOnly for Material-UI < 4.x
<TextField
    label="Username"
    {...form.allMuiProps('username')}
    inputProps={{
        readOnly: form.isReadOnly('username')
    }}
/>

Field Validation Methods

Validation can be configured for fields that require it, inside a validation key in the field configuration. Validation can be changed or overridden programatically.


validate( fieldname )

  • fieldname   {string}

@returns: Validation-Promise {Promise}

💡
If no fieldname is passed, then is same as calling validateAll().

Manually trigger validation of a single form-field.

form.validate('password')

validateAll()

@returns: FormManager {object}

Manually trigger validation of ALL form-fields.

form.validateAll()

ℹ️
See Configuration Methods for validation configuration methods.

Field Error-Message Methods

💡
It is easier and less error-prone to use the field-level error-handling features than to manually manage errors via error-setter methods.
Error-Message Language Support

Error-Messages are part of form configuration so can be changed on-the-fly like all configuration. This can be used to switch error-message languages. (See example below.)

setErrorMessages( errorMessages )

  • errorMessages*   {object}

@returns: FormManager {object}

import { englishErrors, frenchErrors } from './formErrorMessages'

function setErrorLanguage( language ) {
    form.setErrorMessages( `${language}Errors` )
}

hasError( fieldname )

  • fieldname*   {string}

@returns: true|false {boolean}

form.validate('password') // If not already validated
const passwordFieldHasError = form.hasError('password')

hasErrors()

@returns: true|false {boolean}

form.validateAll() // If not already validated
const formHasSomeErrors = form.hasErrors()

error | getError( fieldname, options )

  • fieldname*   {string}

  • options   {object}

    • options.asArray   {object}   @default: false
      Return field-errors as an array instead of a concatenated string.

Get error(s) for one field, as a string or an array.
By default errors are returned as a linebreak-delimited string so it can be rendered as-is. If returned as an array, errors must be iterated.

@returns: FormManager {object}

// Add a "whiteSpace" style so text line-breaks render correctly,
//  plus cosmetic styles so errors looks nice
<div style={{ whiteSpace: 'pre-line', lineHeight: '1.3em', color: 'red'  }}>
    {form.error('password')}
</div>

// Get errors as an array and render them one-by-one
{form.hasError('password') &&
    <ul>
      { const arrErrors = form.getError('password', { asArray: true }) }
      {arrErrors.map( error => (
        <li>{error}</li>
      )}
</ul>
}

setError( fieldname, type, errorMessage )

  • fieldname*   {string}

  • type*   {string}
    A validation type like "required", "minLength", "custom", etc.

  • errorMessage   {string|array<string>}
    A single error-message or an array of error-messages.
    If a falsey value or an empty array is passed, it means clear the error of this type.

@returns: FormManager {object}

ℹ️

Error messages use a templating system with variable replacement:

  • {name} is replaced with the field’s displayName, aliasName or name

  • {value} is replaced with the field-value being validated

form.setError(
    'username',
    'custom',
    // The '{name}' param will be replaced with "Username"
    '{name} can contain only letters and dashes'
)

setErrors( errors, options )

  • errors*   {object}
    An object of errors with fieldnames as keys and error-types as subkeys. If no error-type keys exists, it is assumed to be custom.

  • options   {object}

    • options.merge   {object}   @default: true
      By default the passed errors are merged with existing errors.
      Pass merge: false to replace all existing errors.

@returns: FormManager {object}

💡
You can use alias-names as field keys, even though real fieldnames are used internally to cache field errors.
form.setErrors(
    {
        username: {
            require: 'A username is required'
        },
        password: {
            minLength: 'Passwords must be at least 8 characters',
            // Any 'key' can be used for custom errors
            anything: 'Passwords should contain at least one symbol'
        },
        // This will become a 'custom' error-type
        email: 'Please enter an email address'
    },
    { merge: false }
)

clearError( fieldname )

  • fieldname*   {string}

@returns: FormManager {object}

Remove all errors from a single field — every validation-type.

form.clearError('username')

clearErrors( fieldnames )

  • fieldnames   {array<string>}
    Clears all errors for all fieldnames in this array.
    If no fieldnames passed, then same as calling clearAllErrors().

@returns: FormManager {object}

// Clear specific fields
form.clearErrors([ 'username', 'password' ])

// Clear all fields
form.clearErrors()
// is same as
form.clearAllErrors()

clearAllErrors()

@returns: FormManager {object}

Clear all field errors.

form.clearAllErrors()

Form 'Data' Methods

The 'form data' is the source for 'field values'. Generally you only use 'data' methods when setting form data from the server, or preparing to post data to the server.
Also see Field 'Value' Methods

data | getData()

@returns: All data {object}

// Get and submit all data - after validating it
form.validateAll()
.then(isValid => {
    if (isValid) {
        const formData = form.data()
        postToServer( formData )
    }
    // If validation failed, field-errors will display
}

getFieldData( fieldname )

  • fieldname*   {string}

@returns: Field value (cloned) {any}

const phone = form.data('phone')

setData( data )

  • data*   {object}

@returns: FormManager {object}

ℹ️
When 'data' is set, it also updates the initial-data cache, so a form.reset() will retain this value.
To set temporary values, use setValue() instead.
form.setData({
    phone: '6045551212',
    email: 'john.smith@gmail.com'
})

setFieldData( fieldname, value, options )

  • fieldname*   {string}

  • value   {any}

@returns: FormManager {object}

ℹ️
When 'field-data' is set, it also updates the initial-data cache, so a form.reset() will retain this value.
To set temporary values, use setFieldValue() instead.
form.setFieldData('phone', '6045551212')

isClean( fieldname )

  • fieldname*   {string} Pass a fieldname to check whether that field is unchanged

@returns: true|false {boolean}

Returns clean-state for a single field or entire form.

const isDataChanged = !form.isClean()

isDirty( fieldname )

  • fieldname*   {string} Pass a fieldname to check whether that field is changed

@returns: true|false {boolean}

Returns dirty-state for a single field or entire form.

const isDataChanged = form.isDirty()

changes | getChanges()

@returns: Changed data {object}

const changedData = form.changes()

reset()

@returns: FormManager {object}

Resets the form back to its initial data and all error-messages are cleared, (except initialErrors).

form.reset()

Field 'Value' Methods

The 'field values' are the raw value props of the form-fields. Values may differ from 'form data' in data-type or format. If you 'set' a value, it will be coerced into the format set in the field’s configuration.
Also see Form 'Data' Methods

value | getValue( fieldname )

  • fieldname*   {string}

  • options   {object}

    • options.clean   {boolean}   @default: false
      Apply cleaning to field value before returning it.

@returns: Field value {any}

const phone = form.getValue('phone', { clean: true })

getValues()

@returns: Form-field values {object}

Returns a deep-clone of form values to preserve immutability.

const formValues = form.values()

setValue( fieldname, value, options )

  • fieldname*   {string}

  • value*   {any}

  • options   {object}

    • options.validate   {boolean}   @default: false
      Validate the field after setting the new value.

Value is automatically converted to the correct data-type and reformatted as specified in the field configuration.

@returns: FormManager {object}

form.setValue('phone', '604-555-1212', { validate: true })

setValues( values, options )

  • values*   {object}
    An 1-level object with fieldnames as keys and field-values as values.

  • options   {object}

    • options.clean   {boolean}   @default: false
      Clean the value per the config settings.

    • options.validate   {boolean}   @default: false
      Validate the field after setting the new value.

Field-values is a single-level object so nested keys must use a dot-delimited format like 'user.profile.homePhone'. However field alias-names can also be used as keys.

@returns: FormManager {object}

form.setValues(
    {
        // 'phone' is the aliasName for 'user.profile.homePhone'
        phone, '604-555-1212',
        'user.profile.nickname': 'symore',
        rememberMe: true
    },
    { validate: true }
)

cleanField | cleanFieldValue( fieldname )

  • fieldname*   {string}

@returns: FormManager {object}

Triggers cleaning of the current field value according to the field configuration.

form.cleanField('phone')

Form 'State' Methods

state | getState( key, options )

  • key*   {string}
    The key to get from form-state. Nested values can be specified using a dot-delimited key like category.selected. Can also pass a fieldname-alias here.

  • options   {object}

    • options.clone   {boolean}   @default: false
      Return a shallow-clone of the state value, if applicable.

    • options.cloneDeep   {boolean}   @default: false
      Return a deep-clone of the state value, if applicable.

@returns: State-value {any}

Returns a value from form-state, or undefined if key/path does not exist.

// Add user data to form-state
form.setState('user', this.props.userProfile)

// Read nested data from form-state
const username = form.state('user.nickname')

setState( key, value, options )

  • key*   {string}
    The key to set in form-state. Nested values can be set by passing a dot-delimited key like category.selected. Can also pass a fieldname-alias here.

  • value   {any}  
    Anything can be stored in form-state.
    If no value is passed, the specified key will be deleted.

  • options   {object}

    • options.update   {boolean}   @default: true
      Re-render component after updating state.

    • options.clone   {boolean}   @default: true
      Shallow-clone the new state value, if applicable.

    • options.cloneDeep   {boolean}   @default: false
      Deep-clone the new state value, if applicable. (overrides 'clone')

@returns: FormManager {object}

Sets a value in FM state so is accessible everywhere.

// Add user data to form-state
form.setState('user', this.props.userProfile)

// Remove user data from form-state
form.setState('user', undefined)
// or just
form.setState('user')

Form Rendering Methods

update | render()

@returns: FormManager {object}

Forces a re-render of the component FM is inside.
A render is automatically triggered when necessary. However, if you programmatically change form configuration, you need to manually trigger an update for the changes to take effect. This method provides that, and also increments the form revision value.

form.update()

revision | getRevision()

@returns: Revision {integer}

UID useful for forcing component re-rendering.
If using React.PureComponent or a custom shouldComponentUpdate(), some prop must change to trigger a component render. Since the form-manager object/prop never changes, another prop is needed to force component updates. The form revision number provides this prop. It increments each time anything in FM changes — including every keystroke!

<FormFields form={this.form} rev={this.form.revision()} />

Form-Field Helpers & Handlers

These methods are generally used only in form-field props, but the event handlers can also be triggered programmatically.


fieldProps( fieldname, options )

  • fieldname*   {string}

  • options   {object}

    • options.inputType   {string}   @default: null
      Allows overriding the inputType set in field configuration.

@returns: Data-related props {object}

This helper returns many props, including value & name, input attributes, aria attributes, and event handlers.

<label>
    <input {...fieldProps('password'} />
    Remember Me
</label>

<Switch
    label="Remember Me"
    {...fieldProps('password'}
/>

muiErrorProps( fieldname )

  • fieldname*   {string}

@returns: Error-related props {object}

This helper is specific to Material-UI components.
This returns only: error={hasError(fieldname)} and helperText={getError(fieldname)}

<TextField
    label="Full Name"
    {...form.fieldProps('name')}
    {...form.muiErrorProps('name')}
/>
// Is the same as...
<TextField label="Full Name" {...form.allMuiProps('name')} />

allMuiProps( fieldname, options )

  • fieldname*   {string}

  • options   {object}
    Options are passed-through to fieldProps(); see below.

@returns: FormManager {object}

This helper is specific to Material-UI components.
It returns the combined fields from: fieldProps(fieldname) (any field) and muiErrorProps(fieldname)

<TextField
    label="Password"
    {...allMuiProps('password')}
/>

onFieldChange( event_or_fieldname, value )

  • event_or_fieldname*   {string} — either:

    • event   {Event}
      The DOM Event object returned if bound to onChange, etc.

    • fieldname   {string}
      Fieldname, for calling this handler manually.

  • value   {any}
    Value, for calling this handler manually
    (event.target.value used if a value not passed)

@returns: FormManager {object}

<input name="note" onChange={form.onChange} />
<DatePicker onChange={date => form.onChange('birthdate', date)} />

onFieldBlur( event_or_fieldname, value )

  • event_or_fieldname*   {string} — either:

    • event   {Event}
      The DOM Event object returned if bound to onChange, etc.

    • fieldname   {string}
      Fieldname, for calling this handler manually.

  • value   {any}
    Value, for calling this handler manually
    (event.target.value used if a value not passed)

@returns: FormManager {object}

<input name="note" onChange={form.onBlur} />
<DatePicker onBlur={date => form.onBlur('birthdate', date)} />

onFieldFocus( event_or_fieldname, value )

  • event_or_fieldname*   {string} — either:

    • event   {Event}
      The DOM Event object returned if bound to onChange, etc.

    • fieldname   {string}
      Fieldname, for calling this handler manually.

  • value   {any}
    Value, for calling this handler manually
    (event.target.value used if a value not passed)

@returns: FormManager {object}

<input name="note" onFocus={form.onFocus} />
<DatePicker onFocus={date => form.onFocus('birthdate', date)} />