UNPKG

react-hook-form-6

Version:
1,185 lines (1,129 loc) 82.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var React = require('react'); var isHTMLElement = (value) => value instanceof HTMLElement; const EVENTS = { BLUR: 'blur', CHANGE: 'change', INPUT: 'input', }; const VALIDATION_MODE = { onBlur: 'onBlur', onChange: 'onChange', onSubmit: 'onSubmit', onTouched: 'onTouched', all: 'all', }; const SELECT = 'select'; const UNDEFINED = 'undefined'; const INPUT_VALIDATION_RULES = { max: 'max', min: 'min', maxLength: 'maxLength', minLength: 'minLength', pattern: 'pattern', required: 'required', validate: 'validate', }; function attachEventListeners({ ref }, shouldAttachChangeEvent, handleChange) { if (isHTMLElement(ref) && handleChange) { ref.addEventListener(shouldAttachChangeEvent ? EVENTS.CHANGE : EVENTS.INPUT, handleChange); ref.addEventListener(EVENTS.BLUR, handleChange); } } var isNullOrUndefined = (value) => value == null; const isObjectType = (value) => typeof value === 'object'; var isObject = (value) => !isNullOrUndefined(value) && !Array.isArray(value) && isObjectType(value) && !(value instanceof Date); var isKey = (value) => /^\w*$/.test(value); var compact = (value) => value.filter(Boolean); var stringToPath = (input) => compact(input .replace(/["|']/g, '') .replace(/\[/g, '.') .replace(/\]/g, '') .split('.')); function 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]) ? [] : {}; } object[key] = newValue; object = object[key]; } return object; } var transformToNestObject = (data, value = {}) => { for (const key in data) { !isKey(key) ? set(value, key, data[key]) : (value[key] = data[key]); } return value; }; var isUndefined = (val) => val === undefined; var get = (obj = {}, path, defaultValue) => { const result = compact(path.split(/[,[\].]+?/)).reduce((result, key) => (isNullOrUndefined(result) ? result : result[key]), obj); return isUndefined(result) || result === obj ? isUndefined(obj[path]) ? defaultValue : obj[path] : result; }; var focusOnErrorField = (fields, fieldErrors) => { for (const key in fields) { if (get(fieldErrors, key)) { const field = fields[key]; if (field) { if (field.ref.focus && isUndefined(field.ref.focus())) { break; } else if (field.options) { field.options[0].ref.focus(); break; } } } } }; var removeAllEventListeners = (ref, validateWithStateUpdate) => { if (isHTMLElement(ref) && ref.removeEventListener) { ref.removeEventListener(EVENTS.INPUT, validateWithStateUpdate); ref.removeEventListener(EVENTS.CHANGE, validateWithStateUpdate); ref.removeEventListener(EVENTS.BLUR, validateWithStateUpdate); } }; const defaultReturn = { isValid: false, value: null, }; var getRadioValue = (options) => Array.isArray(options) ? options.reduce((previous, option) => option && option.ref.checked ? { isValid: true, value: option.ref.value, } : previous, defaultReturn) : defaultReturn; var getMultipleSelectValue = (options) => [...options] .filter(({ selected }) => selected) .map(({ value }) => value); var isRadioInput = (element) => element.type === 'radio'; var isFileInput = (element) => element.type === 'file'; var isCheckBoxInput = (element) => element.type === 'checkbox'; var isMultipleSelect = (element) => element.type === `${SELECT}-multiple`; 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.ref.checked) .map(({ ref: { value } }) => value); return { value: values, isValid: !!values.length }; } const { checked, value, attributes } = options[0].ref; return checked ? attributes && !isUndefined(attributes.value) ? isUndefined(value) || value === '' ? validResult : { value: value, isValid: true } : validResult : defaultResult; } return defaultResult; }; function getFieldValue(fieldsRef, name, shallowFieldsStateRef, excludeDisabled, shouldKeepRawValue) { const field = fieldsRef.current[name]; if (field) { const { ref: { value, disabled }, ref, valueAsNumber, valueAsDate, setValueAs, } = field; if (disabled && excludeDisabled) { return; } if (isFileInput(ref)) { return ref.files; } if (isRadioInput(ref)) { return getRadioValue(field.options).value; } if (isMultipleSelect(ref)) { return getMultipleSelectValue(ref.options); } if (isCheckBoxInput(ref)) { return getCheckboxValue(field.options).value; } return shouldKeepRawValue ? value : valueAsNumber ? value === '' ? NaN : +value : valueAsDate ? ref.valueAsDate : setValueAs ? setValueAs(value) : value; } if (shallowFieldsStateRef) { return get(shallowFieldsStateRef.current, name); } } function isDetached(element) { if (!element) { return true; } if (!(element instanceof HTMLElement) || element.nodeType === Node.DOCUMENT_NODE) { return false; } return isDetached(element.parentNode); } var isEmptyObject = (value) => isObject(value) && !Object.keys(value).length; var isBoolean = (value) => typeof value === 'boolean'; 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 unset(object, path) { const updatePath = isKey(path) ? [path] : stringToPath(path); const childObject = updatePath.length == 1 ? object : baseGet(object, updatePath); const key = updatePath[updatePath.length - 1]; let previousObjRef; if (childObject) { delete childObject[key]; } for (let k = 0; k < updatePath.slice(0, -1).length; k++) { let index = -1; let objectRef; const currentPaths = updatePath.slice(0, -(k + 1)); const currentPathsLength = currentPaths.length - 1; if (k > 0) { previousObjRef = object; } while (++index < currentPaths.length) { const item = currentPaths[index]; objectRef = objectRef ? objectRef[item] : object[item]; if (currentPathsLength === index && ((isObject(objectRef) && isEmptyObject(objectRef)) || (Array.isArray(objectRef) && !objectRef.filter((data) => (isObject(data) && !isEmptyObject(data)) || isBoolean(data)).length))) { previousObjRef ? delete previousObjRef[item] : delete object[item]; } previousObjRef = objectRef; } } return object; } const isSameRef = (fieldValue, ref) => fieldValue && fieldValue.ref === ref; function findRemovedFieldAndRemoveListener(fieldsRef, handleChange, field, shallowFieldsStateRef, shouldUnregister, forceDelete) { const { ref, ref: { name }, } = field; const fieldRef = fieldsRef.current[name]; if (!shouldUnregister) { const value = getFieldValue(fieldsRef, name, shallowFieldsStateRef); !isUndefined(value) && set(shallowFieldsStateRef.current, name, value); } if (!ref.type || !fieldRef) { delete fieldsRef.current[name]; return; } if (isRadioInput(ref) || isCheckBoxInput(ref)) { if (Array.isArray(fieldRef.options) && fieldRef.options.length) { compact(fieldRef.options).forEach((option = {}, index) => { if ((isDetached(option.ref) && isSameRef(option, option.ref)) || forceDelete) { removeAllEventListeners(option.ref, handleChange); unset(fieldRef.options, `[${index}]`); } }); if (fieldRef.options && !compact(fieldRef.options).length) { delete fieldsRef.current[name]; } } else { delete fieldsRef.current[name]; } } else if ((isDetached(ref) && isSameRef(fieldRef, ref)) || forceDelete) { removeAllEventListeners(ref, handleChange); delete fieldsRef.current[name]; } } var isPrimitive = (value) => isNullOrUndefined(value) || !isObjectType(value); function deepMerge(target, source) { if (isPrimitive(target) || isPrimitive(source)) { return source; } for (const key in source) { const targetValue = target[key]; const sourceValue = source[key]; try { target[key] = (isObject(targetValue) && isObject(sourceValue)) || (Array.isArray(targetValue) && Array.isArray(sourceValue)) ? deepMerge(targetValue, sourceValue) : sourceValue; } catch (_a) { } } return target; } function deepEqual(object1, object2, isErrorObject) { if (isPrimitive(object1) || isPrimitive(object2) || object1 instanceof Date || object2 instanceof Date) { return object1 === object2; } if (!React.isValidElement(object1)) { 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 (!(isErrorObject && key === 'ref')) { const val2 = object2[key]; if ((isObject(val1) || Array.isArray(val1)) && (isObject(val2) || Array.isArray(val2)) ? !deepEqual(val1, val2, isErrorObject) : val1 !== val2) { return false; } } } } return true; } function setDirtyFields(values, defaultValues, dirtyFields, parentNode, parentName) { let index = -1; while (++index < values.length) { for (const key in values[index]) { if (Array.isArray(values[index][key])) { !dirtyFields[index] && (dirtyFields[index] = {}); dirtyFields[index][key] = []; setDirtyFields(values[index][key], get(defaultValues[index] || {}, key, []), dirtyFields[index][key], dirtyFields[index], key); } else { deepEqual(get(defaultValues[index] || {}, key), values[index][key]) ? set(dirtyFields[index] || {}, key) : (dirtyFields[index] = Object.assign(Object.assign({}, dirtyFields[index]), { [key]: true })); } } parentNode && !dirtyFields.length && delete parentNode[parentName]; } return dirtyFields; } var setFieldArrayDirtyFields = (values, defaultValues, dirtyFields) => deepMerge(setDirtyFields(values, defaultValues, dirtyFields.slice(0, values.length)), setDirtyFields(defaultValues, values, dirtyFields.slice(0, values.length))); var isString = (value) => typeof value === 'string'; var getFieldsValues = (fieldsRef, shallowFieldsState, shouldUnregister, excludeDisabled, search) => { const output = {}; for (const name in fieldsRef.current) { if (isUndefined(search) || (isString(search) ? name.startsWith(search) : Array.isArray(search) && search.find((data) => name.startsWith(data)))) { output[name] = getFieldValue(fieldsRef, name, undefined, excludeDisabled); } } return shouldUnregister ? transformToNestObject(output) : deepMerge(shallowFieldsState, transformToNestObject(output)); }; var isErrorStateChanged = ({ errors, name, error, validFields, fieldsWithValidation, }) => { const isValid = isUndefined(error); const previousError = get(errors, name); return ((isValid && !!previousError) || (!isValid && !deepEqual(previousError, error, true)) || (isValid && get(fieldsWithValidation, name) && !get(validFields, name))); }; var isRegex = (value) => value instanceof RegExp; var getValueAndMessage = (validationData) => isObject(validationData) && !isRegex(validationData) ? validationData : { value: validationData, message: '', }; var isFunction = (value) => typeof value === 'function'; var isMessage = (value) => isString(value) || React.isValidElement(value); function getValidateError(result, ref, type = 'validate') { if (isMessage(result) || (isBoolean(result) && !result)) { return { type, message: isMessage(result) ? result : '', ref, }; } } var appendErrors = (name, validateAllFieldCriteria, errors, type, message) => validateAllFieldCriteria ? Object.assign(Object.assign({}, errors[name]), { types: Object.assign(Object.assign({}, (errors[name] && errors[name].types ? errors[name].types : {})), { [type]: message || true }) }) : {}; var validateField = async (fieldsRef, validateAllFieldCriteria, { ref, ref: { value }, options, required, maxLength, minLength, min, max, pattern, validate, }, shallowFieldsStateRef) => { const name = ref.name; const error = {}; const isRadio = isRadioInput(ref); const isCheckBox = isCheckBoxInput(ref); const isRadioOrCheckbox = isRadio || isCheckBox; const isEmpty = value === ''; 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] = Object.assign({ type: exceedMax ? maxType : minType, message, ref }, (exceedMax ? appendErrorsCurry(maxType, message) : appendErrorsCurry(minType, message))); }; if (required && ((!isRadio && !isCheckBox && (isEmpty || isNullOrUndefined(value))) || (isBoolean(value) && !value) || (isCheckBox && !getCheckboxValue(options).isValid) || (isRadio && !getRadioValue(options).isValid))) { const { value, message } = isMessage(required) ? { value: !!required, message: required } : getValueAndMessage(required); if (value) { error[name] = Object.assign({ type: INPUT_VALIDATION_RULES.required, message, ref: isRadioOrCheckbox ? ((fieldsRef.current[name].options || [])[0] || {}).ref : ref }, appendErrorsCurry(INPUT_VALIDATION_RULES.required, message)); if (!validateAllFieldCriteria) { return error; } } } if ((!isNullOrUndefined(min) || !isNullOrUndefined(max)) && value !== '') { let exceedMax; let exceedMin; const maxOutput = getValueAndMessage(max); const minOutput = getValueAndMessage(min); if (!isNaN(value)) { const valueNumber = ref.valueAsNumber || parseFloat(value); if (!isNullOrUndefined(maxOutput.value)) { exceedMax = valueNumber > maxOutput.value; } if (!isNullOrUndefined(minOutput.value)) { exceedMin = valueNumber < minOutput.value; } } else { const valueDate = ref.valueAsDate || new Date(value); if (isString(maxOutput.value)) { exceedMax = valueDate > new Date(maxOutput.value); } if (isString(minOutput.value)) { exceedMin = valueDate < new Date(minOutput.value); } } if (exceedMax || exceedMin) { getMinMaxMessage(!!exceedMax, maxOutput.message, minOutput.message, INPUT_VALIDATION_RULES.max, INPUT_VALIDATION_RULES.min); if (!validateAllFieldCriteria) { return error; } } } if (isString(value) && !isEmpty && (maxLength || minLength)) { const maxLengthOutput = getValueAndMessage(maxLength); const minLengthOutput = getValueAndMessage(minLength); const exceedMax = !isNullOrUndefined(maxLengthOutput.value) && value.length > maxLengthOutput.value; const exceedMin = !isNullOrUndefined(minLengthOutput.value) && value.length < minLengthOutput.value; if (exceedMax || exceedMin) { getMinMaxMessage(exceedMax, maxLengthOutput.message, minLengthOutput.message); if (!validateAllFieldCriteria) { return error; } } } if (isString(value) && pattern && !isEmpty) { const { value: patternValue, message } = getValueAndMessage(pattern); if (isRegex(patternValue) && !patternValue.test(value)) { error[name] = Object.assign({ type: INPUT_VALIDATION_RULES.pattern, message, ref }, appendErrorsCurry(INPUT_VALIDATION_RULES.pattern, message)); if (!validateAllFieldCriteria) { return error; } } } if (validate) { const fieldValue = getFieldValue(fieldsRef, name, shallowFieldsStateRef, false, true); const validateRef = isRadioOrCheckbox && options ? options[0].ref : ref; if (isFunction(validate)) { const result = await validate(fieldValue); const validateError = getValidateError(result, validateRef); if (validateError) { error[name] = Object.assign(Object.assign({}, validateError), appendErrorsCurry(INPUT_VALIDATION_RULES.validate, validateError.message)); if (!validateAllFieldCriteria) { return error; } } } else if (isObject(validate)) { let validationResult = {}; for (const [key, validateFunction] of Object.entries(validate)) { if (!isEmptyObject(validationResult) && !validateAllFieldCriteria) { break; } const validateResult = await validateFunction(fieldValue); const validateError = getValidateError(validateResult, validateRef, key); if (validateError) { validationResult = Object.assign(Object.assign({}, validateError), appendErrorsCurry(key, validateError.message)); if (validateAllFieldCriteria) { error[name] = validationResult; } } } if (!isEmptyObject(validationResult)) { error[name] = Object.assign({ ref: validateRef }, validationResult); if (!validateAllFieldCriteria) { return error; } } } } return error; }; const getPath = (rootPath, values, paths = []) => { for (const property in values) { const rootName = (rootPath + (isObject(values) ? `.${property}` : `[${property}]`)); isPrimitive(values[property]) ? paths.push(rootName) : getPath(rootName, values[property], paths); } return paths; }; var assignWatchFields = (fieldValues, fieldName, watchFields, inputValue, isSingleField) => { let value = undefined; watchFields.add(fieldName); if (!isEmptyObject(fieldValues)) { value = get(fieldValues, fieldName); if (isObject(value) || Array.isArray(value)) { getPath(fieldName, value).forEach((name) => watchFields.add(name)); } } return isUndefined(value) ? isSingleField ? inputValue : get(inputValue, fieldName) : value; }; var skipValidation = ({ isOnBlur, isOnChange, isOnTouch, isTouched, isReValidateOnBlur, isReValidateOnChange, isBlurEvent, isSubmitted, isOnAll, }) => { if (isOnAll) { return false; } else if (!isSubmitted && isOnTouch) { return !(isTouched || isBlurEvent); } else if (isSubmitted ? isReValidateOnBlur : isOnBlur) { return !isBlurEvent; } else if (isSubmitted ? isReValidateOnChange : isOnChange) { return isBlurEvent; } return true; }; var getFieldArrayParentName = (name) => name.substring(0, name.indexOf('[')); const isMatchFieldArrayName = (name, searchName) => RegExp(`^${searchName}([|.)\\d+`.replace(/\[/g, '\\[').replace(/\]/g, '\\]')).test(name); var isNameInFieldArray = (names, name) => [...names].some((current) => isMatchFieldArrayName(name, current)); var isSelectInput = (element) => element.type === `${SELECT}-one`; function onDomRemove(fieldsRef, removeFieldEventListenerAndRef) { const observer = new MutationObserver(() => { for (const field of Object.values(fieldsRef.current)) { if (field && field.options) { for (const option of field.options) { if (option && option.ref && isDetached(option.ref)) { removeFieldEventListenerAndRef(field); } } } else if (field && isDetached(field.ref)) { removeFieldEventListenerAndRef(field); } } }); observer.observe(window.document, { childList: true, subtree: true, }); return observer; } var isWeb = typeof window !== UNDEFINED && typeof document !== UNDEFINED; function cloneObject(data) { var _a; let copy; if (isPrimitive(data) || (isWeb && (data instanceof File || isHTMLElement(data)))) { return data; } if (!['Set', 'Map', 'Object', 'Date', 'Array'].includes((_a = data.constructor) === null || _a === void 0 ? void 0 : _a.name)) { return data; } if (data instanceof Date) { copy = new Date(data.getTime()); return copy; } if (data instanceof Set) { copy = new Set(); for (const item of data) { copy.add(item); } return copy; } if (data instanceof Map) { copy = new Map(); for (const key of data.keys()) { copy.set(key, cloneObject(data.get(key))); } return copy; } copy = Array.isArray(data) ? [] : {}; for (const key in data) { copy[key] = cloneObject(data[key]); } return copy; } var modeChecker = (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, }); var isRadioOrCheckboxFunction = (ref) => isRadioInput(ref) || isCheckBoxInput(ref); const isWindowUndefined = typeof window === UNDEFINED; const isProxyEnabled = isWeb ? 'Proxy' in window : typeof Proxy !== UNDEFINED; function useForm({ mode = VALIDATION_MODE.onSubmit, reValidateMode = VALIDATION_MODE.onChange, resolver, context, defaultValues = {}, shouldFocusError = true, shouldUnregister = true, criteriaMode, } = {}) { const fieldsRef = React.useRef({}); const fieldArrayDefaultValuesRef = React.useRef({}); const fieldArrayValuesRef = React.useRef({}); const watchFieldsRef = React.useRef(new Set()); const useWatchFieldsRef = React.useRef({}); const useWatchRenderFunctionsRef = React.useRef({}); const fieldsWithValidationRef = React.useRef({}); const validFieldsRef = React.useRef({}); const defaultValuesRef = React.useRef(defaultValues); const isUnMount = React.useRef(false); const isWatchAllRef = React.useRef(false); const handleChangeRef = React.useRef(); const shallowFieldsStateRef = React.useRef({}); const resetFieldArrayFunctionRef = React.useRef({}); const contextRef = React.useRef(context); const resolverRef = React.useRef(resolver); const fieldArrayNamesRef = React.useRef(new Set()); const modeRef = React.useRef(modeChecker(mode)); const { isOnSubmit, isOnTouch } = modeRef.current; const isValidateAllFieldCriteria = criteriaMode === VALIDATION_MODE.all; const [formState, setFormState] = React.useState({ isDirty: false, isValidating: false, dirtyFields: {}, isSubmitted: false, submitCount: 0, touched: {}, isSubmitting: false, isSubmitSuccessful: false, isValid: !isOnSubmit, errors: {}, }); const readFormStateRef = React.useRef({ isDirty: !isProxyEnabled, dirtyFields: !isProxyEnabled, touched: !isProxyEnabled || isOnTouch, isValidating: !isProxyEnabled, isSubmitting: !isProxyEnabled, isValid: !isProxyEnabled, }); const formStateRef = React.useRef(formState); const observerRef = React.useRef(); const { isOnBlur: isReValidateOnBlur, isOnChange: isReValidateOnChange, } = React.useRef(modeChecker(reValidateMode)).current; contextRef.current = context; resolverRef.current = resolver; formStateRef.current = formState; shallowFieldsStateRef.current = shouldUnregister ? {} : isEmptyObject(shallowFieldsStateRef.current) ? cloneObject(defaultValues) : shallowFieldsStateRef.current; const updateFormState = React.useCallback((state = {}) => { if (!isUnMount.current) { formStateRef.current = Object.assign(Object.assign({}, formStateRef.current), state); setFormState(formStateRef.current); } }, []); const updateIsValidating = () => readFormStateRef.current.isValidating && updateFormState({ isValidating: true, }); const shouldRenderBaseOnError = React.useCallback((name, error, shouldRender = false, state = {}, isValid) => { let shouldReRender = shouldRender || isErrorStateChanged({ errors: formStateRef.current.errors, error, name, validFields: validFieldsRef.current, fieldsWithValidation: fieldsWithValidationRef.current, }); const previousError = get(formStateRef.current.errors, name); if (error) { unset(validFieldsRef.current, name); shouldReRender = shouldReRender || !previousError || !deepEqual(previousError, error, true); set(formStateRef.current.errors, name, error); } else { if (get(fieldsWithValidationRef.current, name) || resolverRef.current) { set(validFieldsRef.current, name, true); shouldReRender = shouldReRender || previousError; } unset(formStateRef.current.errors, name); } if ((shouldReRender && !isNullOrUndefined(shouldRender)) || !isEmptyObject(state) || readFormStateRef.current.isValidating) { updateFormState(Object.assign(Object.assign(Object.assign({}, state), (resolverRef.current ? { isValid: !!isValid } : {})), { isValidating: false })); } }, []); const setFieldValue = React.useCallback((name, rawValue) => { const { ref, options } = fieldsRef.current[name]; const value = isWeb && isHTMLElement(ref) && isNullOrUndefined(rawValue) ? '' : rawValue; if (isRadioInput(ref)) { (options || []).forEach(({ ref: radioRef }) => (radioRef.checked = radioRef.value === value)); } else if (isFileInput(ref) && !isString(value)) { ref.files = value; } else if (isMultipleSelect(ref)) { [...ref.options].forEach((selectRef) => (selectRef.selected = value.includes(selectRef.value))); } else if (isCheckBoxInput(ref) && options) { options.length > 1 ? options.forEach(({ ref: checkboxRef }) => (checkboxRef.checked = Array.isArray(value) ? !!value.find((data) => data === checkboxRef.value) : value === checkboxRef.value)) : (options[0].ref.checked = !!value); } else { ref.value = value; } }, []); const isFormDirty = React.useCallback((name, data) => { if (readFormStateRef.current.isDirty) { const formValues = getValues(); name && data && set(formValues, name, data); return !deepEqual(formValues, defaultValuesRef.current); } return false; }, []); const updateAndGetDirtyState = React.useCallback((name, shouldRender = true) => { if (readFormStateRef.current.isDirty || readFormStateRef.current.dirtyFields) { const isFieldDirty = !deepEqual(get(defaultValuesRef.current, name), getFieldValue(fieldsRef, name, shallowFieldsStateRef)); const isDirtyFieldExist = get(formStateRef.current.dirtyFields, name); const previousIsDirty = formStateRef.current.isDirty; isFieldDirty ? set(formStateRef.current.dirtyFields, name, true) : unset(formStateRef.current.dirtyFields, name); const state = { isDirty: isFormDirty(), dirtyFields: formStateRef.current.dirtyFields, }; const isChanged = (readFormStateRef.current.isDirty && previousIsDirty !== state.isDirty) || (readFormStateRef.current.dirtyFields && isDirtyFieldExist !== get(formStateRef.current.dirtyFields, name)); isChanged && shouldRender && updateFormState(state); return isChanged ? state : {}; } return {}; }, []); const executeValidation = React.useCallback(async (name, skipReRender) => { { if (!fieldsRef.current[name]) { console.warn('📋 Field is missing with `name` attribute: ', name); return false; } } const error = (await validateField(fieldsRef, isValidateAllFieldCriteria, fieldsRef.current[name], shallowFieldsStateRef))[name]; shouldRenderBaseOnError(name, error, skipReRender); return isUndefined(error); }, [shouldRenderBaseOnError, isValidateAllFieldCriteria]); const executeSchemaOrResolverValidation = React.useCallback(async (names) => { const { errors } = await resolverRef.current(getValues(), contextRef.current, isValidateAllFieldCriteria); const previousFormIsValid = formStateRef.current.isValid; if (Array.isArray(names)) { const isInputsValid = names .map((name) => { const error = get(errors, name); error ? set(formStateRef.current.errors, name, error) : unset(formStateRef.current.errors, name); return !error; }) .every(Boolean); updateFormState({ isValid: isEmptyObject(errors), isValidating: false, }); return isInputsValid; } else { const error = get(errors, names); shouldRenderBaseOnError(names, error, previousFormIsValid !== isEmptyObject(errors), {}, isEmptyObject(errors)); return !error; } }, [shouldRenderBaseOnError, isValidateAllFieldCriteria]); const trigger = React.useCallback(async (name) => { const fields = name || Object.keys(fieldsRef.current); updateIsValidating(); if (resolverRef.current) { return executeSchemaOrResolverValidation(fields); } if (Array.isArray(fields)) { !name && (formStateRef.current.errors = {}); const result = await Promise.all(fields.map(async (data) => await executeValidation(data, null))); updateFormState({ isValidating: false, }); return result.every(Boolean); } return await executeValidation(fields); }, [executeSchemaOrResolverValidation, executeValidation]); const setInternalValues = React.useCallback((name, value, { shouldDirty, shouldValidate }) => { const data = {}; set(data, name, value); for (const fieldName of getPath(name, value)) { if (fieldsRef.current[fieldName]) { setFieldValue(fieldName, get(data, fieldName)); shouldDirty && updateAndGetDirtyState(fieldName); shouldValidate && trigger(fieldName); } } }, [trigger, setFieldValue, updateAndGetDirtyState]); const setInternalValue = React.useCallback((name, value, config) => { !shouldUnregister && !isPrimitive(value) && set(shallowFieldsStateRef.current, name, Array.isArray(value) ? [...value] : Object.assign({}, value)); if (fieldsRef.current[name]) { setFieldValue(name, value); config.shouldDirty && updateAndGetDirtyState(name); config.shouldValidate && trigger(name); } else if (!isPrimitive(value)) { setInternalValues(name, value, config); if (fieldArrayNamesRef.current.has(name)) { const parentName = getFieldArrayParentName(name) || name; set(fieldArrayDefaultValuesRef.current, name, value); resetFieldArrayFunctionRef.current[parentName]({ [parentName]: get(fieldArrayDefaultValuesRef.current, parentName), }); if ((readFormStateRef.current.isDirty || readFormStateRef.current.dirtyFields) && config.shouldDirty) { set(formStateRef.current.dirtyFields, name, setFieldArrayDirtyFields(value, get(defaultValuesRef.current, name, []), get(formStateRef.current.dirtyFields, name, []))); updateFormState({ isDirty: !deepEqual(Object.assign(Object.assign({}, getValues()), { [name]: value }), defaultValuesRef.current), }); } } } !shouldUnregister && set(shallowFieldsStateRef.current, name, value); }, [updateAndGetDirtyState, setFieldValue, setInternalValues]); const isFieldWatched = (name) => isWatchAllRef.current || watchFieldsRef.current.has(name) || watchFieldsRef.current.has((name.match(/\w+/) || [])[0]); const renderWatchedInputs = (name) => { let found = true; if (!isEmptyObject(useWatchFieldsRef.current)) { for (const key in useWatchFieldsRef.current) { if (!name || !useWatchFieldsRef.current[key].size || useWatchFieldsRef.current[key].has(name) || useWatchFieldsRef.current[key].has(getFieldArrayParentName(name))) { useWatchRenderFunctionsRef.current[key](); found = false; } } } return found; }; function setValue(name, value, config) { setInternalValue(name, value, config || {}); isFieldWatched(name) && updateFormState(); renderWatchedInputs(name); } handleChangeRef.current = handleChangeRef.current ? handleChangeRef.current : async ({ type, target }) => { let name = target.name; const field = fieldsRef.current[name]; let error; let isValid; if (field) { const isBlurEvent = type === EVENTS.BLUR; const shouldSkipValidation = skipValidation(Object.assign({ isBlurEvent, isReValidateOnChange, isReValidateOnBlur, isTouched: !!get(formStateRef.current.touched, name), isSubmitted: formStateRef.current.isSubmitted }, modeRef.current)); let state = updateAndGetDirtyState(name, false); let shouldRender = !isEmptyObject(state) || (!isBlurEvent && isFieldWatched(name)); if (isBlurEvent && !get(formStateRef.current.touched, name) && readFormStateRef.current.touched) { set(formStateRef.current.touched, name, true); state = Object.assign(Object.assign({}, state), { touched: formStateRef.current.touched }); } if (!shouldUnregister && isCheckBoxInput(target)) { set(shallowFieldsStateRef.current, name, getFieldValue(fieldsRef, name)); } if (shouldSkipValidation) { !isBlurEvent && renderWatchedInputs(name); return ((!isEmptyObject(state) || (shouldRender && isEmptyObject(state))) && updateFormState(state)); } updateIsValidating(); if (resolverRef.current) { const { errors } = await resolverRef.current(getValues(), contextRef.current, isValidateAllFieldCriteria); const previousFormIsValid = formStateRef.current.isValid; error = get(errors, name); if (isCheckBoxInput(target) && !error && resolverRef.current) { const parentNodeName = getFieldArrayParentName(name); const currentError = get(errors, parentNodeName, {}); currentError.type && currentError.message && (error = currentError); if (parentNodeName && (currentError || get(formStateRef.current.errors, parentNodeName))) { name = parentNodeName; } } isValid = isEmptyObject(errors); previousFormIsValid !== isValid && (shouldRender = true); } else { error = (await validateField(fieldsRef, isValidateAllFieldCriteria, field, shallowFieldsStateRef))[name]; } !isBlurEvent && renderWatchedInputs(name); shouldRenderBaseOnError(name, error, shouldRender, state, isValid); } }; function setFieldArrayDefaultValues(data) { if (!shouldUnregister) { let copy = cloneObject(data); for (const value of fieldArrayNamesRef.current) { if (isKey(value) && !copy[value]) { copy = Object.assign(Object.assign({}, copy), { [value]: [] }); } } return copy; } return data; } function getValues(payload) { if (isString(payload)) { return getFieldValue(fieldsRef, payload, shallowFieldsStateRef); } if (Array.isArray(payload)) { const data = {}; for (const name of payload) { set(data, name, getFieldValue(fieldsRef, name, shallowFieldsStateRef)); } return data; } return setFieldArrayDefaultValues(getFieldsValues(fieldsRef, cloneObject(shallowFieldsStateRef.current), shouldUnregister)); } const validateResolver = React.useCallback(async (values = {}) => { const newDefaultValues = isEmptyObject(fieldsRef.current) ? defaultValuesRef.current : {}; const { errors } = (await resolverRef.current(Object.assign(Object.assign(Object.assign({}, newDefaultValues), getValues()), values), contextRef.current, isValidateAllFieldCriteria)) || {}; const isValid = isEmptyObject(errors); formStateRef.current.isValid !== isValid && updateFormState({ isValid, }); }, [isValidateAllFieldCriteria]); const removeFieldEventListener = React.useCallback((field, forceDelete) => { findRemovedFieldAndRemoveListener(fieldsRef, handleChangeRef.current, field, shallowFieldsStateRef, shouldUnregister, forceDelete); if (shouldUnregister) { unset(validFieldsRef.current, field.ref.name); unset(fieldsWithValidationRef.current, field.ref.name); } }, [shouldUnregister]); const updateWatchedValue = React.useCallback((name) => { if (isWatchAllRef.current) { updateFormState(); } else { for (const watchField of watchFieldsRef.current) { if (watchField.startsWith(name)) { updateFormState(); break; } } renderWatchedInputs(name); } }, []); const removeFieldEventListenerAndRef = React.useCallback((field, forceDelete) => { if (field) { removeFieldEventListener(field, forceDelete); if (shouldUnregister && !compact(field.options || []).length) { unset(formStateRef.current.errors, field.ref.name); set(formStateRef.current.dirtyFields, field.ref.name, true); updateFormState({ isDirty: isFormDirty(), }); readFormStateRef.current.isValid && resolverRef.current && validateResolver(); updateWatchedValue(field.ref.name); } } }, [validateResolver, removeFieldEventListener]); function clearErrors(name) { name && (Array.isArray(name) ? name : [name]).forEach((inputName) => fieldsRef.current[inputName] && isKey(inputName) ? delete formStateRef.current.errors[inputName] : unset(formStateRef.current.errors, inputName)); updateFormState({ errors: name ? formStateRef.current.errors : {}, }); } function setError(name, error) { const ref = (fieldsRef.current[name] || {}).ref; set(formStateRef.current.errors, name, Object.assign(Object.assign({}, error), { ref })); updateFormState({ isValid: false, }); error.shouldFocus && ref && ref.focus && ref.focus(); } const watchInternal = React.useCallback((fieldNames, defaultValue, watchId) => { const watchFields = watchId ? useWatchFieldsRef.current[watchId] : watchFieldsRef.current; let fieldValues = getFieldsValues(fieldsRef, cloneObject(shallowFieldsStateRef.current), shouldUnregister, false, fieldNames); if (isString(fieldNames)) { const parentNodeName = getFieldArrayParentName(fieldNames) || fieldNames; if (fieldArrayNamesRef.current.has(parentNodeName)) { fieldValues = Object.assign(Object.assign({}, fieldArrayValuesRef.current), fieldValues); } return assignWatchFields(fieldValues, fieldNames, watchFields, isUndefined(get(defaultValuesRef.current, fieldNames)) ? defaultValue : get(defaultValuesRef.current, fieldNames), true); } const combinedDefaultValues = isUndefined(defaultValue) ? defaultValuesRef.current : defaultValue; if (Array.isArray(fieldNames)) { return fieldNames.reduce((previous, name) => (Object.assign(Object.assign({}, previous), { [name]: assignWatchFields(fieldValues, name, watchFields, combinedDefaultValues) })), {}); } isWatchAllRef.current = isUndefined(watchId); return transformToNestObject((!isEmptyObject(fieldValues) && fieldValues) || combinedDefaultValues); }, []); function watch(fieldNames, defaultValue) { return watchInternal(fieldNames, defaultValue); } function unregister(name) { for (const fieldName of Array.isArray(name) ? name : [name]) { removeFieldEventListenerAndRef(fieldsRef.current[fieldName], true); } } function registerFieldRef(ref, options = {}) { { if (!ref.name) { return console.warn('📋 Field is missing `name` attribute', ref, `https://react-hook-form.com/api#useForm`); } if (fieldArrayNamesRef.current.has(ref.name.split(/\[\d+\]$/)[0]) && !RegExp(`^${ref.name.split(/\[\d+\]$/)[0]}[\\d+].\\w+` .replace(/\[/g, '\\[') .replace(/\]/g, '\\]')).test(ref.name)) { return console.warn('📋 `name` prop should be in object shape: name="test[index].name"', ref, 'https://react-hook-form.com/api#useFieldArray'); } } const { name, type, value } = ref; const fieldRefAndValidationOptions = Object.assign({ ref }, options); const fields = fieldsRef.current; const isRadioOrCheckbox = isRadioOrCheckboxFunction(ref); const isFieldArray = isNameInFieldArray(fieldArrayNamesRef.current, name); const compareRef = (currentRef) => isWeb && (!isHTMLElement(ref) || currentRef === ref); let field = fields[name]; let isEmptyDefaultValue = true; let defaultValue; if (field && (isRadioOrCheckbox ? Array.isArray(field.options) && compact(field.options).find((option) => { return value === option.ref.value && compareRef(option.ref); }) : compareRef(field.ref))) { fields[name] = Object.assign(Object.assign({}, field), options); return; } if (type) { field = isRadioOrCheckbox ? Object.assign({ options: [ ...compact((field && field.options) || []), { ref, }, ], ref: { type, name } }, options) : Object.assign({}, fieldRefAndValidationOptions); } else { field = fieldRefAndValidationOptions; } fields[name] = field; const isEmptyUnmountFields = isUndefined(get(shallowFieldsStateRef.current, name)); if (!isEmptyObject(defaultValuesRef.current) || !isEmptyUnmountFields) { defaultValue = get(isEmptyUnmountFields ? defaultValuesRef.current : shallowFieldsStateRef.current, name); isEmptyDefaultValue = isUndefined(defaultValue); if (!isEmptyDefaultValue && !isFieldArray) { setFieldValue(name, defaultValue); } } if (!isEmptyObject(options)) { set(fieldsWithValidationRef.current, name, true); if (!isOnSubmit && readFormStateRef.current.isValid) { validateField(fieldsRef, isValidateAllFieldCriteria, field, shallowFieldsStateRef).then((error) => { const previousFormIsValid = formStateRef.current.isValid; isEmptyObject(error) ? set(validFieldsRef.current, name, true) : unset(validFieldsRef.current, name); previousFormIsValid !== isEmptyObject(error) && updateFormState(); }); } } if (shouldUnregister && !(isFieldArray && isEmptyDefaultValue)) { !isFieldArray && unset(formStateRef.current.dirtyFields, name); } if (type) {