Values & State
Understanding the value and state management mechanism in Form and Field is key to using the core package correctly.
Field Values
Field uses the following state to describe its current value, initial value, and most recent input:
| Value | Description | When Updated |
|---|---|---|
value | Current field and aggregated form value | onInput, setValue, or another form value write |
inputValue | First argument of the latest onInput | Every time onInput is called |
inputValues | All arguments of the latest onInput | Every onInput, for multi-argument component events |
initialValue | Initial field value | On field creation or through setInitialValue |
value vs inputValue
const field = form.createField({
name: 'name',
value: 'initial',
})
console.log(field.value) // 'initial'
console.log(field.inputValue) // null
// Simulate user input: record its source and update the field value
await field.onInput('user input')
console.log(field.inputValue) // 'user input'
console.log(field.inputValues) // ['user input']
console.log(field.value) // 'user input'
console.log(form.values.name) // 'user input'
console.log(field.selfModified) // true
// A programmatic update changes the value without pretending to be user input
field.setValue('programmatic value')
console.log(field.value) // 'programmatic value'
console.log(form.values.name) // 'programmatic value'
console.log(field.inputValue) // 'user input'inputValue is not an uncommitted value waiting for blur. onInput() immediately writes its first argument to value, allowing text inputs, selects, radios, checkboxes, and other components to share the same input channel. The states differ by source and lifecycle semantics:
valueis the current business value and can be updated by bothonInput()and programmatic writesinputValue/inputValuesrecord the arguments from the latestonInput()callonInput()also marks the field as modified, emits input-change lifecycles, and runs validation withtriggerType: 'onInput'setValue()performs a programmatic value update without changinginputValueor actively marking the field as modified
For components that pass multiple arguments, inputValues keeps the complete argument list, while value and inputValue use the first argument:
const option = { label: 'Administrator', value: 'admin' }
await field.onInput('admin', option)
console.log(field.value) // 'admin'
console.log(field.inputValue) // 'admin'
console.log(field.inputValues) // ['admin', option]Value Hierarchy
Form values are an aggregated view of all field values:
Form.values = {
username: 'silver', // ← Field(name: 'username').value
email: '[email protected]', // ← Field(name: 'email').value
profile: { // ← ObjectField(name: 'profile')
name: 'Silver',
age: 18, // ← Field(name: 'profile.age').value
},
items: [ // ← ArrayField(name: 'items')
{ title: 'Item 1' }, // ← Field(name: 'items.0.title').value
{ title: 'Item 2' }, // ← Field(name: 'items.1.title').value
],
}Deep Value Operations
// Set a nested value
form.setValuesIn('profile.name', 'New Name')
// Get a nested value
const name = form.getValuesIn('profile.name')
// Delete a nested value
form.deleteValuesIn('profile.temp')
// Check whether a path exists
const exists = form.existValuesIn('profile.name')Merge Strategies
Form provides multiple merge strategies:
// Deep merge (default)
form.setValues({ profile: { name: 'new' } })
// Preserves omitted profile properties and other top-level properties
// Shallow merge
form.setValues({ username: 'new' }, 'shallowMerge')
// Deep merge (currently identical to the default merge strategy)
form.setValues({ profile: { name: 'new' } }, 'deepMerge')
// Result: { profile: { name: 'new', age: 18 } }
// Overwrite — replaces the complete form value
form.setValues({ username: 'new' }, 'overwrite')
// Result: { username: 'new' }merge and deepMerge currently merge plain objects recursively and replace arrays as a whole. shallowMerge only merges the first level, while overwrite replaces the complete values object.
Form State
Key FormState Fields
| State | Type | Description |
|---|---|---|
values | T | Current form values |
initialValues | T | Initial form values |
modified | boolean | Whether any field modified |
valid | boolean | Validation passed |
invalid | boolean | Validation failed |
submitting | boolean | Submitting in progress |
validating | boolean | Validating in progress |
loading | boolean | Loading state |
errors | IFormFeedback[] | Form-level errors |
warnings | IFormFeedback[] | Form-level warnings |
self-prefix vs Aggregated
Field state with the self prefix only represents the current field; without the prefix it aggregates self + all descendants:
// selfErrors — only this field's own validation errors
console.log(field.selfErrors)
// errors — this field + all descendant fields' validation errors
console.log(field.errors)
// Similarly
console.log(field.selfValid) // Only this field's own validity
console.log(field.valid) // Self + all descendants' validityThis design allows parent fields (e.g., ObjectField) to aggregate the validation state of child fields.
Reactive State Reading
Reading state inside effects or autorun automatically subscribes to changes:
import { autorun } from '@silver-formily/reactive'
autorun(() => {
// Auto-subscribes to changes in field.value and form.values
if (field.value !== form.values.username) {
console.log('Values out of sync')
}
})State Snapshots
// Get a field graph snapshot containing Form and all Field states
const graph = form.getFormGraph()
// Get only the Form state snapshot
const formState = form.getState()
// Get a field state snapshot
const fieldState = field.getState()Batch Updates
Multiple state updates should use batch to avoid unnecessary intermediate notifications:
import { batch } from '@silver-formily/reactive'
batch(() => {
field.setState({ value: 'a' })
field.setState({ visible: false })
field.setState({ loading: true })
// Only one update notification
})Direct Assignment vs Setters
Most field states can be updated by direct assignment or via the corresponding setXxx method, but they are not always fully equivalent.
Generally Equivalent Cases
The following direct assignments usually forward to the corresponding setter and can be treated as regular properties:
field.value = xis equivalent tofield.setValue(x)field.initialValue = xis equivalent tofield.setInitialValue(x)field.required = xis equivalent tofield.setRequired(x)field.selfErrors = []is equivalent tofield.setSelfErrors([])field.selfWarnings = []is equivalent tofield.setSelfWarnings([])field.selfSuccesses = []is equivalent tofield.setSelfSuccesses([])
Not Fully Equivalent Process States
For these states, prefer setters over direct assignment:
loadingvalidatingsubmitting
The reason is that these setters carry additional runtime semantics beyond just changing the state value, for example:
- Cleaning up the previous async timer
- Delaying certain state transitions to avoid flickering
- Triggering the corresponding lifecycle events
For example:
field.loading = true
field.setLoading(true)Both may result in field.loading being true, but only setLoading(true) goes through the full runtime flow.
Similarly:
form.validating = true
form.setValidating(true)Should not be treated as fully equivalent.
Recommended Rules
- Plain values, default values, and feedback states can be assigned directly
- Process states like
loading,validating, andsubmittingshould usesetXxx - When updating multiple states at once, prefer
setState/setFieldState/setFormStatecombined withbatch
Related Mechanisms
Values and state are the foundation for other mechanisms, but their detailed rules are covered in standalone pages:
- Reading and writing nested data through field paths: Path System
- Updating other fields after state changes: Linkage System
- Reading and writing error, warning, and success feedback: Validation System