informed
Version:
A lightweight framework and utility for building powerful forms in React applications
416 lines (382 loc) • 16.2 kB
JavaScript
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, toConsumableArray as _toConsumableArray, objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.js';
import React, { useContext, useState, useRef, useMemo, useEffect } from 'react';
import { useFieldApi } from './useFieldApi.js';
import { useRelevance } from './useRelevance.js';
import { useFieldState } from './useFieldState.js';
import { useFormController } from './useFormController.js';
import { useCursorPosition } from './useCursorPosition.js';
import { ScopeContext, MultistepStepContext, RelevanceContext } from '../Context.js';
import { uuidv4, generateValidationFunction, generateOnChange, generateOnBlur, generateOnFocus, generateValue } from '../utils.js';
import { Debug } from '../debug.js';
import { useUpdateEffect } from './useUpdateEffect.js';
import { useScope } from './useScope.js';
import { useFieldSubscription } from './useFieldSubscription.js';
var _excluded = ["id", "dir", "type", "name", "onBlur", "onChange", "onFocus", "onNativeChange", "onValueChange", "validate", "asyncValidate", "validateModified", "gatherData", "yupSchema", "multiple", "field", "keep", "keepState", "keepStateIfRelevant", "remember", "inputRef", "inputRefs", "relevant", "defaultValue", "initialValue", "autocomplete", "showErrorIfError", "showErrorIfTouched", "showErrorIfDirty", "formatter", "parser", "clean", "mask", "maintainCursor", "required", "noFalsy", "minimum", "maximum", "minLength", "maxLength", "pattern", "allowEmptyString", "emptyValue", "disabled", "gatherOnMount", "gatherOnBlur", "validateOnMount", "modifyOnMount", "validateOn", "maskOnBlur", "validateWhen", "formatterDependencies", "formController", "initialize", "errorMessage", "initializeValueIfPristine", "relevanceWhen", "relevanceDeps", "validateDeps", "evaluate", "evaluateWhen"];
var logger = Debug('informed:useField' + '\t');
/* ----------------------- useField ----------------------- */
var useField = function useField(_ref) {
var _ref2;
var id = _ref.id,
dir = _ref.dir,
type = _ref.type,
userName = _ref.name,
onBlur = _ref.onBlur,
onChange = _ref.onChange,
onFocus = _ref.onFocus,
onNativeChange = _ref.onNativeChange,
onValueChange = _ref.onValueChange,
validationFunc = _ref.validate,
asyncValidate = _ref.asyncValidate,
userValidateModified = _ref.validateModified,
gatherData = _ref.gatherData,
yupSchema = _ref.yupSchema,
multiple = _ref.multiple,
field = _ref.field,
keep = _ref.keep,
userKeepState = _ref.keepState,
userKeepStateIfRelevant = _ref.keepStateIfRelevant,
remember = _ref.remember,
inputRef = _ref.inputRef,
inputRefs = _ref.inputRefs,
relevant = _ref.relevant,
defaultValue = _ref.defaultValue,
userInitialValue = _ref.initialValue,
userAutocomplete = _ref.autocomplete,
userShowErrorIfError = _ref.showErrorIfError,
userShowErrorIfTouched = _ref.showErrorIfTouched,
userShowErrorIfDirty = _ref.showErrorIfDirty,
formatter = _ref.formatter,
parser = _ref.parser,
clean = _ref.clean,
mask = _ref.mask,
userMaintainCursor = _ref.maintainCursor,
required = _ref.required,
noFalsy = _ref.noFalsy,
minimum = _ref.minimum,
maximum = _ref.maximum,
minLength = _ref.minLength,
maxLength = _ref.maxLength,
pattern = _ref.pattern,
userAllowEmptyString = _ref.allowEmptyString,
emptyValue = _ref.emptyValue,
userDisabled = _ref.disabled,
gatherOnMount = _ref.gatherOnMount,
gatherOnBlur = _ref.gatherOnBlur,
userValidateOnMount = _ref.validateOnMount,
modifyOnMount = _ref.modifyOnMount,
userValidateOn = _ref.validateOn,
maskOnBlur = _ref.maskOnBlur,
_ref$validateWhen = _ref.validateWhen,
validateWhen = _ref$validateWhen === void 0 ? [] : _ref$validateWhen,
_ref$formatterDepende = _ref.formatterDependencies,
formatterDependencies = _ref$formatterDepende === void 0 ? [] : _ref$formatterDepende,
userFormController = _ref.formController,
initialize = _ref.initialize,
errorMessage = _ref.errorMessage,
initializeValueIfPristine = _ref.initializeValueIfPristine,
_ref$relevanceWhen = _ref.relevanceWhen,
relevanceWhen = _ref$relevanceWhen === void 0 ? [] : _ref$relevanceWhen,
_ref$relevanceDeps = _ref.relevanceDeps,
relevanceDeps = _ref$relevanceDeps === void 0 ? [] : _ref$relevanceDeps,
_ref$validateDeps = _ref.validateDeps,
validateDeps = _ref$validateDeps === void 0 ? [] : _ref$validateDeps;
_ref.evaluate;
_ref.evaluateWhen;
var userProps = _objectWithoutProperties(_ref, _excluded);
// For backwards compatability
var n = userName !== null && userName !== void 0 ? userName : field;
// Because it could be scoped
var name = useScope(n);
// Get scoped context
var scope = useContext(ScopeContext);
if (!name) {
console.warn('name is a required prop!!!!');
}
// Default to maintain cursor whenever formatter is passed
var maintainCursor = userMaintainCursor !== null && userMaintainCursor !== void 0 ? userMaintainCursor : !!formatter;
// Grab the form controller
var formController = userFormController !== null && userFormController !== void 0 ? userFormController : useFormController();
// Get any options
var autocomplete = userAutocomplete !== null && userAutocomplete !== void 0 ? userAutocomplete : formController.options.current.autocomplete;
var showErrorIfError = userShowErrorIfError !== null && userShowErrorIfError !== void 0 ? userShowErrorIfError : formController.options.current.showErrorIfError;
var showErrorIfTouched = userShowErrorIfTouched !== null && userShowErrorIfTouched !== void 0 ? userShowErrorIfTouched : formController.options.current.showErrorIfTouched;
var showErrorIfDirty = userShowErrorIfDirty !== null && userShowErrorIfDirty !== void 0 ? userShowErrorIfDirty : formController.options.current.showErrorIfDirty;
var validateOnMount = userValidateOnMount !== null && userValidateOnMount !== void 0 ? userValidateOnMount : formController.options.current.validateOnMount;
var validateOn = userValidateOn !== null && userValidateOn !== void 0 ? userValidateOn : formController.options.current.validateOn;
var keepState = userKeepState !== null && userKeepState !== void 0 ? userKeepState : formController.options.current.keepState;
var keepStateIfRelevant = userKeepStateIfRelevant !== null && userKeepStateIfRelevant !== void 0 ? userKeepStateIfRelevant : formController.options.current.keepStateIfRelevant;
var allowEmptyString = userAllowEmptyString !== null && userAllowEmptyString !== void 0 ? userAllowEmptyString : formController.options.current.allowEmptyStrings;
var validateModified = userValidateModified !== null && userValidateModified !== void 0 ? userValidateModified : formController.options.current.validateModified;
var disabled = (_ref2 = userDisabled !== null && userDisabled !== void 0 ? userDisabled : formController.disabled) !== null && _ref2 !== void 0 ? _ref2 : formController.options.current.disabled;
// For getting initialValue
var getInitialValue = function getInitialValue() {
var _ref3;
return (_ref3 = userInitialValue !== null && userInitialValue !== void 0 ? userInitialValue : formController.getInitialValue(name)) !== null && _ref3 !== void 0 ? _ref3 : defaultValue;
};
// Grab the initial value
var _useState = useState(function () {
return getInitialValue();
}),
_useState2 = _slicedToArray(_useState, 1),
initialValue = _useState2[0];
// Hook onto the field api
// Note: we already scoped above so we pass false here
var fieldApi = useFieldApi(name, false);
// For multistep
var inMultistep = useContext(MultistepStepContext);
// For relevance
var isRelevant = useRelevance({
name: name,
relevant: relevant,
relevanceWhen: relevanceWhen,
relevanceDeps: relevanceDeps
});
// If we live in `Relevant`
var relevantContext = useContext(RelevanceContext);
// Create ref
var internalRef = useRef(null);
var ref = React.useMemo(function () {
return inputRef || internalRef;
}, []);
// Create Id for field
var _useState3 = useState(function () {
return id || uuidv4();
}),
_useState4 = _slicedToArray(_useState3, 1),
fieldId = _useState4[0];
// Create reference function for if it changes
var validationFuncRef = useRef();
validationFuncRef.current = validationFunc;
// Generate validation function
var validate = useMemo(function () {
return generateValidationFunction(validationFuncRef, yupSchema, {
required: required,
noFalsy: noFalsy,
minimum: minimum,
maximum: maximum,
minLength: minLength,
maxLength: maxLength,
pattern: pattern,
getErrorMessage: function getErrorMessage(key) {
return formController.getErrorMessage(key, name);
},
validateModified: validateModified,
fieldApi: fieldApi,
formController: formController,
scope: scope,
name: n
});
}, [required, minimum, maximum, minLength, maxLength, pattern, noFalsy, n]);
// Create meta object
var meta = {
name: name,
type: type,
dir: dir,
onBlur: onBlur,
onChange: onChange,
onFocus: onFocus,
onNativeChange: onNativeChange,
initialValue: initialValue,
keep: keep,
remember: remember,
keepState: keepState,
keepStateIfRelevant: keepStateIfRelevant,
initializeValueIfPristine: initializeValueIfPristine,
fieldApi: fieldApi,
getInitialValue: getInitialValue,
formatter: formatter,
parser: parser,
clean: clean,
mask: mask,
validate: validate,
yupSchema: yupSchema,
validateOn: validateOn !== null && validateOn !== void 0 ? validateOn : 'blur',
validateOnMount: validateOnMount,
validateWhen: validateWhen,
showErrorIfError: showErrorIfError,
showErrorIfTouched: showErrorIfTouched,
showErrorIfDirty: showErrorIfDirty,
maskOnBlur: maskOnBlur,
asyncValidate: asyncValidate,
gatherData: gatherData,
initialize: initialize,
errorMessage: errorMessage,
allowEmptyString: allowEmptyString,
emptyValue: emptyValue,
gatherOnMount: gatherOnMount,
gatherOnBlur: gatherOnBlur,
fieldRef: ref,
modifyOnMount: modifyOnMount
};
var metaRef = useRef(meta);
metaRef.current = meta;
// Before we hook into field state initialize the field
useState(function () {
// Only initialize if relevant
if (isRelevant) {
var metaInfo = metaRef.current;
logger('Initialize', metaInfo.name);
formController.initialize(metaInfo.name, metaRef, false);
}
});
// Hook onto the field state
// Note: we already scoped above so we pass false here
// Note: its important this call is here ( below where we call initialize ) so the state can be set up before we start using it
var fieldState = useFieldState(name, false);
// Setup cursor position tracking
var _useCursorPosition = useCursorPosition({
value: fieldState.value,
inputRef: ref,
maintainCursor: maintainCursor,
inputRefs: inputRefs
}),
setCursor = _useCursorPosition.setCursor,
setCursorOffset = _useCursorPosition.setCursorOffset;
// Add to meta
metaRef.current.setCursorOffset = setCursorOffset;
metaRef.current.setCursor = setCursor;
// Register
useEffect(function () {
if (isRelevant) {
// We already initialized before the render so the input exists in the form state, we need to redo after the render
logger('Register', name, metaRef.current);
formController.register(name, metaRef);
logger('Second Initialize', name);
formController.initialize(name, metaRef, false);
}
return function () {
logger('De-Register', name, metaRef.current);
formController.deregister(name);
};
}, [name]);
// Cleanup on irrelivant
// Note: important to use the update effect so we dont call this on first render
useUpdateEffect(function () {
// The info may have changed, grab it from the ref
var metaInfo = metaRef.current;
if (!isRelevant && !keepState) {
logger('RELEVANT REMOVING', metaInfo.name);
formController.remove(metaInfo.name, metaInfo.keep, metaInfo);
logger('RELEVANT De-Register', metaInfo.name);
formController.deregister(metaInfo.name);
}
if (isRelevant) {
logger('RELEVANT register', metaInfo.name);
formController.register(metaInfo.name, metaRef);
logger('RELEVANT Initialize', metaInfo.name);
formController.initialize(metaInfo.name, metaRef);
}
}, [isRelevant]);
// Cleanup on un-mount
useEffect(function () {
return function () {
var keepIt = false;
// The info may have changed, grab it from the ref
var metaInfo = metaRef.current;
logger('CLEANUP REMOVING', metaInfo.name);
// Always keep it if this is passed
if (metaInfo.keepState) {
keepIt = true;
}
// Need to check relevance because we DONT
// want to keep if we are irrelivant
else if (relevantContext && !relevantContext.relevant()) {
keepIt = false;
}
// If we make it here we must be relevant so check keepStateIfRelevant
else if (keepStateIfRelevant) {
keepIt = true;
}
// If its a multistep then we also want to keep it
else if (inMultistep) {
keepIt = true;
}
if (!keepIt) {
formController.remove(metaInfo.name, metaInfo.keep, metaInfo);
}
};
}, []);
useUpdateEffect(function () {
formController.reformat(metaRef.current.name);
}, _toConsumableArray(formatterDependencies));
// Note im not adding this yet as I need to figure out how to solve issue with array fields when you remove 1 [0, 1, 2] and 2 becomes 1
// useUpdateEffect(
// () => {
// // If the form is pristine then reset it when we get new initial values !
// const pristine = fieldApi.getPristine();
// if (pristine) {
// fieldApi.reset();
// }
// },
// [userInitialValue, defaultValue]
// );
useFieldSubscription('field-value', [name], function (target) {
if (onValueChange) {
onValueChange(formController.getFieldState(target));
}
}, false // No scope as we are already scoped
);
useFieldSubscription('field-value', validateWhen, function (target) {
logger("revalidating for ".concat(metaRef.current.name, " because of ").concat(target));
formController.validateField(metaRef.current.name);
});
useUpdateEffect(function () {
logger("revalidating for ".concat(metaRef.current.name, " because of deps change"));
formController.validateField(metaRef.current.name);
}, validateDeps);
var render = function render(children) {
return isRelevant ? children : null;
};
var changeHandler = generateOnChange({
fieldType: type,
setValue: fieldApi.setValue,
multiple: multiple,
ref: ref
});
var blurHandler = generateOnBlur({
setTouched: fieldApi.setTouched
});
var focusHandler = generateOnFocus({
setFocused: fieldApi.setFocused
});
var hookedValue = generateValue({
fieldType: type,
maskedValue: fieldState.maskedValue,
multiple: userProps.multiple,
value: fieldState.value
});
var recombinedUserProps = _objectSpread2({
id: fieldId,
name: name,
// ref,
type: type,
dir: dir,
multiple: multiple,
autoComplete: autocomplete,
disabled: disabled,
required: required,
min: minimum,
max: maximum,
minLength: minLength,
maxLength: maxLength,
pattern: pattern
}, userProps);
// const order66 = new Date('2022-06-08T04:20Z');
// if (new Date() > order66) {
// recombinedUserProps.placeholder = 'Hello World';
// }
return {
fieldState: fieldState,
fieldApi: fieldApi,
userProps: recombinedUserProps,
informed: {
onChange: changeHandler,
onBlur: blurHandler,
onFocus: focusHandler,
value: hookedValue
},
ref: ref,
render: render
};
};
export { useField };