Skip to content

🏁 Framework agnostic, high performance, subscription-based form state management

License

Notifications You must be signed in to change notification settings

k-j-kleist/final-form

Β 
Β 

Repository files navigation

🏁 Final Form

Final Form

NPM Version NPM Downloads Build Status codecov.io styled with prettier

βœ… Zero dependencies *

βœ… Framework agnostic

βœ… Opt-in subscriptions - only update on the state you need!

βœ… πŸ’₯ 4.6k gzipped πŸ’₯


πŸ’¬ Give Feedback on Final Form πŸ’¬

In the interest of making 🏁 Final Form the best library it can be, we'd love your thoughts and feedback.

Take a quick survey.


Installation

npm install --save final-form

or

yarn add final-form

Zero Dependencies

Technically there is a dependency on @babel/runtime, but this actually makes your final bundle size smaller by sharing common babel code between libraries.

Getting Started

🏁 Final Form works on subscriptions to perform updates based on the Observer pattern. Both form and field subscribers must specify exactly which parts of the form state they want to receive updates about.

import { createForm } from 'final-form'

// Create Form
const form = createForm({
  initialValues,
  onSubmit, // required
  validate
})

// Subscribe to form state updates
const unsubscribe = form.subscribe(
  formState => {
    // Update UI
  },
  { // FormSubscription: the list of values you want to be updated about
    dirty: true,
    valid: true,
    values: true
  }
})

// Subscribe to field state updates
const unregisterField = form.registerField(
  'username',
  fieldState => {
    // Update field UI
    const { blur, change, focus, ...rest } = fieldState
    // In addition to the values you subscribe to, field state also
    // includes functions that your inputs need to update their state.
  },
  { // FieldSubscription: the list of values you want to be updated about
    active: true,
    dirty: true,
    touched: true,
    valid: true,
    value: true
  }
)

// Submit
form.submit() // only submits if all validation passes

Table of Contents

Examples

Demonstrates how 🏁 Final Form can be used inside a React component to manage form state. It also shows just how much 🏁 React Final Form does for you out of the box.

For more examples using React, see 🏁 React Final Form Examples.

Official Mutators

Mutators are functions that can be provided to 🏁 Final Form that are allowed to mutate any part of the form state. You provide an object of mutators when you call createForm(), and then they are exposed (bound to the form state) on the form instance you get back, under form.mutators.whatever(). A mutator function is given: the arguments it was called with, the form state, and a collection of utility functions, like getIn and setIn to read and mutate arbitrarily deep values, as well as changeValue, which updates a field value in the form state. They can mutate the state however they wish, and then the form and field subscribers that need to be notified will be notified. Read more about Mutators.

Helps managing array structures in form data.

Sets arbitrary data for fields.

Allows control over the touched flags for fields.

Libraries

A form state management system for React that uses 🏁 Final Form under the hood.

A form state management system for Vue that uses 🏁 Final Form under the hood.

Define Form offers alternative typescript bindings for 🏁 Final Form. The key difference is that the form data is now a strongly typed object, rather than an any. This makes the initialValues config option required.

A "decorator" that will attempt to apply focus to the first field with an error upon an attempted form submission.

A swap-in replacement for 🏁 React Final Form's <Field> component to provide HTML5 Validation.

A collection of useful components for listening to fields in a 🏁 React Final Form.

Field Names

Field names are strings that allow dot-and-bracket syntax, allowing you to create arbitrarily deeply nested fields. There are four main things you need to understand about how field names are used to read and write the form values in 🏁 Final Form.

  • . and [ are treated the same.
  • ] is ignored.
  • Number keys will result in array structures. Why?
  • Setting undefined to a field value deletes any empty object – but not array! – structures. Why?

It is very similar to Lodash's _.set(), except that empty structures are removed. Let's look at some examples:

Field Name Initial Structure Setting Value Result
bar {} 'foo' { bar: 'foo' }
bar.frog {} 'foo' { bar: { frog: 'foo' } }
bar[0] {} 'foo' { bar: [ 'foo' ] }
bar.0 {} 'foo' { bar: [ 'foo' ] }
bar[1] {} 'foo' { bar: [ null, 'foo' ] }
bar[0].frog {} 'foo' { bar: [ { frog: 'foo' } ] }
bar { bar: 'foo' } undefined { }
bar.frog { bar: { frog: 'foo' }, other: 42 } undefined { other: 42 }
bar.frog[0] { bar: { frog: [ 'foo' ] } } undefined { bar: { frog: [ null ] } }

Here is a sandbox that you can play around with to get a better understanding of how it works.

API

The following can be imported from final-form.

createForm: (config: Config) => FormApi

Creates a form instance. It takes a Config and returns a FormApi.

fieldSubscriptionItems: string[]

An Γ  la carte list of all the possible things you can subscribe to for a field. Useful for subscribing to everything.

formSubscriptionItems: string[]

An Γ  la carte list of all the possible things you can subscribe to for a form. Useful for subscribing to everything.

ARRAY_ERROR: string

A special string key used to return an error for an array of fields.

FORM_ERROR: string

A special string key used to return a whole-form error inside error objects returned from validation or submission.

getIn(state: Object, complexKey: string): any

setIn(state: Object, key: string, value: any): Object

version: string

The current used version of 🏁 Final Form.


Types

Config

debug?: DebugFunction

destroyOnUnregister?: boolean

If true, the value of a field will be destroyed when that field is unregistered. Defaults to false. Can be useful when creating dynamic forms where only form values displayed need be submitted.

keepDirtyOnReinitialize?: boolean

If true, only pristine values will be overwritten when initialize(newValues) is called. This can be useful for allowing a user to continue to edit a record while the record is being saved asynchronously, and the form is reinitialized to the saved values when the save is successful. Defaults to false.

initialValues?: Object

The initial values of your form. These will also be used to compare against the current values to calculate pristine and dirty.

mutators?: { [string]: Mutator }

Optional named mutation functions.

onSubmit: (values: Object, form: FormApi, callback: ?(errors: ?Object) => void) => ?Object | Promise<?Object> | void

Function to call when the form is submitted. There are three possible ways to write an onSubmit function:

  • Synchronously: returns undefined on success, or an Object of submission errors on failure
  • Asynchronously with a callback: returns undefined, calls callback() with no arguments on success, or with an Object of submission errors on failure.
  • Asynchronously with a Promise: returns a Promise<?Object> that resolves with no value on success or resolves with an Object of submission errors on failure. The reason it resolves with errors is to leave rejection for when there is a server or communications error.

Submission errors must be in the same shape as the values of the form. You may return a generic error for the whole form (e.g. 'Login Failed') using the special FORM_ERROR string key.

validate?: (values: Object) => Object | Promise<Object>

A whole-record validation function that takes all the values of the form and returns any validation errors. There are three possible ways to write a validate function:

  • Synchronously: returns {} or undefined when the values are valid, or an Object of validation errors when the values are invalid.
  • Asynchronously with a Promise: returns a Promise<?Object> that resolves with no value on success or resolves with an Object of validation errors on failure. The reason it resolves with errors is to leave rejection for when there is a server or communications error.

Validation errors must be in the same shape as the values of the form. You may return a generic error for the whole form using the special FORM_ERROR string key.

validateOnBlur?: boolean

If true, validation will happen on blur. If false, validation will happen on change. Defaults to false.

DebugFunction: (state: FormState, fieldStates: { [string]: FieldState }) => void

An optional callback for debugging that receives the form state and the states of all the fields. It's called on every state change. A typical thing to pass in might be console.log.

Decorator: (form: FormApi) => Unsubscribe

A function that decorates a form by subscribing to it and making changes as the form state changes, and returns an Unsubscribe function to detach itself from the form. e.g. 🏁 Final Form Calculate.

FieldConfig

isEqual?: (a: any, b: any) => boolean

A function to determine if two values are equal. Defaults to ===.

getValidator?: () => (value: ?any, allValues: Object, meta: FieldState) => ?any | ?Promise<any>

A callback that will return a field-level validation function to validate a single field value. The validation function should return an error if the value is not valid, or undefined if the value is valid.

validateFields?: string[]

An array of field names to validate when this field changes. If undefined, every field will be validated when this one changes; if [], only this field will have its field-level validation function called when it changes; if other field names are specified, those fields and this one will be validated when this field changes.

FieldState

FieldState is an object containing:

active?: boolean

Whether or not the field currently has focus.

blur: () => void

A function to blur the field (mark it as inactive).

change: (value: any) => void

A function to change the value of the field.

data?: Object

A place for arbitrary values to be placed by mutators.

dirty?: boolean

true when the value of the field is not equal to the initial value (using the isEqual comparator provided at field registration), false if the values are equal.

dirtySinceLastSubmit?: boolean

true when the value of the field is not equal to the value last submitted (using the isEqual comparator provided at field registration), false if the values are equal.

error?: any

The current validation error for this field.

focus: () => void

A function to focus the field (mark it as active).

initial?: any

The initial value of the field. undefined if it was never initialized.

invalid?: boolean

true if the field has a validation error or a submission error. false otherwise.

length?: number

The length of the array if the value is an array. undefined otherwise.

name: string

The name of the field.

pristine?: boolean

true if the current value is === to the initial value, false if the values are !==.

submitError?: any

The submission error for this field.

submitFailed?: boolean

true if a form submission has been tried and failed. false otherwise.

submitSucceeded?: boolean

true if the form has been successfully submitted. false otherwise.

submitting?: boolean

true if the form is currently being submitted asynchronously. false otherwise.

touched?: boolean

true if this field has ever gained and lost focus. false otherwise. Useful for knowing when to display error messages.

valid?: boolean

true if this field has no validation or submission errors. false otherwise.

value?: any

The value of the field.

visited?: boolean

true if this field has ever gained focus.

FieldSubscriber: (state: FieldState) => void

FieldSubscription: { [string]: boolean }

FieldSubscription is an object containing the following:

active?: boolean

When true the FieldSubscriber will be notified of changes to the active value in FieldState.

data?: boolean

When true the FieldSubscriber will be notified of changes to the data value in FieldState.

dirty?: boolean

When true the FieldSubscriber will be notified of changes to the dirty value in FieldState.

dirtySinceLastSubmit?: boolean

When true the FieldSubscriber will be notified of changes to the dirtySinceLastSubmit value in FieldState.

error?: boolean

When true the FieldSubscriber will be notified of changes to the error value in FieldState.

initial?: boolean

When true the FieldSubscriber will be notified of changes to the initial value in FieldState.

invalid?: boolean

When true the FieldSubscriber will be notified of changes to the invalid value in FieldState.

length?: boolean

When true the FieldSubscriber will be notified of changes to the length value in FieldState.

pristine?: boolean

When true the FieldSubscriber will be notified of changes to the pristine value in FieldState.

submitError?: boolean

When true the FieldSubscriber will be notified of changes to the submitError value in FieldState.

submitFailed?: boolean

When true the FieldSubscriber will be notified of changes to the submitFailed value in FieldState.

submitSucceeded?: boolean

When true the FieldSubscriber will be notified of changes to the submitSucceeded value in FieldState.

submitting?: boolean

When true the FieldSubscriber will be notified of changes to the submitting value in FieldState.

touched?: boolean

When true the FieldSubscriber will be notified of changes to the touched value in FieldState.

valid?: boolean

When true the FieldSubscriber will be notified of changes to the valid value in FieldState.

value?: boolean

When true the FieldSubscriber will be notified of changes to the value value in FieldState.

visited?: boolean

When true the FieldSubscriber will be notified of changes to the visited value in FieldState.

FormApi

batch: (fn: () => void) => void)

Allows batch updates by silencing notifications while the fn is running. Example:

form.batch(() => {
  form.change('firstName', 'Erik') // listeners not notified
  form.change('lastName', 'Rasmussen') // listeners not notified
}) // NOW all listeners notified

blur: (name: string) => void

Blurs (marks inactive) the given field.

change: (name: string, value: ?any) => void

Changes the value of the given field.

focus: (name: string) => void

Focuses (marks active) the given field.

getFieldState: (field: string) => ?FieldState

Returns the state of a specific field as it was last reported to its listeners, or undefined if the field has not been registered.

getRegisteredFields: () => string[]

Returns a list of all the currently registered fields.

getState: () => FormState

A way to request the current state of the form without subscribing.

initialize: (values: Object) => void

Initializes the form to the values provided. All the values will be set to these values, and dirty and pristine will be calculated by performing a shallow-equals between the current values and the values last initialized with. The form will be pristine after this call.

isValidationPaused: () => boolean

Returns true if validation is currently paused, false otherwise.

mutators: ?{ [string]: Function }

The state-bound versions of the mutators provided to Config.

pauseValidation: () => void

If called, validation will be paused until resumeValidation() is called.

registerField: RegisterField

Registers a new field and subscribes to changes to it. The subscriber will only be called when the values specified in subscription change. More than one subscriber can subscribe to the same field.

This is also where you may provide an optional field-level validation function that should return undefined if the value is valid, or an error. It can optionally return a Promise that resolves (not rejects) to undefined or an error.

reset: (initialValues: ?Object) => void

Resets the values back to the initial values the form was initialized with. Or empties all the values if the form was not initialized. If you provide initialValues they will be used as the new initial values.

Note that if you are calling reset() and not specify new initial values, you must call it with no arguments. Be careful to avoid things like promise.catch(reset) or onChange={form.reset} in React, as they will get arguments passed to them and reinitialize your form.

resumeValidation: () => void

Resumes validation paused by pauseValidation(). If validation was blocked while it was paused, validation will be run.

submit: () => ?Promise<?Object>

Submits the form if there are currently no validation errors. It may return undefined or a Promise depending on the nature of the onSubmit configuration value given to the form when it was created.

subscribe: (subscriber: FormSubscriber, subscription: FormSubscription) => Unsubscribe

Subscribes to changes to the form. The subscriber will only be called when values specified in subscription change. A form can have many subscribers.

FormState

active?: string

The name of the currently active field. undefined if none are active.

dirty?: boolean

true if the form values are different from the values it was initialized with. false otherwise. Comparison is done with shallow-equals.

dirtyFields?: { [string]: boolean }

An object full of booleans, with a value of true for each dirty field. Pristine fields will not appear in this object. Note that this is a flat object, so if your field name is addresses.shipping.street, the dirty value for that field will be available under dirty['addresses.shipping.street'].

dirtySinceLastSubmit?: boolean

true if the form values are different from the values it was last submitted with. false otherwise. Comparison is done with shallow-equals.

error?: any

The whole-form error returned by a validation function under the FORM_ERROR key.

errors?: Object

An object containing all the current validation errors. The shape will match the shape of the form's values.

hasSubmitErrors?: boolean

true when the form currently has submit errors. Useful for distinguishing why invalid is true.

hasValidationErrors?: boolean

true when the form currently has validation errors. Useful for distinguishing why invalid is true. For example, if your form is invalid because of a submit error, you might also want to disable the submit button if user's changes to fix the submit errors causes the form to have sync validation errors.

initialValues?: Object

The values the form was initialized with. undefined if the form was never initialized.

invalid?: boolean

true if any of the fields or the form has a validation or submission error. false otherwise. Note that a form can be invalid even if the errors do not belong to any currently registered fields.

pristine?: boolean

true if the form values are the same as the initial values. false otherwise. Comparison is done with shallow-equals.

submitError?: any

The whole-form submission error returned by onSubmit under the FORM_ERROR key.

submitErrors?: Object

An object containing all the current submission errors. The shape will match the shape of the form's values.

submitFailed?: boolean

true if the form was submitted, but the submission failed with submission errors. false otherwise.

submitSucceeded?: boolean

true if the form was successfully submitted. false otherwise.

submitting?: boolean

true if the form is currently being submitted asynchronously. false otherwise.

touched?: { [string]: boolean }

An object full of booleans, with a boolean value for each field name denoting whether that field is touched or not. Note that this is a flat object, so if your field name is addresses.shipping.street, the touched value for that field will be available under touched['addresses.shipping.street'].

valid?: boolean

true if neither the form nor any of its fields has a validation or submission error. false otherwise. Note that a form can be invalid even if the errors do not belong to any currently registered fields.

validating?: boolean

true if the form is currently being validated asynchronously. false otherwise.

values?: Object

The current values of the form.

visited?: { [string]: boolean }

An object full of booleans, with a boolean value for each field name denoting whether that field is visited or not. Note that this is a flat object, so if your field name is addresses.shipping.street, the visited value for that field will be available under visited['addresses.shipping.street'].

FormSubscriber: (state: FormState) => void

FormSubscription: { [string]: boolean }

FormSubscription is an object containing the following:

active?: boolean

When true the FormSubscriber will be notified of changes to the active value in FormState.

dirty?: boolean

When true the FormSubscriber will be notified of changes to the dirty value in FormState.

dirtyFields?: boolean

When true the FormSubscriber will be notified of changes to the dirtyFields value in FormState.

dirtySinceLastSubmit?: boolean

When true the FormSubscriber will be notified of changes to the dirtySinceLastSubmit value in FormState.

error?: boolean

When true the FormSubscriber will be notified of changes to the error value in FormState.

errors?: boolean

When true the FormSubscriber will be notified of changes to the errors value in FormState.

hasSubmitErrors?: boolean

When true the FormSubscriber will be notified of changes to the hasSubmitErrors value in FormState.

hasValidationErrors?: boolean

When true the FormSubscriber will be notified of changes to the hasValidationErrors value in FormState.

initialValues?: boolean

When true the FormSubscriber will be notified of changes to the initialValues value in FormState.

invalid?: boolean

When true the FormSubscriber will be notified of changes to the invalid value in FormState.

pristine?: boolean

When true the FormSubscriber will be notified of changes to the pristine value in FormState.

setConfig: (name: string, value: any) => void

Allows mutating the values on the original Config.

submitError?: boolean

When true the FormSubscriber will be notified of changes to the submitError value in FormState.

submitErrors?: boolean

When true the FormSubscriber will be notified of changes to the submitErrors value in FormState.

submitFailed?: boolean

When true the FormSubscriber will be notified of changes to the submitFailed value in FormState.

submitSucceeded?: boolean

When true the FormSubscriber will be notified of changes to the submitSucceeded value in FormState.

submitting?: boolean

When true the FormSubscriber will be notified of changes to the submitting value in FormState.

touched?: boolean

When true the FormSubscriber will be notified of changes to the touched value in FormState.

valid?: boolean

When true the FormSubscriber will be notified of changes to the valid value in FormState.

validating?: boolean

When true the FormSubscriber will be notified of changes to the validating value in FormState.

values?: boolean

When true the FormSubscriber will be notified of changes to the values value in FormState.

visited?: boolean

When true the FormSubscriber will be notified of changes to the visited value in FormState.

InternalFieldState

Very similar to the published FieldState.

active: boolean

Whether or not the field currently has focus.

blur: () => void

A function to blur the field (mark it as inactive).

change: (value: any) => void

A function to change the value of the field.

data: Object

A place for arbitrary values to be placed by mutators.

focus: () => void

A function to focus the field (mark it as active).

isEqual: (a: any, b: any) => boolean

A function to determine if two values are equal. Used to calculate pristine/dirty.

name: string

The name of the field.

touched: boolean

true if this field has ever gained and lost focus. false otherwise. Useful for knowing when to display error messages.

validateFields: ?(string[])

Fields to validate when this field value changes.

validators: { [number]: (value: ?any, allValues: Object, meta: FieldState) => ?any | Promise<?any> } }

Field-level validators for each field that is registered.

valid: boolean

true if this field has no validation or submission errors. false otherwise.

visited: boolean

true if this field has ever gained focus.

InternalFormState

Very similar to the published FormState, with a few minor differences.

active?: string

The name of the currently active field. undefined if none are active.

dirtySinceLastSubmit: boolean

true if the form values have changed since the last time the form was submitted, false otherwise.

error?: any

The whole-form error returned by a validation function under the FORM_ERROR key.

errors: Object

An object containing all the current validation errors. The shape will match the shape of the form's values.

initialValues?: Object

The values the form was initialized with. undefined if the form was never initialized.

lastSubmittedValues?: Object

The values last submitted. Used to calculate dirtySinceLastSubmit.

pristine: boolean

true if the form values are the same as the initial values. false otherwise. Comparison is done with shallow-equals.

submitError: any

The whole-form submission error returned by onSubmit under the FORM_ERROR key.

submitErrors?: Object

An object containing all the current submission errors. The shape will match the shape of the form's values.

submitFailed: boolean

true if the form was submitted, but the submission failed with submission errors. false otherwise.

submitSucceeded: boolean

true if the form was successfully submitted. false otherwise.

submitting: boolean

true if the form is currently being submitted asynchronously. false otherwise.

valid: boolean

true if neither the form nor any of its fields has a validation or submission error. false otherwise. Note that a form can be invalid even if the errors do not belong to any currently registered fields.

validating: number

The number of asynchronous validators currently running.

values: Object

The current values of the form.

MutableState

MutableState is an object containing the following:

formState: InternalFormState

The InternalFormState.

fields: { [string]: InternalFieldState }

An object of InternalFieldStates.

fieldSubscribers: { [string]: Subscribers<FieldState> }

An object of field subscribers.

lastFormState: ?InternalFormState

The last form state sent to form subscribers.

Mutator: (args: any[], state: MutableState, tools: Tools) => any

A mutator function that takes some arguments, the internal form MutableState, and some Tools and optionally modifies the form state.

RegisterField: (name: string, subscriber: FieldSubscriber, subscription: FieldSubscription, config?: FieldConfig) => Unsubscribe

Takes a name, and a FieldSubscriber, FieldSubscription, and a FieldConfig and registers a field subscription. Learn more about how Field Names.

Tools

An object containing:

Tools.changeValue: (state: MutableState, name: string, mutate: (value: any) => any) => void

A utility function to modify a single field value in form state. mutate() takes the old value and returns the new value.

Tools.getIn: (state: Object, complexKey: string) => any

A utility function to get any arbitrarily deep value from an object using dot-and-bracket syntax (e.g. some.deep.values[3].whatever).

Tools.renameField: (state: MutableState, from: string, to: string) => any) => void

A utility function to rename a field, copying over its value and field subscribers. Advanced usage only.

Tools.setIn: (state: Object, key: string, value: any) => Object

A utility function to set any arbitrarily deep value inside an object using dot-and-bracket syntax (e.g. some.deep.values[3].whatever). Note: it does not mutate the object, but returns a new object.

Tools.shallowEqual: (a: any, b: any) => boolean

A utility function to compare the keys of two objects. Returns true if the objects have the same keys and the values are ===, false otherwise.

Unsubscribe : () => void

Unsubscribes a listener.

Contributors

This project exists thanks to all the people who contribute. [Contribute].

Backers

Thank you to all our backers! πŸ™ [Become a backer]

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]

About

🏁 Framework agnostic, high performance, subscription-based form state management

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 98.8%
  • TypeScript 1.2%