Tutorial: Form State

Form State

Input States

Inputs have two implicit states which manages certain CSS classes.

Dirty Input

Inputs are deemed dirty when their current value differs from the previously saved. If configured, the dirtyCssClass will be added to the input. The saved value can be updated using the save method or a Submit button.

Invalid Input

Inputs have the invalidCssClass added to them when their validation fails.

Form States

Forms have access to three states for their inputs: dirty, errors, and values. Forms also have a isDirty and isValid signal.

Dirty State

// Example state
F.dirty == {
  password: {
    old: '',
    new: '********'
  },
  nickname: {
    old: '',
    new: 'root'
  }
}

The dirty state is linked to an internal commit state that contains the form's default values or whatever values last set by save. Keys are only added to the dirty state when input values differ from their committed state. The changes are provided as an object containing both the old and new value(e.g. { old: '', new: 'user@domain.tld' }).

There are two ways of updating the commit state. The first and easiest being Submit which will save the commit state when the form is valid. The other option is using the save method and a button which will save the form regardless of its validity. If there's a need to check the current commit state, it can be done with a combination of dirty and values(see Internal Commit State).

Calling reset or using the Reset button will discard all changes. This includes inputs that are otherwise valid. Users wanting to save valid inputs and reset invalid ones can do so with dirty, errors, and save(see Partial Form Saving).

Dirty Signal

(Since v1.3.0)
If any input is deemed dirty then the signal returns true otherwise it returns false.

<Show when={F.isDirty}>You have unsaved changes</Show>

Errors State

// Example state
F.errors == {
  username: 'is not an email address',
  password: 'is required'
}

Validation happens on...

Whenever an input fails validation, the errors state will contain the name of invalid inputs as keys and the first error they encountered as values. Keys are added to the state when an input becomes invalid and they are removed when the input becomes valid.

Valid Signal

(Since v1.3.0)
If any input is deemed invalid then the signal returns false otherwise it returns true.

<Show when={!F.isValid}>Please correct the errors below</Show>

Values State

// Example state
F.values == {
  username: 'user@domain.tld',
  password: '********',
  nickname: 'root'
}

The values of inputs is always available through the values state. This state is primarily updated using the onKeyUp event on inputs which simply copies event.target.value. It's also updated using the onBlur and onChange events which transform values before setting them.

Internal Commit State

ValidatedForm instances don't have access to the internal commit state but, since the dirty state is based off of the commit state, the internal commit state can easily be derived with a bit of logic. Simple implementation of commit state logging.

const F = ValidatedForm({
  username (value) {
    if (value === '') return 'is required'
  },
  nickname () {
    // Accept any value
  }
})

// Outputs the commit state to console
const log = () => {
  const state = {}

  Object.keys(F.values).forEach((name) => {
    // Note we're accessing the old value of dirty
    state[name] = F.dirty[name] ? F.dirty[name].old : F.values[name]
  })

  console.log('current commit state:')
  console.dir(state)
}
<F.Form>
  <F.Text name='username' />
  <F.Text name='nickname' />
  <F.Submit />
  <F.Reset />
  <F.Button onClick={log} label='Log State' />
</F.Form>

Partial Form Saving

Simple implementation of partial form saving.

const F = ValidatedForm({
  username (value) {
    if (value === '') return 'is required'
  },
  nickname () {
    // Accept any value
  }
})

// Saves all the dirty inputs that are valid
const save = () => {
  Object.keys(F.dirty).forEach((name) => {
    // Saving one input at a time
    if (!F.errors[name]) F.save(name)
  })
}
<F.Form>
  <F.Text name='username' />
  <F.Text name='nickname' />
  <F.Submit />
  <F.Reset />
  <F.Button onClick={save} label='Save' />
</F.Form>