UNPKG

react-form-hooks

Version:

Create forms in react using hooks

499 lines (479 loc) 19.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var react = require('react'); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __spreadArrays() { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; } var handleFormSubmit = function (func) { return function (event) { if (event) { event.preventDefault(); } return func(); }; }; var isObject = function (value) { return value !== null && typeof value === 'object'; }; var isIndex = function (k) { return /^(\d+)$/.test(k); }; function getProperty(obj, key) { var parts = key .replace(/\[(\w+)]/g, '.$1') // convert indexes to properties .replace(/^\./, '') // strip a leading dot .split('.'); var curObj = obj; for (var i = 0; i < parts.length; ++i) { var k = parts[i]; if (curObj === undefined || curObj === null) { return undefined; } if (typeof curObj === 'string') { return undefined; } if (k in curObj) { curObj = curObj[k]; } else { return undefined; } } return curObj; } function setProperty(obj, key, value) { var path = key .replace(/\[(\w+)]/g, '.$1') // convert indexes to properties .replace(/^\./, '') // strip a leading dot .split('.'); var newObj = __assign({}, obj); var nested = newObj; for (var i = 0; nested != null && i < path.length; ++i) { var key_1 = path[i]; var newValue = value; if (i !== path.length - 1) { var objValue = nested[key_1]; if (isObject(objValue)) { newValue = Array.isArray(objValue) ? __spreadArrays(objValue) : __assign({}, objValue); } else { newValue = isIndex(path[i + 1]) ? [] : {}; } } nested[key_1] = newValue; nested = nested[key_1]; } return newObj; } // Simplified version of createStore from Redux to keep the size small function createStore(reducer, initialState) { var state = initialState; var listeners = []; function getState() { return state; } function dispatch(action) { // console.debug(`%cDISPATCH`, 'background:red;color:#fff', action) state = reducer(state, action); listeners.forEach(function (listener) { return listener(); }); return action; } function subscribe(listener) { // console.debug('%cSUBSCRIBE', 'color: green') listeners.push(listener); return function () { // console.debug('%cUNSUBSCRIBE', 'color: red') listeners = listeners.filter(function (l) { return l !== listener; }); }; } return { subscribe: subscribe, getState: getState, dispatch: dispatch }; } var ActionTypes; (function (ActionTypes) { ActionTypes["INIT_FORM_VALUES"] = "@@form/INIT_FORM_VALUES"; ActionTypes["CHANGE_FIELD_VALUE"] = "@@form/CHANGE_FIELD_VALUE"; ActionTypes["INIT_FIELD"] = "@@form/INIT_FIELD"; ActionTypes["DESTROY_FIELD"] = "@@form/DESTROY_FIELD"; ActionTypes["TOUCH_FIELD"] = "@@form/TOUCH_FIELD"; })(ActionTypes || (ActionTypes = {})); function fieldReducer(state, action) { switch (action.type) { case ActionTypes.INIT_FIELD: return { touched: false, dirty: false }; case ActionTypes.CHANGE_FIELD_VALUE: return __assign(__assign({}, state), { dirty: true }); case ActionTypes.TOUCH_FIELD: return __assign(__assign({}, state), { touched: true }); } } function fieldState(state, action) { var _a; switch (action.type) { case ActionTypes.INIT_FIELD: case ActionTypes.CHANGE_FIELD_VALUE: case ActionTypes.TOUCH_FIELD: return __assign(__assign({}, state), (_a = {}, _a[action.field] = fieldReducer(state[action.field], action), _a)); case ActionTypes.DESTROY_FIELD: var _b = state, _c = action.field, _ = _b[_c], remaining = __rest(_b, [typeof _c === "symbol" ? _c : _c + ""]); return remaining; default: return state; } } function formErrors(state, action) { var _a; switch (action.type) { case ActionTypes.INIT_FIELD: case ActionTypes.CHANGE_FIELD_VALUE: var newError = action.error || undefined; return state[action.field] === newError ? state : __assign(__assign({}, state), (_a = {}, _a[action.field] = newError, _a)); case ActionTypes.DESTROY_FIELD: var _b = state, _c = action.field, _ = _b[_c], remaining = __rest(_b, [typeof _c === "symbol" ? _c : _c + ""]); return remaining; default: return state; } } function formValues(state, action) { switch (action.type) { case ActionTypes.INIT_FORM_VALUES: return action.values; case ActionTypes.CHANGE_FIELD_VALUE: return setProperty(state, action.field, action.value); default: return state; } } function formReducer(state, action) { return { formValues: formValues(state.formValues, action), formErrors: formErrors(state.formErrors, action), fieldState: fieldState(state.fieldState, action), }; } function createForm(_a) { var _this = this; var initialValues = (_a === void 0 ? { initialValues: {} } : _a).initialValues; var store = createStore(formReducer, { formValues: initialValues, formErrors: {}, fieldState: {}, }); var fieldRefs = {}; // any = { [ref: symbol]: FieldOptions } var validateField = function (fieldId, value) { if (!fieldRefs[fieldId]) { return undefined; } var validate = Object.getOwnPropertySymbols(fieldRefs[fieldId]) .map(function (ref) { return fieldRefs[fieldId][ref]; }) .map(function (x) { return x.validate; }) .find(function (x) { return !!x; }) || (function () { return undefined; }); return validate(value); }; // State selectors var getFieldValue = function (fieldId) { var val = getProperty(store.getState().formValues, fieldId); return typeof val === 'undefined' ? null : val; }; var getFieldState = function (fieldId) { return (__assign(__assign({}, store.getState().fieldState[fieldId]), { error: store.getState().formErrors[fieldId], value: getFieldValue(fieldId) })); }; var getFormState = function () { var state = store.getState(); var anyTouched = Object.values(state.fieldState).some(function (field) { return field.touched; }); return { values: state.formValues, errors: state.formErrors, anyError: Object.values(state.formErrors).some(function (field) { return !!field; }), anyTouched: anyTouched, allTouched: anyTouched && Object.values(state.fieldState).every(function (field) { return field.touched; }), anyDirty: Object.values(state.fieldState).some(function (field) { return field.dirty; }), }; }; // Field handlers var setFieldOptions = function (fieldId, ref, opts) { if (opts === void 0) { opts = {}; } var validate = opts.validate; if (!fieldRefs[fieldId]) { fieldRefs[fieldId] = {}; } fieldRefs[fieldId][ref] = { validate: validate, }; }; var unsetFieldOptions = function (fieldId, ref) { delete fieldRefs[fieldId][ref]; }; var initializeField = function (fieldId) { var value = getFieldValue(fieldId); var error = validateField(fieldId, value); store.dispatch({ type: ActionTypes.INIT_FIELD, field: fieldId, error: error, }); }; var destroyField = function (fieldId) { store.dispatch({ type: ActionTypes.DESTROY_FIELD, field: fieldId, }); }; var changeFieldValue = function (fieldId, value) { store.dispatch({ type: ActionTypes.CHANGE_FIELD_VALUE, field: fieldId, value: value, error: validateField(fieldId, value), }); return value; }; var touchField = function (fieldId) { store.dispatch({ type: ActionTypes.TOUCH_FIELD, field: fieldId }); }; // Form handlers var resetFormValues = function (newInitialValues) { if (newInitialValues) { initialValues = newInitialValues; } store.dispatch({ type: ActionTypes.INIT_FORM_VALUES, values: initialValues, }); Object.keys(fieldRefs).forEach(function (fieldId) { initializeField(fieldId); }); }; var touchAll = function () { Object.keys(fieldRefs).forEach(function (fieldId) { touchField(fieldId); }); }; var submitHandler = function (fn) { return handleFormSubmit(function () { return __awaiter(_this, void 0, void 0, function () { var state; return __generator(this, function (_a) { state = getFormState(); if (state.anyError) { touchAll(); return [2 /*return*/]; } return [2 /*return*/, fn(state.values)]; }); }); }); }; var id = Math.random() .toString(36) .substring(7); var getId = function () { return id; }; return { getId: getId, subscribe: store.subscribe, formActions: { resetFormValues: resetFormValues, submitHandler: submitHandler, getFormState: getFormState, }, fieldActions: { setFieldOptions: setFieldOptions, unsetFieldOptions: unsetFieldOptions, initializeField: initializeField, destroyField: destroyField, changeFieldValue: changeFieldValue, touchField: touchField, getFieldState: getFieldState, }, }; } var emptyObj = {}; function useForm(opts) { if (opts === void 0) { opts = { initialValues: emptyObj }; } var form = react.useRef(null); var initialValues = opts.initialValues; function getForm() { if (form.current === null) { form.current = createForm(opts); } return form.current; } var prevInitialValues = react.useRef(initialValues); react.useEffect(function () { var resetFormValues = getForm().formActions.resetFormValues; if (prevInitialValues.current !== initialValues) { resetFormValues(initialValues); } prevInitialValues.current = initialValues; }, [initialValues]); return getForm(); } var hasOwnProperty = Object.prototype.hasOwnProperty; function shallowEqual(a, b) { if (Object.is(a, b)) { return true; } if (typeof a !== 'object' || !a || typeof b !== 'object' || !b) { return false; } var keysA = Object.keys(a); var keysB = Object.keys(b); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. for (var i = 0; i < keysA.length; i++) { var key = keysA[i]; if (!hasOwnProperty.call(b, keysA[i]) || a[key] !== b[key]) { return false; } } return true; } function useFormState(form, mapState) { if (mapState === void 0) { mapState = function (s) { return s; }; } if (!form) { throw new Error('react-form-hooks requires the form instance ' + 'created using useForm() to be passed to useFormState as 1st argument'); } var getFormState = form.formActions.getFormState; var getMappedFormState = react.useCallback(function () { return mapState(getFormState()); }, [ mapState, getFormState, ]); var _a = react.useState(getMappedFormState), formState = _a[0], setFormState = _a[1]; var prevFormState = react.useRef(formState); var updateState = react.useCallback(function () { var newState = getMappedFormState(); if (!shallowEqual(newState, prevFormState.current)) { setFormState(newState); prevFormState.current = newState; } }, [getMappedFormState, prevFormState, setFormState]); react.useEffect(function () { updateState(); return form.subscribe(updateState); }, [form, updateState]); return formState; } var defaultMapState = function (s) { return s; }; function useFieldState(form, fieldId, mapState, opts) { if (mapState === void 0) { mapState = defaultMapState; } if (opts === void 0) { opts = {}; } if (!form) { throw new Error('react-form-hooks requires the form instance ' + 'created using useForm() to be passed to useFieldState as 1st argument'); } if (!fieldId) { throw new Error('react-form-hooks requires the id of the field ' + 'to be passed to useFieldState as 2nd argument'); } var _a = form.fieldActions, setFieldOptions = _a.setFieldOptions, unsetFieldOptions = _a.unsetFieldOptions, initializeField = _a.initializeField, destroyField = _a.destroyField, getFieldState = _a.getFieldState; var validate = opts.validate; var getMappedFieldState = react.useCallback(function () { return mapState(getFieldState(fieldId)); }, [mapState, getFieldState, fieldId]); var ref = react.useRef(null); var getRef = function () { if (!ref.current) { ref.current = Symbol(); } return ref.current; }; var _b = react.useState(function () { setFieldOptions(fieldId, getRef(), opts); initializeField(fieldId); return getMappedFieldState(); }), fieldState = _b[0], setFieldState = _b[1]; var prevFieldState = react.useRef(fieldState); var updateState = react.useCallback(function () { var newState = getMappedFieldState(); if (!shallowEqual(newState, prevFieldState.current)) { setFieldState(newState); prevFieldState.current = newState; } }, [getMappedFieldState, prevFieldState, setFieldState]); react.useEffect(function () { setFieldOptions(fieldId, getRef(), { validate: validate }); return function () { return unsetFieldOptions(fieldId, getRef()); }; }, [form, fieldId, ref, validate]); react.useEffect(function () { initializeField(fieldId); return function () { return destroyField(fieldId); }; }, [form, fieldId]); react.useEffect(function () { updateState(); return form.subscribe(updateState); }, [form, fieldId, updateState]); return fieldState; } exports.useFieldState = useFieldState; exports.useForm = useForm; exports.useFormState = useFormState; //# sourceMappingURL=index.js.map