UNPKG

react-hook-form

Version:

Performant, flexible and extensible forms library for React Hooks

1,320 lines (1,262 loc) • 67.1 kB
var appendErrors = (name, validateAllFieldCriteria, errors, type, message) => validateAllFieldCriteria ? { ...errors[name], types: { ...(errors[name] && errors[name].types ? errors[name].types : {}), [type]: message || true, }, } : {}; const EVENTS = { BLUR: 'blur', FOCUS_OUT: 'focusout'}; const VALIDATION_MODE = { onBlur: 'onBlur', onChange: 'onChange', onSubmit: 'onSubmit', onTouched: 'onTouched', all: 'all', }; const INPUT_VALIDATION_RULES = { max: 'max', min: 'min', maxLength: 'maxLength', minLength: 'minLength', pattern: 'pattern', required: 'required', validate: 'validate', }; var isDateObject = (value) => value instanceof Date; var isNullOrUndefined = (value) => value == null; const isObjectType = (value) => typeof value === 'object'; var isObject = (value) => !isNullOrUndefined(value) && !Array.isArray(value) && isObjectType(value) && !isDateObject(value); var isPlainObject = (tempObject) => { const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype; return (isObject(prototypeCopy) && prototypeCopy.hasOwnProperty('isPrototypeOf')); }; var isWeb = typeof window !== 'undefined' && typeof window.HTMLElement !== 'undefined' && typeof document !== 'undefined'; function cloneObject(data) { let copy; const isArray = Array.isArray(data); const isFileListInstance = typeof FileList !== 'undefined' ? data instanceof FileList : false; if (data instanceof Date) { copy = new Date(data); } else if (data instanceof Set) { copy = new Set(data); } else if (!(isWeb && (data instanceof Blob || isFileListInstance)) && (isArray || isObject(data))) { copy = isArray ? [] : {}; if (!isArray && !isPlainObject(data)) { copy = data; } else { for (const key in data) { if (data.hasOwnProperty(key)) { copy[key] = cloneObject(data[key]); } } } } else { return data; } return copy; } var compact = (value) => Array.isArray(value) ? value.filter(Boolean) : []; var convertToArrayPayload = (value) => (Array.isArray(value) ? value : [value]); var createSubject = () => { let _observers = []; const next = (value) => { for (const observer of _observers) { observer.next && observer.next(value); } }; const subscribe = (observer) => { _observers.push(observer); return { unsubscribe: () => { _observers = _observers.filter((o) => o !== observer); }, }; }; const unsubscribe = () => { _observers = []; }; return { get observers() { return _observers; }, next, subscribe, unsubscribe, }; }; var isPrimitive = (value) => isNullOrUndefined(value) || !isObjectType(value); function deepEqual(object1, object2) { if (isPrimitive(object1) || isPrimitive(object2)) { return object1 === object2; } if (isDateObject(object1) && isDateObject(object2)) { return object1.getTime() === object2.getTime(); } const keys1 = Object.keys(object1); const keys2 = Object.keys(object2); if (keys1.length !== keys2.length) { return false; } for (const key of keys1) { const val1 = object1[key]; if (!keys2.includes(key)) { return false; } if (key !== 'ref') { const val2 = object2[key]; if ((isDateObject(val1) && isDateObject(val2)) || (isObject(val1) && isObject(val2)) || (Array.isArray(val1) && Array.isArray(val2)) ? !deepEqual(val1, val2) : val1 !== val2) { return false; } } } return true; } var isUndefined = (val) => val === undefined; var get = (object, path, defaultValue) => { if (!path || !isObject(object)) { return defaultValue; } const result = compact(path.split(/[,[\].]+?/)).reduce((result, key) => isNullOrUndefined(result) ? result : result[key], object); return isUndefined(result) || result === object ? isUndefined(object[path]) ? defaultValue : object[path] : result; }; var isBoolean = (value) => typeof value === 'boolean'; var isCheckBoxInput = (element) => element.type === 'checkbox'; var isEmptyObject = (value) => isObject(value) && !Object.keys(value).length; var isFileInput = (element) => element.type === 'file'; var isFunction = (value) => typeof value === 'function'; var isHTMLElement = (value) => { if (!isWeb) { return false; } const owner = value ? value.ownerDocument : 0; return (value instanceof (owner && owner.defaultView ? owner.defaultView.HTMLElement : HTMLElement)); }; var isMultipleSelect = (element) => element.type === `select-multiple`; var isRadioInput = (element) => element.type === 'radio'; var isRadioOrCheckbox = (ref) => isRadioInput(ref) || isCheckBoxInput(ref); var isString = (value) => typeof value === 'string'; var live = (ref) => isHTMLElement(ref) && ref.isConnected; var isKey = (value) => /^\w*$/.test(value); var stringToPath = (input) => compact(input.replace(/["|']|\]/g, '').split(/\.|\[/)); var set = (object, path, value) => { let index = -1; const tempPath = isKey(path) ? [path] : stringToPath(path); const length = tempPath.length; const lastIndex = length - 1; while (++index < length) { const key = tempPath[index]; let newValue = value; if (index !== lastIndex) { const objValue = object[key]; newValue = isObject(objValue) || Array.isArray(objValue) ? objValue : !isNaN(+tempPath[index + 1]) ? [] : {}; } if (key === '__proto__' || key === 'constructor' || key === 'prototype') { return; } object[key] = newValue; object = object[key]; } }; function baseGet(object, updatePath) { const length = updatePath.slice(0, -1).length; let index = 0; while (index < length) { object = isUndefined(object) ? index++ : object[updatePath[index++]]; } return object; } function isEmptyArray(obj) { for (const key in obj) { if (obj.hasOwnProperty(key) && !isUndefined(obj[key])) { return false; } } return true; } function unset(object, path) { const paths = Array.isArray(path) ? path : isKey(path) ? [path] : stringToPath(path); const childObject = paths.length === 1 ? object : baseGet(object, paths); const index = paths.length - 1; const key = paths[index]; if (childObject) { delete childObject[key]; } if (index !== 0 && ((isObject(childObject) && isEmptyObject(childObject)) || (Array.isArray(childObject) && isEmptyArray(childObject)))) { unset(object, paths.slice(0, -1)); } return object; } var generateWatchOutput = (names, _names, formValues, isGlobal, defaultValue) => { if (isString(names)) { isGlobal && _names.watch.add(names); return get(formValues, names, defaultValue); } if (Array.isArray(names)) { return names.map((fieldName) => (isGlobal && _names.watch.add(fieldName), get(formValues, fieldName))); } isGlobal && (_names.watchAll = true); return formValues; }; var objectHasFunction = (data) => { for (const key in data) { if (isFunction(data[key])) { return true; } } return false; }; function markFieldsDirty(data, fields = {}) { const isParentNodeArray = Array.isArray(data); if (isObject(data) || isParentNodeArray) { for (const key in data) { if (Array.isArray(data[key]) || (isObject(data[key]) && !objectHasFunction(data[key]))) { fields[key] = Array.isArray(data[key]) ? [] : {}; markFieldsDirty(data[key], fields[key]); } else if (!isNullOrUndefined(data[key])) { fields[key] = true; } } } return fields; } function getDirtyFieldsFromDefaultValues(data, formValues, dirtyFieldsFromValues) { const isParentNodeArray = Array.isArray(data); if (isObject(data) || isParentNodeArray) { for (const key in data) { if (Array.isArray(data[key]) || (isObject(data[key]) && !objectHasFunction(data[key]))) { if (isUndefined(formValues) || isPrimitive(dirtyFieldsFromValues[key])) { dirtyFieldsFromValues[key] = Array.isArray(data[key]) ? markFieldsDirty(data[key], []) : { ...markFieldsDirty(data[key]) }; } else { getDirtyFieldsFromDefaultValues(data[key], isNullOrUndefined(formValues) ? {} : formValues[key], dirtyFieldsFromValues[key]); } } else { dirtyFieldsFromValues[key] = !deepEqual(data[key], formValues[key]); } } } return dirtyFieldsFromValues; } var getDirtyFields = (defaultValues, formValues) => getDirtyFieldsFromDefaultValues(defaultValues, formValues, markFieldsDirty(formValues)); var getEventValue = (event) => isObject(event) && event.target ? isCheckBoxInput(event.target) ? event.target.checked : event.target.value : event; const defaultResult = { value: false, isValid: false, }; const validResult = { value: true, isValid: true }; var getCheckboxValue = (options) => { if (Array.isArray(options)) { if (options.length > 1) { const values = options .filter((option) => option && option.checked && !option.disabled) .map((option) => option.value); return { value: values, isValid: !!values.length }; } return options[0].checked && !options[0].disabled ? // @ts-expect-error expected to work in the browser options[0].attributes && !isUndefined(options[0].attributes.value) ? isUndefined(options[0].value) || options[0].value === '' ? validResult : { value: options[0].value, isValid: true } : validResult : defaultResult; } return defaultResult; }; var getFieldValueAs = (value, { valueAsNumber, valueAsDate, setValueAs }) => isUndefined(value) ? value : valueAsNumber ? value === '' ? NaN : value ? +value : value : valueAsDate && isString(value) ? new Date(value) : setValueAs ? setValueAs(value) : value; const defaultReturn = { isValid: false, value: null, }; var getRadioValue = (options) => Array.isArray(options) ? options.reduce((previous, option) => option && option.checked && !option.disabled ? { isValid: true, value: option.value, } : previous, defaultReturn) : defaultReturn; function getFieldValue(_f) { const ref = _f.ref; if (isFileInput(ref)) { return ref.files; } if (isRadioInput(ref)) { return getRadioValue(_f.refs).value; } if (isMultipleSelect(ref)) { return [...ref.selectedOptions].map(({ value }) => value); } if (isCheckBoxInput(ref)) { return getCheckboxValue(_f.refs).value; } return getFieldValueAs(isUndefined(ref.value) ? _f.ref.value : ref.value, _f); } var getResolverOptions = (fieldsNames, _fields, criteriaMode, shouldUseNativeValidation) => { const fields = {}; for (const name of fieldsNames) { const field = get(_fields, name); field && set(fields, name, field._f); } return { criteriaMode, names: [...fieldsNames], fields, shouldUseNativeValidation, }; }; var isRegex = (value) => value instanceof RegExp; var getRuleValue = (rule) => isUndefined(rule) ? rule : isRegex(rule) ? rule.source : isObject(rule) ? isRegex(rule.value) ? rule.value.source : rule.value : rule; var getValidationModes = (mode) => ({ isOnSubmit: !mode || mode === VALIDATION_MODE.onSubmit, isOnBlur: mode === VALIDATION_MODE.onBlur, isOnChange: mode === VALIDATION_MODE.onChange, isOnAll: mode === VALIDATION_MODE.all, isOnTouch: mode === VALIDATION_MODE.onTouched, }); const ASYNC_FUNCTION = 'AsyncFunction'; var hasPromiseValidation = (fieldReference) => !!fieldReference && !!fieldReference.validate && !!((isFunction(fieldReference.validate) && fieldReference.validate.constructor.name === ASYNC_FUNCTION) || (isObject(fieldReference.validate) && Object.values(fieldReference.validate).find((validateFunction) => validateFunction.constructor.name === ASYNC_FUNCTION))); var hasValidation = (options) => options.mount && (options.required || options.min || options.max || options.maxLength || options.minLength || options.pattern || options.validate); var getNodeParentName = (name) => name.substring(0, name.search(/\.\d+(\.|$)/)) || name; var isNameInFieldArray = (names, name) => names.has(getNodeParentName(name)); var isWatched = (name, _names, isBlurEvent) => !isBlurEvent && (_names.watchAll || _names.watch.has(name) || [..._names.watch].some((watchName) => name.startsWith(watchName) && /^\.\w+/.test(name.slice(watchName.length)))); const iterateFieldsByAction = (fields, action, fieldsNames, abortEarly) => { for (const key of fieldsNames || Object.keys(fields)) { const field = get(fields, key); if (field) { const { _f, ...currentField } = field; if (_f) { if (_f.refs && _f.refs[0] && action(_f.refs[0], key) && !abortEarly) { return true; } else if (_f.ref && action(_f.ref, _f.name) && !abortEarly) { return true; } else { if (iterateFieldsByAction(currentField, action)) { break; } } } else if (isObject(currentField)) { if (iterateFieldsByAction(currentField, action)) { break; } } } } return; }; function schemaErrorLookup(errors, _fields, name) { const error = get(errors, name); if (error || isKey(name)) { return { error, name, }; } const names = name.split('.'); while (names.length) { const fieldName = names.join('.'); const field = get(_fields, fieldName); const foundError = get(errors, fieldName); if (field && !Array.isArray(field) && name !== fieldName) { return { name }; } if (foundError && foundError.type) { return { name: fieldName, error: foundError, }; } names.pop(); } return { name, }; } var shouldRenderFormState = (formStateData, _proxyFormState, updateFormState, isRoot) => { updateFormState(formStateData); const { name, ...formState } = formStateData; return (isEmptyObject(formState) || Object.keys(formState).length >= Object.keys(_proxyFormState).length || Object.keys(formState).find((key) => _proxyFormState[key] === (!isRoot || VALIDATION_MODE.all))); }; var shouldSubscribeByName = (name, signalName, exact) => !name || !signalName || name === signalName || convertToArrayPayload(name).some((currentName) => currentName && (exact ? currentName === signalName : currentName.startsWith(signalName) || signalName.startsWith(currentName))); var skipValidation = (isBlurEvent, isTouched, isSubmitted, reValidateMode, mode) => { if (mode.isOnAll) { return false; } else if (!isSubmitted && mode.isOnTouch) { return !(isTouched || isBlurEvent); } else if (isSubmitted ? reValidateMode.isOnBlur : mode.isOnBlur) { return !isBlurEvent; } else if (isSubmitted ? reValidateMode.isOnChange : mode.isOnChange) { return isBlurEvent; } return true; }; var unsetEmptyArray = (ref, name) => !compact(get(ref, name)).length && unset(ref, name); var updateFieldArrayRootError = (errors, error, name) => { const fieldArrayErrors = convertToArrayPayload(get(errors, name)); set(fieldArrayErrors, 'root', error[name]); set(errors, name, fieldArrayErrors); return errors; }; var isMessage = (value) => isString(value); function getValidateError(result, ref, type = 'validate') { if (isMessage(result) || (Array.isArray(result) && result.every(isMessage)) || (isBoolean(result) && !result)) { return { type, message: isMessage(result) ? result : '', ref, }; } } var getValueAndMessage = (validationData) => isObject(validationData) && !isRegex(validationData) ? validationData : { value: validationData, message: '', }; var validateField = async (field, disabledFieldNames, formValues, validateAllFieldCriteria, shouldUseNativeValidation, isFieldArray) => { const { ref, refs, required, maxLength, minLength, min, max, pattern, validate, name, valueAsNumber, mount, } = field._f; const inputValue = get(formValues, name); if (!mount || disabledFieldNames.has(name)) { return {}; } const inputRef = refs ? refs[0] : ref; const setCustomValidity = (message) => { if (shouldUseNativeValidation && inputRef.reportValidity) { inputRef.setCustomValidity(isBoolean(message) ? '' : message || ''); inputRef.reportValidity(); } }; const error = {}; const isRadio = isRadioInput(ref); const isCheckBox = isCheckBoxInput(ref); const isRadioOrCheckbox = isRadio || isCheckBox; const isEmpty = ((valueAsNumber || isFileInput(ref)) && isUndefined(ref.value) && isUndefined(inputValue)) || (isHTMLElement(ref) && ref.value === '') || inputValue === '' || (Array.isArray(inputValue) && !inputValue.length); const appendErrorsCurry = appendErrors.bind(null, name, validateAllFieldCriteria, error); const getMinMaxMessage = (exceedMax, maxLengthMessage, minLengthMessage, maxType = INPUT_VALIDATION_RULES.maxLength, minType = INPUT_VALIDATION_RULES.minLength) => { const message = exceedMax ? maxLengthMessage : minLengthMessage; error[name] = { type: exceedMax ? maxType : minType, message, ref, ...appendErrorsCurry(exceedMax ? maxType : minType, message), }; }; if (isFieldArray ? !Array.isArray(inputValue) || !inputValue.length : required && ((!isRadioOrCheckbox && (isEmpty || isNullOrUndefined(inputValue))) || (isBoolean(inputValue) && !inputValue) || (isCheckBox && !getCheckboxValue(refs).isValid) || (isRadio && !getRadioValue(refs).isValid))) { const { value, message } = isMessage(required) ? { value: !!required, message: required } : getValueAndMessage(required); if (value) { error[name] = { type: INPUT_VALIDATION_RULES.required, message, ref: inputRef, ...appendErrorsCurry(INPUT_VALIDATION_RULES.required, message), }; if (!validateAllFieldCriteria) { setCustomValidity(message); return error; } } } if (!isEmpty && (!isNullOrUndefined(min) || !isNullOrUndefined(max))) { let exceedMax; let exceedMin; const maxOutput = getValueAndMessage(max); const minOutput = getValueAndMessage(min); if (!isNullOrUndefined(inputValue) && !isNaN(inputValue)) { const valueNumber = ref.valueAsNumber || (inputValue ? +inputValue : inputValue); if (!isNullOrUndefined(maxOutput.value)) { exceedMax = valueNumber > maxOutput.value; } if (!isNullOrUndefined(minOutput.value)) { exceedMin = valueNumber < minOutput.value; } } else { const valueDate = ref.valueAsDate || new Date(inputValue); const convertTimeToDate = (time) => new Date(new Date().toDateString() + ' ' + time); const isTime = ref.type == 'time'; const isWeek = ref.type == 'week'; if (isString(maxOutput.value) && inputValue) { exceedMax = isTime ? convertTimeToDate(inputValue) > convertTimeToDate(maxOutput.value) : isWeek ? inputValue > maxOutput.value : valueDate > new Date(maxOutput.value); } if (isString(minOutput.value) && inputValue) { exceedMin = isTime ? convertTimeToDate(inputValue) < convertTimeToDate(minOutput.value) : isWeek ? inputValue < minOutput.value : valueDate < new Date(minOutput.value); } } if (exceedMax || exceedMin) { getMinMaxMessage(!!exceedMax, maxOutput.message, minOutput.message, INPUT_VALIDATION_RULES.max, INPUT_VALIDATION_RULES.min); if (!validateAllFieldCriteria) { setCustomValidity(error[name].message); return error; } } } if ((maxLength || minLength) && !isEmpty && (isString(inputValue) || (isFieldArray && Array.isArray(inputValue)))) { const maxLengthOutput = getValueAndMessage(maxLength); const minLengthOutput = getValueAndMessage(minLength); const exceedMax = !isNullOrUndefined(maxLengthOutput.value) && inputValue.length > +maxLengthOutput.value; const exceedMin = !isNullOrUndefined(minLengthOutput.value) && inputValue.length < +minLengthOutput.value; if (exceedMax || exceedMin) { getMinMaxMessage(exceedMax, maxLengthOutput.message, minLengthOutput.message); if (!validateAllFieldCriteria) { setCustomValidity(error[name].message); return error; } } } if (pattern && !isEmpty && isString(inputValue)) { const { value: patternValue, message } = getValueAndMessage(pattern); if (isRegex(patternValue) && !inputValue.match(patternValue)) { error[name] = { type: INPUT_VALIDATION_RULES.pattern, message, ref, ...appendErrorsCurry(INPUT_VALIDATION_RULES.pattern, message), }; if (!validateAllFieldCriteria) { setCustomValidity(message); return error; } } } if (validate) { if (isFunction(validate)) { const result = await validate(inputValue, formValues); const validateError = getValidateError(result, inputRef); if (validateError) { error[name] = { ...validateError, ...appendErrorsCurry(INPUT_VALIDATION_RULES.validate, validateError.message), }; if (!validateAllFieldCriteria) { setCustomValidity(validateError.message); return error; } } } else if (isObject(validate)) { let validationResult = {}; for (const key in validate) { if (!isEmptyObject(validationResult) && !validateAllFieldCriteria) { break; } const validateError = getValidateError(await validate[key](inputValue, formValues), inputRef, key); if (validateError) { validationResult = { ...validateError, ...appendErrorsCurry(key, validateError.message), }; setCustomValidity(validateError.message); if (validateAllFieldCriteria) { error[name] = validationResult; } } } if (!isEmptyObject(validationResult)) { error[name] = { ref: inputRef, ...validationResult, }; if (!validateAllFieldCriteria) { return error; } } } } setCustomValidity(true); return error; }; const defaultOptions = { mode: VALIDATION_MODE.onSubmit, reValidateMode: VALIDATION_MODE.onChange, shouldFocusError: true, }; function createFormControl(props = {}) { let _options = { ...defaultOptions, ...props, }; let _formState = { submitCount: 0, isDirty: false, isReady: false, isLoading: isFunction(_options.defaultValues), isValidating: false, isSubmitted: false, isSubmitting: false, isSubmitSuccessful: false, isValid: false, touchedFields: {}, dirtyFields: {}, validatingFields: {}, errors: _options.errors || {}, disabled: _options.disabled || false, }; const _fields = {}; let _defaultValues = isObject(_options.defaultValues) || isObject(_options.values) ? cloneObject(_options.values || _options.defaultValues) || {} : {}; let _formValues = _options.shouldUnregister ? {} : cloneObject(_defaultValues); let _state = { action: false, mount: false, watch: false, }; let _names = { mount: new Set(), disabled: new Set(), unMount: new Set(), array: new Set(), watch: new Set(), }; let delayErrorCallback; let timer = 0; const _proxyFormState = { isDirty: false, dirtyFields: false, validatingFields: false, touchedFields: false, isValidating: false, isValid: false, errors: false, }; let _proxySubscribeFormState = { ..._proxyFormState, }; const _subjects = { array: createSubject(), state: createSubject(), }; const validationModeBeforeSubmit = getValidationModes(_options.mode); const validationModeAfterSubmit = getValidationModes(_options.reValidateMode); const shouldDisplayAllAssociatedErrors = _options.criteriaMode === VALIDATION_MODE.all; const debounce = (callback) => (wait) => { clearTimeout(timer); timer = setTimeout(callback, wait); }; const _setValid = async (shouldUpdateValid) => { if (!_options.disabled && (_proxyFormState.isValid || _proxySubscribeFormState.isValid || shouldUpdateValid)) { const isValid = _options.resolver ? isEmptyObject((await _runSchema()).errors) : await executeBuiltInValidation(_fields, true); if (isValid !== _formState.isValid) { _subjects.state.next({ isValid, }); } } }; const _updateIsValidating = (names, isValidating) => { if (!_options.disabled && (_proxyFormState.isValidating || _proxyFormState.validatingFields || _proxySubscribeFormState.isValidating || _proxySubscribeFormState.validatingFields)) { (names || Array.from(_names.mount)).forEach((name) => { if (name) { isValidating ? set(_formState.validatingFields, name, isValidating) : unset(_formState.validatingFields, name); } }); _subjects.state.next({ validatingFields: _formState.validatingFields, isValidating: !isEmptyObject(_formState.validatingFields), }); } }; const _setFieldArray = (name, values = [], method, args, shouldSetValues = true, shouldUpdateFieldsAndState = true) => { if (args && method && !_options.disabled) { _state.action = true; if (shouldUpdateFieldsAndState && Array.isArray(get(_fields, name))) { const fieldValues = method(get(_fields, name), args.argA, args.argB); shouldSetValues && set(_fields, name, fieldValues); } if (shouldUpdateFieldsAndState && Array.isArray(get(_formState.errors, name))) { const errors = method(get(_formState.errors, name), args.argA, args.argB); shouldSetValues && set(_formState.errors, name, errors); unsetEmptyArray(_formState.errors, name); } if ((_proxyFormState.touchedFields || _proxySubscribeFormState.touchedFields) && shouldUpdateFieldsAndState && Array.isArray(get(_formState.touchedFields, name))) { const touchedFields = method(get(_formState.touchedFields, name), args.argA, args.argB); shouldSetValues && set(_formState.touchedFields, name, touchedFields); } if (_proxyFormState.dirtyFields || _proxySubscribeFormState.dirtyFields) { _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues); } _subjects.state.next({ name, isDirty: _getDirty(name, values), dirtyFields: _formState.dirtyFields, errors: _formState.errors, isValid: _formState.isValid, }); } else { set(_formValues, name, values); } }; const updateErrors = (name, error) => { set(_formState.errors, name, error); _subjects.state.next({ errors: _formState.errors, }); }; const _setErrors = (errors) => { _formState.errors = errors; _subjects.state.next({ errors: _formState.errors, isValid: false, }); }; const updateValidAndValue = (name, shouldSkipSetValueAs, value, ref) => { const field = get(_fields, name); if (field) { const defaultValue = get(_formValues, name, isUndefined(value) ? get(_defaultValues, name) : value); isUndefined(defaultValue) || (ref && ref.defaultChecked) || shouldSkipSetValueAs ? set(_formValues, name, shouldSkipSetValueAs ? defaultValue : getFieldValue(field._f)) : setFieldValue(name, defaultValue); _state.mount && _setValid(); } }; const updateTouchAndDirty = (name, fieldValue, isBlurEvent, shouldDirty, shouldRender) => { let shouldUpdateField = false; let isPreviousDirty = false; const output = { name, }; if (!_options.disabled) { if (!isBlurEvent || shouldDirty) { if (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty) { isPreviousDirty = _formState.isDirty; _formState.isDirty = output.isDirty = _getDirty(); shouldUpdateField = isPreviousDirty !== output.isDirty; } const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue); isPreviousDirty = !!get(_formState.dirtyFields, name); isCurrentFieldPristine ? unset(_formState.dirtyFields, name) : set(_formState.dirtyFields, name, true); output.dirtyFields = _formState.dirtyFields; shouldUpdateField = shouldUpdateField || ((_proxyFormState.dirtyFields || _proxySubscribeFormState.dirtyFields) && isPreviousDirty !== !isCurrentFieldPristine); } if (isBlurEvent) { const isPreviousFieldTouched = get(_formState.touchedFields, name); if (!isPreviousFieldTouched) { set(_formState.touchedFields, name, isBlurEvent); output.touchedFields = _formState.touchedFields; shouldUpdateField = shouldUpdateField || ((_proxyFormState.touchedFields || _proxySubscribeFormState.touchedFields) && isPreviousFieldTouched !== isBlurEvent); } } shouldUpdateField && shouldRender && _subjects.state.next(output); } return shouldUpdateField ? output : {}; }; const shouldRenderByError = (name, isValid, error, fieldState) => { const previousFieldError = get(_formState.errors, name); const shouldUpdateValid = (_proxyFormState.isValid || _proxySubscribeFormState.isValid) && isBoolean(isValid) && _formState.isValid !== isValid; if (_options.delayError && error) { delayErrorCallback = debounce(() => updateErrors(name, error)); delayErrorCallback(_options.delayError); } else { clearTimeout(timer); delayErrorCallback = null; error ? set(_formState.errors, name, error) : unset(_formState.errors, name); } if ((error ? !deepEqual(previousFieldError, error) : previousFieldError) || !isEmptyObject(fieldState) || shouldUpdateValid) { const updatedFormState = { ...fieldState, ...(shouldUpdateValid && isBoolean(isValid) ? { isValid } : {}), errors: _formState.errors, name, }; _formState = { ..._formState, ...updatedFormState, }; _subjects.state.next(updatedFormState); } }; const _runSchema = async (name) => { _updateIsValidating(name, true); const result = await _options.resolver(_formValues, _options.context, getResolverOptions(name || _names.mount, _fields, _options.criteriaMode, _options.shouldUseNativeValidation)); _updateIsValidating(name); return result; }; const executeSchemaAndUpdateState = async (names) => { const { errors } = await _runSchema(names); if (names) { for (const name of names) { const error = get(errors, name); error ? set(_formState.errors, name, error) : unset(_formState.errors, name); } } else { _formState.errors = errors; } return errors; }; const executeBuiltInValidation = async (fields, shouldOnlyCheckValid, context = { valid: true, }) => { for (const name in fields) { const field = fields[name]; if (field) { const { _f, ...fieldValue } = field; if (_f) { const isFieldArrayRoot = _names.array.has(_f.name); const isPromiseFunction = field._f && hasPromiseValidation(field._f); if (isPromiseFunction && _proxyFormState.validatingFields) { _updateIsValidating([name], true); } const fieldError = await validateField(field, _names.disabled, _formValues, shouldDisplayAllAssociatedErrors, _options.shouldUseNativeValidation && !shouldOnlyCheckValid, isFieldArrayRoot); if (isPromiseFunction && _proxyFormState.validatingFields) { _updateIsValidating([name]); } if (fieldError[_f.name]) { context.valid = false; if (shouldOnlyCheckValid) { break; } } !shouldOnlyCheckValid && (get(fieldError, _f.name) ? isFieldArrayRoot ? updateFieldArrayRootError(_formState.errors, fieldError, _f.name) : set(_formState.errors, _f.name, fieldError[_f.name]) : unset(_formState.errors, _f.name)); } !isEmptyObject(fieldValue) && (await executeBuiltInValidation(fieldValue, shouldOnlyCheckValid, context)); } } return context.valid; }; const _removeUnmounted = () => { for (const name of _names.unMount) { const field = get(_fields, name); field && (field._f.refs ? field._f.refs.every((ref) => !live(ref)) : !live(field._f.ref)) && unregister(name); } _names.unMount = new Set(); }; const _getDirty = (name, data) => !_options.disabled && (name && data && set(_formValues, name, data), !deepEqual(getValues(), _defaultValues)); const _getWatch = (names, defaultValue, isGlobal) => generateWatchOutput(names, _names, { ...(_state.mount ? _formValues : isUndefined(defaultValue) ? _defaultValues : isString(names) ? { [names]: defaultValue } : defaultValue), }, isGlobal, defaultValue); const _getFieldArray = (name) => compact(get(_state.mount ? _formValues : _defaultValues, name, _options.shouldUnregister ? get(_defaultValues, name, []) : [])); const setFieldValue = (name, value, options = {}) => { const field = get(_fields, name); let fieldValue = value; if (field) { const fieldReference = field._f; if (fieldReference) { !fieldReference.disabled && set(_formValues, name, getFieldValueAs(value, fieldReference)); fieldValue = isHTMLElement(fieldReference.ref) && isNullOrUndefined(value) ? '' : value; if (isMultipleSelect(fieldReference.ref)) { [...fieldReference.ref.options].forEach((optionRef) => (optionRef.selected = fieldValue.includes(optionRef.value))); } else if (fieldReference.refs) { if (isCheckBoxInput(fieldReference.ref)) { fieldReference.refs.length > 1 ? fieldReference.refs.forEach((checkboxRef) => (!checkboxRef.defaultChecked || !checkboxRef.disabled) && (checkboxRef.checked = Array.isArray(fieldValue) ? !!fieldValue.find((data) => data === checkboxRef.value) : fieldValue === checkboxRef.value)) : fieldReference.refs[0] && (fieldReference.refs[0].checked = !!fieldValue); } else { fieldReference.refs.forEach((radioRef) => (radioRef.checked = radioRef.value === fieldValue)); } } else if (isFileInput(fieldReference.ref)) { fieldReference.ref.value = ''; } else { fieldReference.ref.value = fieldValue; if (!fieldReference.ref.type) { _subjects.state.next({ name, values: cloneObject(_formValues), }); } } } } (options.shouldDirty || options.shouldTouch) && updateTouchAndDirty(name, fieldValue, options.shouldTouch, options.shouldDirty, true); options.shouldValidate && trigger(name); }; const setValues = (name, value, options) => { for (const fieldKey in value) { const fieldValue = value[fieldKey]; const fieldName = `${name}.${fieldKey}`; const field = get(_fields, fieldName); (_names.array.has(name) || isObject(fieldValue) || (field && !field._f)) && !isDateObject(fieldValue) ? setValues(fieldName, fieldValue, options) : setFieldValue(fieldName, fieldValue, options); } }; const setValue = (name, value, options = {}) => { const field = get(_fields, name); const isFieldArray = _names.array.has(name); const cloneValue = cloneObject(value); set(_formValues, name, cloneValue); if (isFieldArray) { _subjects.array.next({ name, values: cloneObject(_formValues), }); if ((_proxyFormState.isDirty || _proxyFormState.dirtyFields || _proxySubscribeFormState.isDirty || _proxySubscribeFormState.dirtyFields) && options.shouldDirty) { _subjects.state.next({ name, dirtyFields: getDirtyFields(_defaultValues, _formValues), isDirty: _getDirty(name, cloneValue), }); } } else { field && !field._f && !isNullOrUndefined(cloneValue) ? setValues(name, cloneValue, options) : setFieldValue(name, cloneValue, options); } isWatched(name, _names) && _subjects.state.next({ ..._formState }); _subjects.state.next({ name: _state.mount ? name : undefined, values: cloneObject(_formValues), }); }; const onChange = async (event) => { _state.mount = true; const target = event.target; let name = target.name; let isFieldValueUpdated = true; const field = get(_fields, name); const _updateIsFieldValueUpdated = (fieldValue) => { isFieldValueUpdated = Number.isNaN(fieldValue) || (isDateObject(fieldValue) && isNaN(fieldValue.getTime())) || deepEqual(fieldValue, get(_formValues, name, fieldValue)); }; if (field) { let error; let isValid; const fieldValue = target.type ? getFieldValue(field._f) : getEventValue(event); const isBlurEvent = event.type === EVENTS.BLUR || event.type === EVENTS.FOCUS_OUT; const shouldSkipValidation = (!hasValidation(field._f) && !_options.resolver && !get(_formState.errors, name) && !field._f.deps) || skipValidation(isBlurEvent, get(_formState.touchedFields, name), _formState.isSubmitted, validationModeAfterSubmit, validationModeBeforeSubmit); const watched = isWatched(name, _names, isBlurEvent); set(_formValues, name, fieldValue); if (isBlurEvent) { field._f.onBlur && field._f.onBlur(event); delayErrorCallback && delayErrorCallback(0); } else if (field._f.onChange) { field._f.onChange(event); } const fieldState = updateTouchAndDirty(name, fieldValue, isBlurEvent); const shouldRender = !isEmptyObject(fieldState) || watched; !isBlurEvent && _subjects.state.next({ name, type: event.type, values: cloneObject(_formValues), }); if (shouldSkipValidation) { if (_proxyFormState.isValid || _proxySubscribeFormState.isValid) { if (_options.mode === 'onBlur') { if (isBlurEvent) { _setValid(); } } else if (!isBlurEvent) { _setValid(); } } return (shouldRender && _subjects.state.next({ name, ...(watched ? {} : fieldState) })); } !isBlurEvent && watched && _subjects.state.next({ ..._formState }); if (_options.resolver) { const { errors } = await _runSchema([name]); _updateIsFieldValueUpdated(fieldValue); if (isFieldValueUpdated) { const previousErrorLookupResult = schemaErrorLookup(_formState.errors, _fields, name); const errorLookupResult = schemaErrorLookup(errors, _fields, previousErrorLookupResult.name || name); error = errorLookupResult.error; name = errorLookupResult.name; isValid = isEmptyObject(errors); } } else { _updateIsValidating([name], true); error = (await validateField(field, _names.disabled, _formValues, shouldDisplayAllAssociatedErrors, _options.shouldUseNativeValidation))[name]; _updateIsValidating([name]); _updateIsFieldValueUpdated(fieldValue); if (isFieldValueUpdated) { if (error) { isValid = false; } else if (_proxyFormState.isValid || _proxySubscribeFormState.isValid) { isValid = await executeBuiltInValidation(_fields, true); } } } if (isFieldValueUpdated) { field._f.deps && trigger(field._f.deps); shouldRenderByError(name, isValid, error, fieldState); } } }; const _focusInput = (ref, key) => { if (get(_formState.errors, key) && ref.focus) { ref.focus(); return 1; } return; }; const trigger = async (name, options = {}) => { let isValid; let validationResult; const fieldNames = convertToArrayPayload(name); if (_options.resolver) { const errors = await executeSchemaAndUpdateState(isUndefined(name) ? name : fieldNames); isValid = isEmptyObject(errors); validationResult = name ? !fieldNames.some((name) => get(errors, name)) : isValid; } else if (name) { validationResult = (await Promise.all(fieldNames.map(async (fieldName) => { const field = get(_fields, fieldName); return await executeBuiltInValidation(field && field._f ? { [fieldName]: field } : field); }))).every(Boolean); !(!validationResult && !_formState.isValid) && _setValid(); } else { validationResult = isValid = await executeBuiltInValidation(_fields); } _subjects.state.next({ ...(!isString(name) || ((_proxyFormState.isValid || _proxySubscribeFormState.isValid) && isValid !== _formState.isValid) ? {} : { name }), ...(_options.resolver || !name ? { isValid } : {}), errors: _formState.errors, }); options.shouldFocus && !validationResult && iterateFieldsByAction(_fields, _focusInput, name ? fieldNames : _names.mount); return validationResult; }; const getValues = (fieldNames) => { const values = { ...(_state.mount ? _formValues : _defaultValues), }; return isUndefined(fieldNames) ? values : isString(fieldNames) ? get(values, fieldNames) : fieldNames.map((name) => get(values, name)); }; const getFieldState = (name, formState) => ({ invalid: !!get((formState || _formState).errors, name), isDirty: !!get((formState || _formState).dirtyFields, name), error: get((formState || _formState).errors, name), isValidating: !!get(_formState.validatingFields, name), isTouched: !!get((formState || _formState).touchedFields, name), }); const clearErrors = (name) => { name && convertToArrayPayload(name).forEach((inputName) => unset(_formState.errors, inputName)); _subjects.state.next({ errors: name ? _formState.errors : {}, }); }; const setError = (name, error, options) => { const ref = (get(_fields, name, { _f: {} })._f || {}).ref; const currentError = get(_formState.errors, name) || {}; // Don