Files
payloadcms/src/admin/components/forms/useField/index.tsx
2022-01-07 14:21:36 -05:00

135 lines
3.4 KiB
TypeScript

import {
useCallback, useEffect, useState,
} from 'react';
import { useFormProcessing, useFormSubmitted, useFormModified, useForm } from '../Form/context';
import useDebounce from '../../../hooks/useDebounce';
import { Options, FieldType } from './types';
const useField = <T extends unknown>(options: Options): FieldType<T> => {
const {
path,
validate,
enableDebouncedValue,
disableFormData,
ignoreWhileFlattening,
stringify,
condition,
} = options;
const formContext = useForm();
const submitted = useFormSubmitted();
const processing = useFormProcessing();
const modified = useFormModified();
const {
dispatchFields,
getField,
setModified,
} = formContext || {};
const [internalValue, setInternalValue] = useState(undefined);
// Debounce internal values to update form state only every 60ms
const debouncedValue = useDebounce(internalValue, 120);
// Get field by path
const field = getField(path);
const initialValue = field?.initialValue;
// Valid could be a string equal to an error message
const valid = (field && typeof field.valid === 'boolean') ? field.valid : true;
const showError = valid === false && submitted;
// Method to send update field values from field component(s)
// Should only be used internally
const sendField = useCallback(async (valueToSend) => {
const fieldToDispatch = {
path,
stringify,
disableFormData,
ignoreWhileFlattening,
initialValue,
validate,
condition,
value: valueToSend,
valid: false,
errorMessage: undefined,
};
const validationResult = typeof validate === 'function' ? await validate(valueToSend) : true;
if (typeof validationResult === 'string') {
fieldToDispatch.errorMessage = validationResult;
fieldToDispatch.valid = false;
} else {
fieldToDispatch.valid = validationResult;
}
if (typeof dispatchFields === 'function') {
dispatchFields(fieldToDispatch);
}
}, [
path,
dispatchFields,
validate,
disableFormData,
ignoreWhileFlattening,
initialValue,
stringify,
condition,
]);
// Method to return from `useField`, used to
// update internal field values from field component(s)
// as fast as they arrive. NOTE - this method is NOT debounced
const setValue = useCallback((e, modifyForm = true) => {
const val = (e && e.target) ? e.target.value : e;
if ((!ignoreWhileFlattening && !modified) && modifyForm) {
if (typeof setModified === 'function') {
setModified(true);
}
}
setInternalValue(val);
}, [
setModified,
modified,
ignoreWhileFlattening,
]);
useEffect(() => {
setInternalValue(initialValue);
}, [initialValue]);
// The only time that the FORM value should be updated
// is when the debounced value updates. So, when the debounced value updates,
// send it up to the form
const valueToSend = enableDebouncedValue ? debouncedValue : internalValue;
useEffect(() => {
if (field?.value !== valueToSend && valueToSend !== undefined) {
sendField(valueToSend);
}
}, [
path,
valueToSend,
sendField,
field,
]);
return {
...options,
showError,
errorMessage: field?.errorMessage,
value: internalValue,
formSubmitted: submitted,
formProcessing: processing,
setValue,
initialValue,
};
};
export default useField;