UNPKG

informed

Version:

A lightweight framework and utility for building powerful forms in React applications

416 lines (382 loc) 16.2 kB
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 };