Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
implement struct validation result
  • Loading branch information
Luncher committed May 10, 2022
commit 83cd99ac4579159c8d77fed7d4f840c73a64aad4
14 changes: 10 additions & 4 deletions dumi/docs/concepts/state/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ export interface IState<V> {
activated: boolean
/** Current validate status. */
validateStatus: ValidateStatus
/** The error info of validation. */
/** The error info of validation, ErrorObject type will be filled with ErrorObject.message. */
Copy link
Collaborator

@nighca nighca May 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议不要放在 error & ownError 上说明;不需要用 rawError 的人就不需要接触 ErrorObject 之类的概念是最好

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是从这边 state 同步过来的

不需要用 rawError 的人就不需要接触 ErrorObject 之类的概念是最好

意思是, error & ownError 实现上包含了 ErrorObejct, 但是这边不说吗?..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

嗯,我感觉这里不说应该问题不大,毕竟类型上也会反映它只会是 string | undefined;当他真的想要通过 validator 返回 ErrorObejct 的时候,那时他能找到对应的文档就好

error: ValidationError
/** The state's own error info, regardless of child states. */
/** The state's own error info, regardless of child states, ErrorObject type will be filled with ErrorObject.message. */
ownError: ValidationError
/** Ihe state's own error info, regardless of child states. */
rawError: ValidationRawError
/** Append validator(s). */
withValidator(...validators: Array<Validator<V>>): this
/** Fire a validation behavior imperatively. */
Expand Down Expand Up @@ -67,11 +69,11 @@ Validation is the process of validating user input values.
Validation is important for cases like:

* When user inputs, we display error tips if validation not passed, so users see that and correct the input
* Before form submiiting, we check if all value is valid, so invalid requests to the server can be avoided
* Before form submitting, we check if all value is valid, so invalid requests to the server can be avoided

That's why validation should provide such features:

* It should run automatically, when users changed the value, or when some other data change influcend the value validity
* It should run automatically, when users changed the value, or when some other data change influenced the value validity
* It should produce details such as a meaningful message, so users can get friendly hint

With formstate-x, we define validators and append them to states with `withValidator`. formstate-x will do validation for us. Through `validateStatus` & `error`, we can access the validate status and result.
Expand All @@ -97,6 +99,10 @@ States will not be auto-validated until it becomes **activated**. And they will

`ownError` & `hasOwnError` are special fields especially for composed states. You can check details about them in issue [#71](https://github.com/qiniu/formstate-x/issues/71).

### Raw Error

Ihe state's own raw error info, regardless of child states. The difference compared to `ownError` is that it contains the type of `ErrorObject`. You can check details about them in issue [#82](https://github.com/qiniu/formstate-x/issues/82).

### Disable State

You may find that we defined method `disableWhen` to configure when a state should be disabled. It is useful in some specific cases. You can check details in section [Disable State](/guide/advanced#disable-state).
4 changes: 4 additions & 0 deletions dumi/docs/concepts/validator/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export type ValidationResult =
| null
| undefined
| false
| ErrorObject

/** Object type validation result. */
export type ErrorObject = { message: string }

/** Return value of validator. */
export type ValidatorReturned =
Expand Down
3 changes: 2 additions & 1 deletion src/adapter/v2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ describe('toV2', () => {
const validateRet = state.validate()
expect(state._validateStatus).toBe(v3.ValidateStatus.Validating)
const validated = await validateRet
expect(validated).toEqual({ hasError: true, error: 'empty' })
expect(validated).toEqual({ hasError: true, error: 'empty', rawError: 'empty' })
expect(state._validateStatus).toBe(v3.ValidateStatus.Validated)
})

Expand Down Expand Up @@ -705,6 +705,7 @@ describe('toV2', () => {
touched = false
ownError = undefined
error = undefined
rawError = undefined
activated = false
validateStatus = v3.ValidateStatus.NotValidated
async validate() {
Expand Down
12 changes: 10 additions & 2 deletions src/adapter/v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ interface IV3StateFromV2<T extends v2.ComposibleValidatable<unknown, V>, V> exte
$: T
}

type ExcludeErrorObject<V> = V extends v3.ErrorObject ? never : V extends Promise<infer R> ? Promise<ExcludeErrorObject<R>> : V

type Conditional<K> = K extends any ? ExcludeErrorObject<K> : never

// Omit ErrorObject
type LegacyValidator<T> = (V: T) => Conditional<ReturnType<v3.Validator<T>>>

class Upgrader<T extends v2.ComposibleValidatable<unknown, V>, V> extends BaseState implements IV3StateFromV2<T, V> {
constructor(private stateV2: T) {
super()
Expand All @@ -27,6 +34,7 @@ class Upgrader<T extends v2.ComposibleValidatable<unknown, V>, V> extends BaseSt
@computed get ownError() {
return getV3OwnError(this.stateV2)
}
@computed get rawError() { return this.ownError }
@computed get error() { return this.stateV2.error }
@computed get activated() { return this.stateV2._activated }
@computed get validateStatus() {
Expand All @@ -42,7 +50,7 @@ class Upgrader<T extends v2.ComposibleValidatable<unknown, V>, V> extends BaseSt
setForV2(this.stateV2, value)
}
reset() { this.stateV2.reset() }
withValidator(...validators: Array<v3.Validator<V>>) {
withValidator(...validators: Array<LegacyValidator<V>>) {
if (
isV2FieldState(this.stateV2)
|| isV2FormState(this.stateV2)
Expand All @@ -64,7 +72,7 @@ class Upgrader<T extends v2.ComposibleValidatable<unknown, V>, V> extends BaseSt
}
}

/** Convets [email protected] state to [email protected] state */
/** Converts [email protected] state to [email protected] state */
export function fromV2<T extends v2.ComposibleValidatable<unknown, unknown>>(stateV2: T): IV3StateFromV2<T, T['value']> {
return new Upgrader(stateV2)
}
Expand Down
28 changes: 28 additions & 0 deletions src/debouncedState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,20 @@ describe('DebouncedState validation', () => {
expect(state.error).toBe('empty')
expect(state.ownError).toBe('empty')
})

it('should work well with resolved error object', async () => {
const fooState = new FieldState('')
const formState = new FormState({ foo: fooState })
const state = new DebouncedState(formState, defaultDelay).withValidator(
() => ({ message: 'mock msg' })
)

await state.validate()
expect(state.hasError).toBe(true)
expect(state.ownError).toBe('mock msg')
expect(state.error).toBe('mock msg')
expect(state.rawError).toEqual({ message: 'mock msg' })
})
})

function createFieldState<T>(initialValue: T, delay = defaultDelay) {
Expand Down Expand Up @@ -607,4 +621,18 @@ describe('DebouncedFieldState validation', () => {
expect(validator).toBeCalled()
expect(state.validateStatus).toBe(ValidateStatus.Validated)
})

it('should work well with resolved error object', async () => {
const state = createFieldState(0).withValidator(
_ => ({ message: 'empty' })
)

state.validate()

await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('empty')
expect(state.ownError).toBe('empty')
expect(state.rawError).toEqual({ message: 'empty' })
})
})
4 changes: 2 additions & 2 deletions src/debouncedState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { action, computed, makeObservable, observable, override, reaction } from
import { FieldState } from './fieldState'
import { ValidatableState } from './state'
import { IState, ValidateStatus, ValueOf } from './types'
import { debounce } from './utils'
import { debounce, isErrorObject } from './utils'

const defaultDelay = 200 // ms

Expand Down Expand Up @@ -52,7 +52,7 @@ export class DebouncedState<S extends IState<V>, V = ValueOf<S>> extends Validat

@override override get ownError() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在实现 DebouncedState 的时候,也应该去通过 override rawError 来实现,而不是 override ownError

#87 (comment) 这里所述,ownError = normalizeError(rawError) 这个逻辑对于 DebouncedState 也应该是成立的?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

没注意这里有个 override,我看怎么改下。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

031f8a1
done

if (this.disabled) return undefined
if (this._error) return this._error
if (this._error) return isErrorObject(this._error) ? this._error.message : this._error
return this.original.ownError
}

Expand Down
27 changes: 27 additions & 0 deletions src/fieldState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,4 +373,31 @@ describe('FieldState validation', () => {
assertType<string>(res.value)
}
})

describe('should work well with resolved error object', () => {
it('should work well with sync resolved', async () => {
const state = new FieldState('').withValidator(
_ => ({ message: 'error-object-msg'})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好多这种少了右空格的 😂

)

const res = await state.validate()
expect(state.hasError).toBe(true)
expect(state.error).toBe('error-object-msg')
expect(state.rawError).toEqual({ message: 'error-object-msg'})
expect(res).toEqual({ hasError: true, error: 'error-object-msg', rawError: { message: 'error-object-msg' }})
})

it('should work well with async resolved', async () => {
const state = new FieldState('').withValidator(
_ => null,
_ => delayValue({ message: 'error-object-msg' }, 100)
)

const res = await state.validate()
expect(state.hasError).toBe(true)
expect(state.error).toBe('error-object-msg')
expect(state.rawError).toEqual({ message: 'error-object-msg'})
expect(res).toEqual({ hasError: true, error: 'error-object-msg', rawError: { message: 'error-object-msg' }})
})
})
})
53 changes: 53 additions & 0 deletions src/formState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,59 @@ describe('FormState (mode: object) validation', () => {
assertType<{ foo: string }>(res.value)
}
})

describe('should work well with resolved error object', () => {
it('should work well with mixed sync and async resolved error object', async () => {
const initialValue = { foo: '123', bar: '123' }
const state = new FormState({
foo: new FieldState(initialValue.foo),
bar: new FieldState(initialValue.bar)
}).withValidator(
({ foo, bar }) => delayValue(foo === bar && { message: 'same' }),
({ foo }) => foo === '' && { message: 'empty' }
)
state.validate()

await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('same')
expect(state.ownError).toBe('same')
expect(state.rawError).toEqual({ message: 'same' })

state.$.foo.onChange('')

await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('empty')
expect(state.ownError).toBe('empty')
expect(state.rawError).toEqual({ message: 'empty' })

state.$.foo.onChange('456')

await delay()
expect(state.hasError).toBe(false)
expect(state.error).toBeUndefined()
expect(state.rawError).toBeUndefined()
})

it('should work well with child states resolved error object', async () => {
const state = new FormState({
foo: new FieldState(''),
bar: new FieldState('').withValidator(
v => v === '' && { message: 'empty' }
)
})
state.validate()
await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('empty')
expect(state.rawError).toBeUndefined()
expect(state.ownError).toBeUndefined()
expect(state.$.bar.hasError).toBe(true)
expect(state.$.bar.error).toBe('empty')
expect(state.$.bar.rawError).toEqual({ message: 'empty' })
})
})
})

describe('FormState (mode: array)', () => {
Expand Down
16 changes: 10 additions & 6 deletions src/state.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { action, autorun, computed, makeObservable, observable, when } from 'mobx'
import { ValidationError, IState, Validation, ValidateResult, ValidateStatus, Validator } from './types'
import { ValidationRawError, ValidationError, IState, Validation, ValidateResult, ValidateStatus, Validator } from './types'
import Disposable from './disposable'
import { applyValidators, isValid, isPromiseLike } from './utils'
import { applyValidators, isValid, isPromiseLike, isErrorObject } from './utils'

/** Extraction for some basic features of State */
export abstract class BaseState extends Disposable implements Pick<
Expand Down Expand Up @@ -56,10 +56,14 @@ export abstract class ValidatableState<V> extends BaseState implements IState<V>
/**
* The original error info of validation.
*/
@observable protected _error: ValidationError
@observable protected _error: ValidationRawError

@computed get rawError() {
return this.disabled ? undefined : this._error
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return this.disabled ? undefined : this._error
return this.disabled ? undefined : this._error

}

@computed get ownError() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是不是能挪到 BaseState 上了啊?因为看起来不管是什么样的 state,ownError = normalizeError(rawError) 这个逻辑都是成立的

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不太明确 BaseState 的定位,我看 BaseState 上一些属性,跟 ValidatableState 关系也挺大的

Copy link
Collaborator

@nighca nighca May 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BaseState 抽的是对所有 state 都成立的逻辑,现在 formstate-x 代码中所有对 interface IState 的实现都直接或间接地继承了它

ValidatableState 是校验逻辑的抽象,适用于那些有自己的校验逻辑/过程的 state

举个例子,TransformedState 继承了 BaseState 而没有继承 ValidatableState,是因为它没有“自己的校验逻辑/过程”,而是依赖的其包裹的 state($)的校验;TransformedState 不需要维护自己的 validators,也没有自己的 validate status,也不需要自己去维护一份单独的 error 信息

Copy link
Collaborator

@nighca nighca May 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BaseState 抽的是对所有 state 都成立的逻辑

基于这个标准看的话,现在 hasError 放在这上边就不太合适了,因为现在 hasError 的实现逻辑(hasError = !isPassed(rawError))并不是对所有 state 都成立的

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

假如我们有 packaged raw error,那么我们其实是能实现出这样一份对所有 state 都适用的逻辑的:

ownError = normalizeError(rawError)
error = normalizeError(packagedRawError)
hasError = !isPassed(packagedRawError)

这样不同的 state,它们去实现各自的 rawError & packagedRawError 就好,逻辑会相对清晰一点

不过现在我们没有 packagedRawError,也没有一个可靠的从 rawError 推出 hasError 的逻辑,所以那些 override 了 error 的地方都需要再去 override 一遍 hasError;基于这个前提,我觉得 #87 (comment) 这里提到的

不过感觉,它(hasError)的值也从 rawError / validationResult 直接得到要比从 error 直接得到更好

就不太对了;既然我们已经通过 throw 确保了 error object 的 message 一定不是空字符串,那么 hasError = !!error 的逻辑就还成立(而且是对所有的 state 成立),那走这个逻辑会对实现更方便?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

既然我们已经通过 throw 确保了 error object 的 message 一定不是空字符串,那么 hasError = !!error 的逻辑就还成立(而且是对所有的 state 成立),那走这个逻辑会对实现更方便?

#87 (comment)

我之前在这里就说了啊..,现在要把实现改回去吗?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#87 (comment)

我之前在这里就说了啊..

那边我没看懂你的意思..

现在要把实现改回去吗?

我倾向改回去,如果确实 hasError = !!error 这个逻辑还成立,而且基于这个逻辑确实更方便的话

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ba4b45e
done

return this.disabled ? undefined : this._error
return this.rawError ? (isErrorObject(this.rawError) ? this.rawError.message : this.rawError) : undefined
}

@computed get error() {
Expand All @@ -69,7 +73,7 @@ export abstract class ValidatableState<V> extends BaseState implements IState<V>
/**
* Set error info.
*/
@action setError(error: ValidationError) {
@action setError(error: ValidationRawError) {
this._error = error
}

Expand Down Expand Up @@ -111,7 +115,7 @@ export abstract class ValidatableState<V> extends BaseState implements IState<V>
@computed protected get validateResult(): ValidateResult<V> {
return (
this.error
? { hasError: true, error: this.error } as const
? { hasError: true, error: this.error, rawError: this.rawError } as const
: { hasError: false, value: this.value } as const
)
}
Expand Down
27 changes: 27 additions & 0 deletions src/transformedState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,19 @@ describe('TransformedState (for FieldState) validation', () => {
expect(state.ownError).toBe('non positive')
})

it('should work well with resolved error object', async () => {
const state = createNumState(0).withValidator(
_ => ({ message: 'empty' })
)

state.validate()

await delay()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIP: 类似这种为什么不直接 await state.validate()

expect(state.hasError).toBe(true)
expect(state.error).toBe('empty')
expect(state.ownError).toBe('empty')
expect(state.rawError).toEqual({ message: 'empty' })
})
})

interface Host {
Expand Down Expand Up @@ -738,4 +751,18 @@ describe('TransformedState (for FormState) validation', () => {
expect(state.hasOwnError).toBe(false)
expect(state.ownError).toBeUndefined()
})

it('should work well with resolved error object', async () => {
const state = createHostState('127.0.0.1').withValidator(
_ => ({ message: 'mock msg'})
)

state.validate()

await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('mock msg')
expect(state.ownError).toBe('mock msg')
expect(state.rawError).toEqual({ message: 'mock msg' })
})
})
4 changes: 4 additions & 0 deletions src/transformedState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export class TransformedState<S extends IState<$V>, V, $V = ValueOf<S>> extends
return this.$.ownError
}

@computed get rawError() {
return this.$.rawError
}

@computed get error() {
return this.$.error
}
Expand Down
11 changes: 8 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type ValidationResult =
| null
| undefined
| false
| ErrorObject

/** Return value of validator. */
export type ValidatorReturned =
Expand All @@ -18,9 +19,11 @@ export type Validation<TValue> = {
returned: ValidatorReturned // result of applying validators
}

export type ErrorObject = { message: string }
export type ValidationError = string | undefined
export type ValidationRawError = ErrorObject | ValidationError
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValidationRawErrorValidationResult 统一成一个东西会不会好点?这俩其实很接近,多一个概念不如少一个?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

从概念上讲,ValidationResultValidationResp 这种比较接近,而不是 ValidationRawError, 后者只是前者的子集。和在一起的话,会不会有理解上的困难,之前的实现好像有意的区分,ResultError 的区别:

this.setError(isValid(result) ? undefined : result)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

从概念上讲,ValidationResultValidationResp 这种比较接近

ValidationResp 是啥?

之前的实现好像有意的区分,ResultError 的区别

是的,ResultError 是两个概念;不过 RawErrorResult 可以做成一个概念?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResultError 是两个概念

当然可以不区分这两个概念我觉得会更好,只是因为从方便 validator 实现的角度来说,需要允许它 return boolean | string | undefined | null,而从方便 error 消费的角度来说会希望它的取值是 string | undefined;所以这边拆了两个概念来达到这个目的

如果我们评估觉得,对 validator 来说只允许它 return string | undefined 也还算方便,且有比较好的兼容历史(validator)代码的方法,那么我会倾向于把 ResultError 也合成一个概念的

Copy link
Contributor Author

@Luncher Luncher May 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果我们评估觉得,对 validator 来说只允许它 return string | undefined 也还算方便,且有比较好的兼容历史(validator)代码的方法

我估计这样对用户负担比较大。譬如:

  return new FormState({
    name: new FieldState('').validators(v => !v && '用户名不能为空'),
    email: new FieldState('').validators(
      val => !val && '邮箱不能为空',
      val => !mailReg.test(val) && '请输入有效的邮箱地址'
    )
  })

类似这种写法还是蛮多的。如果换成:

  return new FormState({
    name: new FieldState('').validators(v => !v  ?  '用户名不能为空' : undefined),
    email: new FieldState('').validators(
      val => !val ? '邮箱不能为空' : undefined,
      val => !mailReg.test(val) ? '请输入有效的邮箱地址' : undefined
    )
  })

相对使用体验没有那么好。

按照目前的使用习惯,用户其实已经养成了 “零” 值表示验证通过了:

export function isValid(result: ValidationResult): result is '' | null | undefined | false {
  return !result
}

从方便 validator 实现的角度来说我觉得这个还是留着比较好。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果我们评估觉得,对 validator 来说只允许它 return string | undefined 也还算方便

哦我不是要现在评估,我们也不可能现在把对于 null | false 的支持去掉(这是 breaking);我是指,如果我们将来评估得出“支持 null | false 不重要”这一结论,那么我也会倾向把 ResultError 合成一个概念

这边是两个事情:

  1. Result & Error 概念合并
  2. Result & RawError 概念合并

1 我们肯定不会现在去做的,也不用现在去评估;之所以上边提到 1 是因为 1 跟 2 的动力是一样的:概念越少理解起来越简单


export type ValidateResultWithError = { hasError: true, error: NonNullable<ValidationError> }
export type ValidateResultWithError = { hasError: true, rawError: ValidationRawError, error: NonNullable<ValidationError> }
export type ValidateResultWithValue<T> = { hasError: false, value: T }
export type ValidateResult<T> = ValidateResultWithError | ValidateResultWithValue<T>

Expand All @@ -30,12 +33,14 @@ export interface IState<V = unknown> {
value: V
/** If value has been touched. */
touched: boolean
/** The error info of validation. */
/** The error info of validation, ErrorObject type will be filled with ErrorObject.message. */
error: ValidationError
/** If the state contains error. */
hasError: boolean
/** The state's own error info, regardless of child states. */
/** The state's own error info, regardless of child states, ErrorObject type will be filled with ErrorObject.message. */
ownError: ValidationError
/** Ihe state's own error info, regardless of child states. */
rawError: ValidationRawError
/** If the state contains its own error info. */
hasOwnError: boolean
/** If activated (with auto-validation). */
Expand Down
Loading