UNPKG

@ehfuse/forma

Version:

Advanced React state management library with individual field subscriptions - supports both forms and general state management with useFormaState

313 lines 13.1 kB
"use strict"; /** * useForm.ts * * Forma - 고급 폼 상태 관리 훅 / Advanced form state management hook * 개별 필드 구독과 성능 최적화를 제공하는 핵심 훅 * Core hook providing individual field subscriptions and performance optimization * * @license MIT License * @copyright 2025 KIM YOUNG JIN (Kim Young Jin) * @author KIM YOUNG JIN (ehfuse@gmail.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.useForm = useForm; const useFormaState_1 = require("./useFormaState"); const utils_1 = require("../utils"); const react_1 = require("react"); function useForm(props = {}) { const { initialValues = {}, onSubmit, onValidate, onComplete, actions: userActions, watch, _externalStore, } = props; // 초기값 안정화: 첫 번째 렌더링에서만 초기값을 고정 // Stabilize initial values: fix initial values only on first render const stableInitialValues = (0, react_1.useRef)(null); if (!stableInitialValues.current) { stableInitialValues.current = initialValues; } // useFormaState를 기반으로 사용 / Use useFormaState as foundation const fieldState = (0, useFormaState_1.useFormaState)(stableInitialValues.current, { _externalStore, watch, }); // 폼 특정 상태 관리 / Form-specific state management const [isSubmitting, setIsSubmitting] = (0, react_1.useState)(false); const [isValidating, setIsValidating] = (0, react_1.useState)(false); // 폼이 수정되었는지 확인 / Check if form is modified // Store의 변경 사항을 추적하여 효율적으로 감지 const [isModified, setIsModified] = (0, react_1.useState)(false); (0, react_1.useEffect)(() => { const unsubscribe = fieldState._store.subscribeGlobal(() => { setIsModified(fieldState._store.isModified()); }); // 초기 상태 설정 setIsModified(fieldState._store.isModified()); return unsubscribe; }, [fieldState._store]); /** * 통합 폼 변경 핸들러 / Unified form change handler * MUI Select, TextField, DatePicker 등 모든 컴포넌트 지원 * Supports all components: MUI Select, TextField, DatePicker, etc. * * 두 가지 호출 방식 지원 / Supports two calling patterns: * 1. handleFormChange(event) - 이벤트 객체 전달 * 2. handleFormChange(name, value) - 직접 name, value 전달 */ const handleFormChange = (0, react_1.useCallback)((eventOrName, directValue) => { let name; let value; // (name, value) 형태로 직접 호출된 경우 if (typeof eventOrName === "string") { name = eventOrName; value = directValue; } else { // 이벤트 객체로 호출된 경우 const target = eventOrName.target; if (!target || !target.name) return; const { type, checked } = target; name = target.name; value = target.value; // 체크박스 처리 / Checkbox handling if (type === "checkbox") { value = checked; } // 숫자 타입 처리 / Number type handling else if (type === "number") { value = Number(value); } } // DatePicker 처리 (Dayjs 객체) / DatePicker handling (Dayjs object) if (value && typeof value === "object" && value.format) { value = value.format("YYYY-MM-DD"); } // null 값 처리 / Null value handling else if (value === null) { value = undefined; } fieldState.setValue(name, value); }, [fieldState.setValue]); /** * DatePicker 전용 변경 핸들러 / DatePicker-specific change handler * 간편한 사용을 위한 커링 함수 / Curried function for convenient usage */ const handleDatePickerChange = (0, react_1.useCallback)((fieldName) => { return (value, _context) => { let newValue = value; // DatePicker 처리 (Dayjs 객체) / DatePicker handling (Dayjs object) if (value && typeof value === "object" && value.format) { newValue = value.format("YYYY-MM-DD"); } else if (value === null) { newValue = undefined; } fieldState.setValue(fieldName, newValue); }; }, [fieldState.setValue]); /** * 개별 필드 값 설정 / Set individual field value */ const setFormValue = (0, react_1.useCallback)((name, value) => { let processedValue = value; // DatePicker에서 오는 Dayjs 객체 처리 / Handle Dayjs object from DatePicker if (value && typeof value === "object" && value.format) { processedValue = value.format("YYYY-MM-DD"); } else if (value === null) { processedValue = undefined; } fieldState.setValue(name, processedValue); }, [fieldState.setValue]); /** * 전체 폼 값 설정 / Set all form values */ const setFormValues = (0, react_1.useCallback)((newValues) => { fieldState.setValues(newValues); }, [fieldState.setValues]); /** * 초기값 재설정 / Reset initial values */ const setInitialFormValues = (0, react_1.useCallback)((newInitialValues) => { stableInitialValues.current = newInitialValues; fieldState._store.setInitialValues(newInitialValues); }, [fieldState._store]); /** * 구독 없이 현재 값만 가져오기 / Get current value without subscription */ const getFormValue = (0, react_1.useCallback)((fieldName) => { return fieldState._store.getValue(fieldName); }, [fieldState._store]); /** * 모든 폼 값 가져오기 / Get all form values */ const getFormValues = (0, react_1.useCallback)(() => { return fieldState.getValues(); }, [fieldState.getValues]); /** * 개별 필드 구독 Hook / Individual field subscription hook * 해당 필드가 변경될 때만 컴포넌트가 리렌더링됩니다 * Component re-renders only when the specific field changes * * NOTE: This is NOT a useCallback - it directly delegates to fieldState.useValue */ const useFormValue = (fieldName) => { const value = fieldState.useValue(fieldName); // undefined를 빈 문자열로 변환하여 MUI TextField와 호환성 확보 return value === undefined ? "" : value; }; /** * 폼 검증 / Form validation */ const validateForm = (0, react_1.useCallback)(async (valuesToValidate) => { if (!onValidate) return true; setIsValidating(true); const currentValues = valuesToValidate || fieldState.getValues(); try { return await onValidate(currentValues); } catch (error) { (0, utils_1.devError)("Validation error:", error); return false; } finally { setIsValidating(false); } }, [onValidate, fieldState.getValues]); /** * 폼 초기화 / Reset form */ const resetForm = (0, react_1.useCallback)(() => { fieldState.reset(); setIsSubmitting(false); setIsValidating(false); }, [fieldState.reset]); /** * 폼 제출 / Submit form */ const submit = (0, react_1.useCallback)(async (e) => { if (e) e.preventDefault(); const currentValues = fieldState.getValues(); if (!(await validateForm(currentValues))) { return false; } setIsSubmitting(true); try { if (onSubmit) { const result = await onSubmit(currentValues); // onSubmit이 boolean을 반환하면 해당 값 사용, 아니면 true로 간주 if (result === false) { return false; } } if (onComplete) { onComplete(currentValues); } return true; } catch (error) { (0, utils_1.devError)("Form submission error:", error); return false; } finally { setIsSubmitting(false); } }, [onSubmit, onComplete, validateForm, fieldState.getValues]); // Actions 바인딩 - context와 함께 사용할 수 있도록 / Actions binding - to use with context const boundActions = (0, react_1.useMemo)(() => { if (!userActions) return {}; // 배열이면 병합, 객체면 그대로 사용 const mergedActions = (0, utils_1.mergeActions)(userActions); if (!mergedActions) return {}; const bound = {}; const context = { get values() { return fieldState.getValues(); }, getValue: (fieldName) => fieldState._store.getValue(fieldName), setValue: (fieldName, value) => fieldState._store.setValue(fieldName, value), setValues: (values) => { const currentValues = fieldState._store.getValues(); const newValues = { ...currentValues, ...values }; fieldState._store.setValues(newValues); }, reset: resetForm, submit, validate: validateForm, actions: bound, // 순환 참조 / circular reference }; // 모든 action 함수들을 context와 바인딩 / Bind all action functions with context Object.keys(mergedActions).forEach((key) => { const action = mergedActions[key]; if (typeof action === "function") { bound[key] = (...args) => action(context, ...args); } }); return bound; }, [userActions, fieldState, resetForm, submit, validateForm]); return (0, react_1.useMemo)(() => ({ // 상태 / State isSubmitting, isValidating, isModified, // 값 가져오기 / Get values useFormValue, // Hook - 구독 있음 (성능 최적화) / with subscription (performance optimized) getFormValue, // 함수 - 구독 없음 (현재 값만) / function - no subscription (current value only) getFormValues, // 함수 - 모든 값 / function - all values // 값 설정 / Set values setFormValue, // 개별 필드 설정 / set individual field setFormValues, // 전체 값 설정 / set all values setInitialFormValues, // 초기값 재설정 / reset initial values // 이벤트 핸들러 / Event handlers handleFormChange, // 폼 요소 onChange (MUI 완전 호환) / form element onChange (fully MUI compatible) handleDatePickerChange, // DatePicker 전용 onChange / DatePicker-specific onChange // 폼 액션 / Form actions submit, // 폼 제출 / submit form resetForm, // 폼 초기화 / reset form validateForm, // 폼 검증 / validate form // Actions actions: boundActions, // 사용자 정의 actions / user-defined actions // 호환성 / Compatibility values: fieldState.getValues(), // 호환성을 위한 values 객체 (비권장) / Values object for compatibility (not recommended) // 고급 사용 / Advanced usage _store: fieldState._store, // 직접 store 접근용 / direct store access }), [ isSubmitting, isValidating, isModified, useFormValue, getFormValue, getFormValues, setFormValue, setFormValues, setInitialFormValues, handleFormChange, handleDatePickerChange, submit, resetForm, validateForm, boundActions, // actions 의존성 추가 fieldState._store, // Store 의존성으로 대체 ]); } //# sourceMappingURL=useForm.js.map