'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = require('react'); var isEqual = _interopDefault(require('react-fast-compare')); var deepmerge = _interopDefault(require('deepmerge')); var isPlainObject = _interopDefault(require('lodash/isPlainObject')); var clone = _interopDefault(require('lodash/clone')); var toPath = _interopDefault(require('lodash/toPath')); var invariant = _interopDefault(require('tiny-warning')); var hoistNonReactStatics = _interopDefault(require('hoist-non-react-statics')); var cloneDeep = _interopDefault(require('lodash/cloneDeep')); function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } /** @private is the value an empty array? */ var isEmptyArray = function isEmptyArray(value) { return Array.isArray(value) && value.length === 0; }; /** @private is the given object a Function? */ var isFunction = function isFunction(obj) { return typeof obj === 'function'; }; /** @private is the given object an Object? */ var isObject = function isObject(obj) { return obj !== null && typeof obj === 'object'; }; /** @private is the given object an integer? */ var isInteger = function isInteger(obj) { return String(Math.floor(Number(obj))) === obj; }; /** @private is the given object a string? */ var isString = function isString(obj) { return Object.prototype.toString.call(obj) === '[object String]'; }; /** @private is the given object a NaN? */ // eslint-disable-next-line no-self-compare var isNaN$1 = function isNaN(obj) { return obj !== obj; }; /** @private Does a React component have exactly 0 children? */ var isEmptyChildren = function isEmptyChildren(children) { return React.Children.count(children) === 0; }; /** @private is the given object/value a promise? */ var isPromise = function isPromise(value) { return isObject(value) && isFunction(value.then); }; /** @private is the given object/value a type of synthetic event? */ var isInputEvent = function isInputEvent(value) { return value && isObject(value) && isObject(value.target); }; /** * Same as document.activeElement but wraps in a try-catch block. In IE it is * not safe to call document.activeElement if there is nothing focused. * * The activeElement will be null only if the document or document body is not * yet defined. * * @param {?Document} doc Defaults to current document. * @return {Element | null} * @see https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/dom/getActiveElement.js */ function getActiveElement(doc) { doc = doc || (typeof document !== 'undefined' ? document : undefined); if (typeof doc === 'undefined') { return null; } try { return doc.activeElement || doc.body; } catch (e) { return doc.body; } } /** * Deeply get a value from an object via its path. */ function getIn(obj, key, def, p) { if (p === void 0) { p = 0; } var path = toPath(key); while (obj && p < path.length) { obj = obj[path[p++]]; } return obj === undefined ? def : obj; } /** * Deeply set a value from in object via it's path. If the value at `path` * has changed, return a shallow copy of obj with `value` set at `path`. * If `value` has not changed, return the original `obj`. * * Existing objects / arrays along `path` are also shallow copied. Sibling * objects along path retain the same internal js reference. Since new * objects / arrays are only created along `path`, we can test if anything * changed in a nested structure by comparing the object's reference in * the old and new object, similar to how russian doll cache invalidation * works. * * In earlier versions of this function, which used cloneDeep, there were * issues whereby settings a nested value would mutate the parent * instead of creating a new object. `clone` avoids that bug making a * shallow copy of the objects along the update path * so no object is mutated in place. * * Before changing this function, please read through the following * discussions. * * @see https://github.com/developit/linkstate * @see https://github.com/jaredpalmer/formik/pull/123 */ function setIn(obj, path, value) { var res = clone(obj); // this keeps inheritance when obj is a class var resVal = res; var i = 0; var pathArray = toPath(path); for (; i < pathArray.length - 1; i++) { var currentPath = pathArray[i]; var currentObj = getIn(obj, pathArray.slice(0, i + 1)); if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) { resVal = resVal[currentPath] = clone(currentObj); } else { var nextPath = pathArray[i + 1]; resVal = resVal[currentPath] = isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {}; } } // Return original object if new value is the same as current if ((i === 0 ? obj : resVal)[pathArray[i]] === value) { return obj; } if (value === undefined) { delete resVal[pathArray[i]]; } else { resVal[pathArray[i]] = value; } // If the path array has a single element, the loop did not run. // Deleting on `resVal` had no effect in this scenario, so we delete on the result instead. if (i === 0 && value === undefined) { delete res[pathArray[i]]; } return res; } /** * Recursively a set the same value for all keys and arrays nested object, cloning * @param object * @param value * @param visited * @param response */ function setNestedObjectValues(object, value, visited, response) { if (visited === void 0) { visited = new WeakMap(); } if (response === void 0) { response = {}; } for (var _i = 0, _Object$keys = Object.keys(object); _i < _Object$keys.length; _i++) { var k = _Object$keys[_i]; var val = object[k]; if (isObject(val)) { if (!visited.get(val)) { visited.set(val, true); // In order to keep array values consistent for both dot path and // bracket syntax, we need to check if this is an array so that // this will output { friends: [true] } and not { friends: { "0": true } } response[k] = Array.isArray(val) ? [] : {}; setNestedObjectValues(val, value, visited, response[k]); } } else { response[k] = value; } } return response; } var FormikContext = /*#__PURE__*/React.createContext(undefined); FormikContext.displayName = 'FormikContext'; var FormikProvider = FormikContext.Provider; var FormikConsumer = FormikContext.Consumer; function useFormikContext() { var formik = React.useContext(FormikContext); !!!formik ? invariant(false, "Formik context is undefined, please verify you are calling useFormikContext() as child of a component.") : void 0; return formik; } function formikReducer(state, msg) { switch (msg.type) { case 'SET_VALUES': return _extends({}, state, { values: msg.payload }); case 'SET_TOUCHED': return _extends({}, state, { touched: msg.payload }); case 'SET_ERRORS': if (isEqual(state.errors, msg.payload)) { return state; } return _extends({}, state, { errors: msg.payload }); case 'SET_STATUS': return _extends({}, state, { status: msg.payload }); case 'SET_ISSUBMITTING': return _extends({}, state, { isSubmitting: msg.payload }); case 'SET_ISVALIDATING': return _extends({}, state, { isValidating: msg.payload }); case 'SET_FIELD_VALUE': return _extends({}, state, { values: setIn(state.values, msg.payload.field, msg.payload.value) }); case 'SET_FIELD_TOUCHED': return _extends({}, state, { touched: setIn(state.touched, msg.payload.field, msg.payload.value) }); case 'SET_FIELD_ERROR': return _extends({}, state, { errors: setIn(state.errors, msg.payload.field, msg.payload.value) }); case 'RESET_FORM': return _extends({}, state, msg.payload); case 'SET_FORMIK_STATE': return msg.payload(state); case 'SUBMIT_ATTEMPT': return _extends({}, state, { touched: setNestedObjectValues(state.values, true), isSubmitting: true, submitCount: state.submitCount + 1 }); case 'SUBMIT_FAILURE': return _extends({}, state, { isSubmitting: false }); case 'SUBMIT_SUCCESS': return _extends({}, state, { isSubmitting: false }); default: return state; } } // Initial empty states // objects var emptyErrors = {}; var emptyTouched = {}; function useFormik(_ref) { var _ref$validateOnChange = _ref.validateOnChange, validateOnChange = _ref$validateOnChange === void 0 ? true : _ref$validateOnChange, _ref$validateOnBlur = _ref.validateOnBlur, validateOnBlur = _ref$validateOnBlur === void 0 ? true : _ref$validateOnBlur, _ref$validateOnMount = _ref.validateOnMount, validateOnMount = _ref$validateOnMount === void 0 ? false : _ref$validateOnMount, isInitialValid = _ref.isInitialValid, _ref$enableReinitiali = _ref.enableReinitialize, enableReinitialize = _ref$enableReinitiali === void 0 ? false : _ref$enableReinitiali, onSubmit = _ref.onSubmit, rest = _objectWithoutPropertiesLoose(_ref, ["validateOnChange", "validateOnBlur", "validateOnMount", "isInitialValid", "enableReinitialize", "onSubmit"]); var props = _extends({ validateOnChange: validateOnChange, validateOnBlur: validateOnBlur, validateOnMount: validateOnMount, onSubmit: onSubmit }, rest); var initialValues = React.useRef(props.initialValues); var initialErrors = React.useRef(props.initialErrors || emptyErrors); var initialTouched = React.useRef(props.initialTouched || emptyTouched); var initialStatus = React.useRef(props.initialStatus); var isMounted = React.useRef(false); var fieldRegistry = React.useRef({}); { // eslint-disable-next-line react-hooks/rules-of-hooks React.useEffect(function () { !(typeof isInitialValid === 'undefined') ? invariant(false, 'isInitialValid has been deprecated and will be removed in future versions of Formik. Please use initialErrors or validateOnMount instead.') : void 0; // eslint-disable-next-line }, []); } React.useEffect(function () { isMounted.current = true; return function () { isMounted.current = false; }; }, []); var _React$useReducer = React.useReducer(formikReducer, { values: props.initialValues, errors: props.initialErrors || emptyErrors, touched: props.initialTouched || emptyTouched, status: props.initialStatus, isSubmitting: false, isValidating: false, submitCount: 0 }), state = _React$useReducer[0], dispatch = _React$useReducer[1]; var runValidateHandler = React.useCallback(function (values, field) { return new Promise(function (resolve, reject) { var maybePromisedErrors = props.validate(values, field); if (maybePromisedErrors == null) { // use loose null check here on purpose resolve(emptyErrors); } else if (isPromise(maybePromisedErrors)) { maybePromisedErrors.then(function (errors) { resolve(errors || emptyErrors); }, function (actualException) { { console.warn("Warning: An unhandled error was caught during validation in ", actualException); } reject(actualException); }); } else { resolve(maybePromisedErrors); } }); }, [props.validate]); /** * Run validation against a Yup schema and optionally run a function if successful */ var runValidationSchema = React.useCallback(function (values, field) { var validationSchema = props.validationSchema; var schema = isFunction(validationSchema) ? validationSchema(field) : validationSchema; var promise = field && schema.validateAt ? schema.validateAt(field, values) : validateYupSchema(values, schema); return new Promise(function (resolve, reject) { promise.then(function () { resolve(emptyErrors); }, function (err) { // Yup will throw a validation error if validation fails. We catch those and // resolve them into Formik errors. We can sniff if something is a Yup error // by checking error.name. // @see https://github.com/jquense/yup#validationerrorerrors-string--arraystring-value-any-path-string if (err.name === 'ValidationError') { resolve(yupToFormErrors(err)); } else { // We throw any other errors { console.warn("Warning: An unhandled error was caught during validation in ", err); } reject(err); } }); }); }, [props.validationSchema]); var runSingleFieldLevelValidation = React.useCallback(function (field, value) { return new Promise(function (resolve) { return resolve(fieldRegistry.current[field].validate(value)); }); }, []); var runFieldLevelValidations = React.useCallback(function (values) { var fieldKeysWithValidation = Object.keys(fieldRegistry.current).filter(function (f) { return isFunction(fieldRegistry.current[f].validate); }); // Construct an array with all of the field validation functions var fieldValidations = fieldKeysWithValidation.length > 0 ? fieldKeysWithValidation.map(function (f) { return runSingleFieldLevelValidation(f, getIn(values, f)); }) : [Promise.resolve('DO_NOT_DELETE_YOU_WILL_BE_FIRED')]; // use special case ;) return Promise.all(fieldValidations).then(function (fieldErrorsList) { return fieldErrorsList.reduce(function (prev, curr, index) { if (curr === 'DO_NOT_DELETE_YOU_WILL_BE_FIRED') { return prev; } if (curr) { prev = setIn(prev, fieldKeysWithValidation[index], curr); } return prev; }, {}); }); }, [runSingleFieldLevelValidation]); // Run all validations and return the result var runAllValidations = React.useCallback(function (values) { return Promise.all([runFieldLevelValidations(values), props.validationSchema ? runValidationSchema(values) : {}, props.validate ? runValidateHandler(values) : {}]).then(function (_ref2) { var fieldErrors = _ref2[0], schemaErrors = _ref2[1], validateErrors = _ref2[2]; var combinedErrors = deepmerge.all([fieldErrors, schemaErrors, validateErrors], { arrayMerge: arrayMerge }); return combinedErrors; }); }, [props.validate, props.validationSchema, runFieldLevelValidations, runValidateHandler, runValidationSchema]); // Run all validations methods and update state accordingly var validateFormWithHighPriority = useEventCallback(function (values) { if (values === void 0) { values = state.values; } dispatch({ type: 'SET_ISVALIDATING', payload: true }); return runAllValidations(values).then(function (combinedErrors) { if (!!isMounted.current) { dispatch({ type: 'SET_ISVALIDATING', payload: false }); dispatch({ type: 'SET_ERRORS', payload: combinedErrors }); } return combinedErrors; }); }); React.useEffect(function () { if (validateOnMount && isMounted.current === true && isEqual(initialValues.current, props.initialValues)) { validateFormWithHighPriority(initialValues.current); } }, [validateOnMount, validateFormWithHighPriority]); var resetForm = React.useCallback(function (nextState) { var values = nextState && nextState.values ? nextState.values : initialValues.current; var errors = nextState && nextState.errors ? nextState.errors : initialErrors.current ? initialErrors.current : props.initialErrors || {}; var touched = nextState && nextState.touched ? nextState.touched : initialTouched.current ? initialTouched.current : props.initialTouched || {}; var status = nextState && nextState.status ? nextState.status : initialStatus.current ? initialStatus.current : props.initialStatus; initialValues.current = values; initialErrors.current = errors; initialTouched.current = touched; initialStatus.current = status; var dispatchFn = function dispatchFn() { dispatch({ type: 'RESET_FORM', payload: { isSubmitting: !!nextState && !!nextState.isSubmitting, errors: errors, touched: touched, status: status, values: values, isValidating: !!nextState && !!nextState.isValidating, submitCount: !!nextState && !!nextState.submitCount && typeof nextState.submitCount === 'number' ? nextState.submitCount : 0 } }); }; if (props.onReset) { var maybePromisedOnReset = props.onReset(state.values, imperativeMethods); if (isPromise(maybePromisedOnReset)) { maybePromisedOnReset.then(dispatchFn); } else { dispatchFn(); } } else { dispatchFn(); } }, [props.initialErrors, props.initialStatus, props.initialTouched]); React.useEffect(function () { if (isMounted.current === true && !isEqual(initialValues.current, props.initialValues)) { if (enableReinitialize) { initialValues.current = props.initialValues; resetForm(); } if (validateOnMount) { validateFormWithHighPriority(initialValues.current); } } }, [enableReinitialize, props.initialValues, resetForm, validateOnMount, validateFormWithHighPriority]); React.useEffect(function () { if (enableReinitialize && isMounted.current === true && !isEqual(initialErrors.current, props.initialErrors)) { initialErrors.current = props.initialErrors || emptyErrors; dispatch({ type: 'SET_ERRORS', payload: props.initialErrors || emptyErrors }); } }, [enableReinitialize, props.initialErrors]); React.useEffect(function () { if (enableReinitialize && isMounted.current === true && !isEqual(initialTouched.current, props.initialTouched)) { initialTouched.current = props.initialTouched || emptyTouched; dispatch({ type: 'SET_TOUCHED', payload: props.initialTouched || emptyTouched }); } }, [enableReinitialize, props.initialTouched]); React.useEffect(function () { if (enableReinitialize && isMounted.current === true && !isEqual(initialStatus.current, props.initialStatus)) { initialStatus.current = props.initialStatus; dispatch({ type: 'SET_STATUS', payload: props.initialStatus }); } }, [enableReinitialize, props.initialStatus, props.initialTouched]); var validateField = useEventCallback(function (name) { // This will efficiently validate a single field by avoiding state // changes if the validation function is synchronous. It's different from // what is called when using validateForm. if (fieldRegistry.current[name] && isFunction(fieldRegistry.current[name].validate)) { var value = getIn(state.values, name); var maybePromise = fieldRegistry.current[name].validate(value); if (isPromise(maybePromise)) { // Only flip isValidating if the function is async. dispatch({ type: 'SET_ISVALIDATING', payload: true }); return maybePromise.then(function (x) { return x; }).then(function (error) { dispatch({ type: 'SET_FIELD_ERROR', payload: { field: name, value: error } }); dispatch({ type: 'SET_ISVALIDATING', payload: false }); }); } else { dispatch({ type: 'SET_FIELD_ERROR', payload: { field: name, value: maybePromise } }); return Promise.resolve(maybePromise); } } else if (props.validationSchema) { dispatch({ type: 'SET_ISVALIDATING', payload: true }); return runValidationSchema(state.values, name).then(function (x) { return x; }).then(function (error) { dispatch({ type: 'SET_FIELD_ERROR', payload: { field: name, value: error[name] } }); dispatch({ type: 'SET_ISVALIDATING', payload: false }); }); } return Promise.resolve(); }); var registerField = React.useCallback(function (name, _ref3) { var validate = _ref3.validate; fieldRegistry.current[name] = { validate: validate }; }, []); var unregisterField = React.useCallback(function (name) { delete fieldRegistry.current[name]; }, []); var setTouched = useEventCallback(function (touched, shouldValidate) { dispatch({ type: 'SET_TOUCHED', payload: touched }); var willValidate = shouldValidate === undefined ? validateOnBlur : shouldValidate; return willValidate ? validateFormWithHighPriority(state.values) : Promise.resolve(); }); var setErrors = React.useCallback(function (errors) { dispatch({ type: 'SET_ERRORS', payload: errors }); }, []); var setValues = useEventCallback(function (values, shouldValidate) { var resolvedValues = isFunction(values) ? values(state.values) : values; dispatch({ type: 'SET_VALUES', payload: resolvedValues }); var willValidate = shouldValidate === undefined ? validateOnChange : shouldValidate; return willValidate ? validateFormWithHighPriority(resolvedValues) : Promise.resolve(); }); var setFieldError = React.useCallback(function (field, value) { dispatch({ type: 'SET_FIELD_ERROR', payload: { field: field, value: value } }); }, []); var setFieldValue = useEventCallback(function (field, value, shouldValidate) { dispatch({ type: 'SET_FIELD_VALUE', payload: { field: field, value: value } }); var willValidate = shouldValidate === undefined ? validateOnChange : shouldValidate; return willValidate ? validateFormWithHighPriority(setIn(state.values, field, value)) : Promise.resolve(); }); var executeChange = React.useCallback(function (eventOrTextValue, maybePath) { // By default, assume that the first argument is a string. This allows us to use // handleChange with React Native and React Native Web's onChangeText prop which // provides just the value of the input. var field = maybePath; var val = eventOrTextValue; var parsed; // If the first argument is not a string though, it has to be a synthetic React Event (or a fake one), // so we handle like we would a normal HTML change event. if (!isString(eventOrTextValue)) { // If we can, persist the event // @see https://reactjs.org/docs/events.html#event-pooling if (eventOrTextValue.persist) { eventOrTextValue.persist(); } var target = eventOrTextValue.target ? eventOrTextValue.target : eventOrTextValue.currentTarget; var type = target.type, name = target.name, id = target.id, value = target.value, checked = target.checked, outerHTML = target.outerHTML, options = target.options, multiple = target.multiple; field = maybePath ? maybePath : name ? name : id; if (!field && "development" !== "production") { warnAboutMissingIdentifier({ htmlContent: outerHTML, documentationAnchorLink: 'handlechange-e-reactchangeeventany--void', handlerName: 'handleChange' }); } val = /number|range/.test(type) ? (parsed = parseFloat(value), isNaN(parsed) ? '' : parsed) : /checkbox/.test(type) // checkboxes ? getValueForCheckbox(getIn(state.values, field), checked, value) : options && multiple //