-
Notifications
You must be signed in to change notification settings - Fork 0
/
useValidation.ts
98 lines (81 loc) · 3.17 KB
/
useValidation.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { useEffect, useMemo, useRef } from 'react'
import { noop } from 'lodash'
import {
Validator,
getCombinedValidatorOutput,
ValidatorOutput,
combineValidatorOutput,
AsyncValidator,
} from '../Validator'
import { useAsyncValidator } from './useAsyncValidator'
interface UseValidationCommonProps<TValue> {
name: string
onValidChange?(name: string, valid: boolean): void
validators: Validator<TValue>[]
// TODO support dependency array style
validationKey?: string | number | boolean
// TODO support multiple async validators?
asyncValidator?: AsyncValidator<TValue>
/** defaults to onError from ItiReactCoreContext */
onAsyncError?(e: unknown): void
formLevelValidatorOutput?: ValidatorOutput
}
/**
* Input components that call [[`useValidation`]] should generally have their
* Props interface extend this.
*/
export interface UseValidationProps<TValue> extends UseValidationCommonProps<TValue> {
value?: TValue
defaultValue?: TValue
onChange?(value: TValue): void
showValidation: boolean
}
export interface UseValidationOptions<TValue> extends UseValidationCommonProps<TValue> {
value: TValue
}
/**
* A hook that allows implementing validation in any input component.
*
* Internally, `useValidation` calls `useMemo` on `validators` and `asyncValidator`
* since those props won't have stable identities if defined during the render
* as is typical. If `useValidation` did not do this, every component that rendered
* a validated component would have to call `useMemo` on `validators` and
* `asyncValidator` (or move the definitions outside of the component) to prevent
* infinite `useEffect` loops.
*
* If and when your `validators` or `asyncValidator` do change, you **must** pass a
* different `validationKey` for `useValidation` to pick up the changes.
*
* @typeParam TValue the type of the input's value
*/
export function useValidation<TValue>({
value,
name,
validationKey,
onAsyncError,
formLevelValidatorOutput,
...otherProps
}: UseValidationOptions<TValue>): ValidatorOutput {
/* eslint-disable react-hooks/exhaustive-deps */
const validators = useMemo(() => otherProps.validators, [validationKey])
const asyncValidator = useMemo(() => otherProps.asyncValidator, [validationKey])
/* eslint-enable react-hooks/exhaustive-deps */
const synchronousValidatorOutput = getCombinedValidatorOutput(value, validators)
const asyncValidatorOutput = useAsyncValidator({
value,
synchronousValidatorsValid: !synchronousValidatorOutput,
asyncValidator,
onError: onAsyncError,
})
const onValidChangeRef = useRef(otherProps.onValidChange ?? noop)
useEffect(() => {
onValidChangeRef.current = otherProps.onValidChange ?? noop
})
const overallValid = !synchronousValidatorOutput && !asyncValidatorOutput
useEffect(() => {
onValidChangeRef.current(name, overallValid)
}, [name, overallValid])
const validatorOutputs = [synchronousValidatorOutput, asyncValidatorOutput]
if (formLevelValidatorOutput) validatorOutputs.push(formLevelValidatorOutput)
return combineValidatorOutput(validatorOutputs)
}