UNPKG

@ehfuse/forma

Version:

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

230 lines (227 loc) 11.6 kB
"use strict"; /** * useGlobalForm.ts * * Forma - 글로벌 폼 상태 관리 훅 / Global form state management hook * 여러 컴포넌트 간 폼 상태 공유를 위한 확장 훅 * Extended hook for sharing form state across multiple components * * @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.useGlobalForm = useGlobalForm; const react_1 = require("react"); const useForm_1 = require("./useForm"); const GlobalFormaContext_1 = require("../contexts/GlobalFormaContext"); const utils_1 = require("../utils"); /** * 글로벌 폼 상태 관리 훅 / Global form state management hook * * 여러 컴포넌트 간 폼 데이터를 공유하기 위한 훅입니다 * Hook for sharing form data across multiple components * * 데이터 공유에만 집중하며, 초기값 설정과 제출/검증 로직은 각 컴포넌트에서 직접 처리합니다 * Focuses only on data sharing; initial values and submission/validation logic handled by individual components * * @template T 폼 데이터의 타입 / Form data type * @param propsOrFormId 글로벌 폼 설정 옵션 또는 formId 문자열 / Global form configuration options or formId string * @returns 글로벌 폼 관리 API 객체 / Global form management API object */ function useGlobalForm(propsOrFormId) { // 문자열로 전달된 경우 props 객체로 변환 const props = typeof propsOrFormId === "string" ? { formId: propsOrFormId } : propsOrFormId; const { formId, initialValues, autoCleanup = true, onSubmit, onValidate, onComplete, actions, watch, } = props; const context = (0, react_1.useContext)(GlobalFormaContext_1.GlobalFormaContext); // Context가 제대로 설정되지 않았을 때 명확한 에러 표시 // Show clear error when Context is not properly configured if (!context || !context.getOrCreateStore) { // 페이지에 에러가 표시되도록 컴포넌트 렌더링을 방해하는 에러를 던짐 // Throw error that prevents component rendering so error shows on page const errorMessage = ` 🚨 GlobalFormaProvider 설정 오류 | Configuration Error GlobalFormaProvider가 App.tsx에 설정되지 않았습니다! GlobalFormaProvider is not configured in App.tsx! 해결 방법 | Solution: 1. App.tsx 파일에서 GlobalFormaProvider로 컴포넌트를 감싸주세요. 2. import { GlobalFormaProvider } from '@/forma'; 3. <GlobalFormaProvider><YourApp /></GlobalFormaProvider> Details: GlobalFormaContext must be used within GlobalFormaProvider (formId: ${formId}) `.trim(); throw new Error(errorMessage); } const { getOrCreateStore, incrementRef, decrementRef, validateAndStoreAutoCleanupSetting, registerHandlers, getHandlers, registerActions, getActions, } = context; // autoCleanup 설정 일관성 검증 validateAndStoreAutoCleanupSetting(formId, autoCleanup); // 글로벌 스토어 가져오기 또는 생성 / Get or create global store const store = getOrCreateStore(formId); // 핸들러가 제공되면 글로벌에 동기적으로 등록 / Register handlers to global synchronously if provided if (onSubmit || onValidate || onComplete) { registerHandlers(formId, { onSubmit, onValidate, onComplete, }); } // actions가 제공되면 글로벌에 동기적으로 등록 / Register actions to global synchronously if provided if (actions) { const mergedActions = (0, utils_1.mergeActions)(actions); if (mergedActions) { registerActions(formId, mergedActions); } } // 글로벌 핸들러 가져오기 / Get global handlers const globalHandlers = getHandlers(formId); // 글로벌 actions 가져오기 / Get global actions const globalActions = getActions(formId); // 로컬 핸들러가 없으면 글로벌 핸들러 사용 / Use global handlers if local handlers are not provided const effectiveOnSubmit = onSubmit || globalHandlers?.onSubmit; const effectiveOnValidate = onValidate || globalHandlers?.onValidate; const effectiveOnComplete = onComplete || globalHandlers?.onComplete; // 로컬 actions가 없으면 글로벌 actions 사용 / Use global actions if local actions are not provided const effectiveActions = actions || globalActions; // useForm에 외부 스토어와 핸들러들 전달 / Pass external store and handlers to useForm const form = (0, useForm_1.useForm)({ initialValues: initialValues || {}, _externalStore: store, onSubmit: effectiveOnSubmit, onValidate: effectiveOnValidate, onComplete: effectiveOnComplete, actions: effectiveActions, }); // 초기값이 있고 스토어가 비어있다면 초기값 설정 (올바른 방법으로) // Set initial values if provided and store is empty (using proper method) (0, react_1.useEffect)(() => { if (initialValues && Object.keys(store.getValues()).length === 0) { form.setInitialFormValues(initialValues); } }, [formId, initialValues, store, form.setInitialFormValues]); // form 전체 대신 setInitialFormValues만 의존성으로 // 참조 카운팅을 통한 자동 정리 관리 // Auto cleanup management through reference counting (0, react_1.useEffect)(() => { if (autoCleanup) { // 컴포넌트 마운트 시 참조 카운트 증가 // Increment reference count on component mount incrementRef(formId, autoCleanup); return () => { // 컴포넌트 언마운트 시 참조 카운트 감소 (마지막 참조자면 자동 정리) // Decrement reference count on unmount (auto cleanup if last reference) decrementRef(formId, autoCleanup); }; } return undefined; }, [formId, autoCleanup, incrementRef, decrementRef]); // Watch 등록 / Register watchers (0, react_1.useEffect)(() => { if (!watch) return; const unsubscribers = []; Object.entries(watch).forEach(([path, handler]) => { const actionContext = { getValue: (p) => store.getValue(p), setValue: (p, value) => store.setValue(p, value), getValues: () => store.getValues(), setValues: (values) => store.setValues(values), reset: () => store.reset(), submit: form.submit, values: store.getValues(), actions: globalActions || {}, }; const unsubscribe = store.watch(path, (value, prevValue) => { // context 업데이트 (최신 값) / Update context with latest values actionContext.values = store.getValues(); actionContext.actions = getActions(formId) || {}; handler(actionContext, value, prevValue); }); unsubscribers.push(unsubscribe); }); // cleanup return () => { unsubscribers.forEach((unsub) => unsub()); }; }, [formId, watch, store, globalActions, getActions, form.submit]); // actions를 동적으로 가져오는 getter 생성 / Create getter to dynamically fetch actions const actionsGetter = (0, react_1.useMemo)(() => { return new Proxy({}, { get: (_target, prop) => { // 항상 최신 글로벌 actions를 가져옴 / Always get the latest global actions const currentGlobalActions = getActions(formId); const currentEffectiveActions = actions || currentGlobalActions || {}; const action = currentEffectiveActions[prop]; if (typeof action === "function") { // context를 바인딩하여 반환 / Return with context binding return (...args) => { const context = { values: store.getValues(), getValue: (field) => store.getValue(field), setValue: (field, value) => store.setValue(field, value), setValues: (values) => { const currentValues = store.getValues(); const newValues = { ...currentValues, ...values, }; store.setValues(newValues); }, reset: () => store.reset(), submit: form.submit, actions: {}, // Will be filled after }; context.actions = actionsGetter; return action(context, ...args); }; } return action; }, has: (_target, prop) => { const currentGlobalActions = getActions(formId); const currentEffectiveActions = actions || currentGlobalActions || {}; return prop in currentEffectiveActions; }, ownKeys: (_target) => { const currentGlobalActions = getActions(formId); const currentEffectiveActions = actions || currentGlobalActions || {}; return Reflect.ownKeys(currentEffectiveActions); }, getOwnPropertyDescriptor: (_target, prop) => { const currentGlobalActions = getActions(formId); const currentEffectiveActions = actions || currentGlobalActions || {}; if (prop in currentEffectiveActions) { return { enumerable: true, configurable: true, }; } return undefined; }, }); }, [formId, actions, getActions, store, form.submit]); return { ...form, actions: actionsGetter, // 동적 actions getter로 교체 / Replace with dynamic actions getter formId, // 글로벌 폼 ID 추가 제공 / Provide additional global form ID _store: store, // 글로벌 스토어 직접 접근용 / Direct access to global store }; } //# sourceMappingURL=useGlobalForm.js.map