UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

360 lines (359 loc) 12.9 kB
"use client"; import React, { useRef, useCallback, useMemo } from 'react'; import { errorChanged, FormError } from "../utils/index.js"; import { extendErrorMessagesWithTranslationMessages } from "../utils/errors.js"; import { convertJsxToString } from "../../../shared/component-helper.js"; import useId from "../../../shared/helpers/useId.js"; export function checkForError(potentialErrors) { return potentialErrors.some(error => { return error instanceof Error || error instanceof FormError; }); } export default function useFieldError({ initialErrorProp, warningProp, infoProp, errorMessages, validateInitially, validateContinuously, disabled, identifier, locale, handleFieldAsVisible, inFieldBlock, prerenderFieldProps, updateContextDataInSync, hasZodSchema, setFieldStateDataContext, setFieldStateFieldBlock, setFieldErrorDataContext, setFieldErrorBoundary, setFieldErrorWizard, setBlockRecord, showFieldErrorFieldBlock, revealErrorDataContext, revealErrorBoundary, wizardIndex, dataContextErrors, contextErrorMessages, valueRef, hasFocusRef, isInternalRerenderRef, schemaValidatorRef, translationRef, formatMessage, getFieldByPath, getValueByPath, forceUpdate }) { const stateId = useId(); const errorMessageCacheRef = useRef(new Map()); const ensureErrorMessageObject = useCallback(error => { let key = null; let returnValue = error; if (typeof error === 'string') { key = error; returnValue = new Error(error); } else if (error && React.isValidElement(error)) { key = error.key || convertJsxToString(error); returnValue = new FormError('Error', { formattedMessage: error }); } if (key) { if (errorMessageCacheRef.current.has(key)) { return errorMessageCacheRef.current.get(key); } else { errorMessageCacheRef.current.set(key, returnValue); } } return returnValue; }, []); const revealErrorRef = useRef(null); const messageCacheRef = useRef({ isSet: false, message: undefined }); const executeMessage = useCallback((message, forceReturnErrorMessageObject) => { if (typeof message === 'function') { const ALWAYS = 4; const INITIALLY = 8; let currentMode = ALWAYS; const msg = message(valueRef.current, { conditionally: (callback, options) => { currentMode &= ~ALWAYS; if (options?.showInitially) { currentMode |= INITIALLY; } return executeMessage(callback()); }, getValueByPath, getFieldByPath }); if (msg === undefined) { messageCacheRef.current.message = undefined; return null; } const isError = msg instanceof Error || msg instanceof FormError || Array.isArray(msg) && checkForError(msg); if (!messageCacheRef.current.isSet && currentMode & INITIALLY || currentMode & ALWAYS || hasFocusRef.current === false || messageCacheRef.current.message) { if (isInternalRerenderRef.current || currentMode & ALWAYS || !messageCacheRef.current.isSet && currentMode & INITIALLY) { if (msg) { messageCacheRef.current.isSet = true; } if (msg || !hasFocusRef.current || currentMode & ALWAYS) { messageCacheRef.current.message = msg; } } message = messageCacheRef.current.message; if (isError && message) { revealErrorRef.current = true; } if (!isError && !message) { return null; } } else { return undefined; } } return forceReturnErrorMessageObject ? ensureErrorMessageObject(message) : message; }, [getFieldByPath, getValueByPath, ensureErrorMessageObject]); const errorProp = initialErrorProp === 'initial' ? undefined : initialErrorProp; const error = executeMessage(errorProp, true); const warning = executeMessage(warningProp); const info = executeMessage(infoProp); if (revealErrorRef.current === null) { revealErrorRef.current = validateInitially !== null && validateInitially !== void 0 ? validateInitially : Boolean(errorProp); } const errorMethodRef = useRef({}); const localErrorRef = useRef(undefined); const localErrorInitiatorRef = useRef(undefined); const contextErrorRef = useRef(undefined); const errorMessagesCacheRef = useRef({ locale: null, errorMessages: null, extendedErrorMessages: null }); const combinedErrorMessages = useMemo(() => { const cache = errorMessagesCacheRef.current; if (cache.locale === locale && errorMessages && cache.extendedErrorMessages && Object.values(cache.errorMessages || {}).join('') === Object.values(errorMessages || {}).join('')) { return cache.extendedErrorMessages; } const messages = { ...contextErrorMessages, ...contextErrorMessages?.[identifier], ...errorMessages }; const extendedErrorMessages = extendErrorMessagesWithTranslationMessages(messages, translationRef.current); errorMessagesCacheRef.current = { locale, errorMessages, extendedErrorMessages }; return extendedErrorMessages; }, [contextErrorMessages, errorMessages, identifier, locale]); const prepareError = useCallback(error => { const prepare = error => { if (error instanceof FormError) { let message = error.message; const { ajvKeyword } = error; if (typeof ajvKeyword === 'string') { const ajvMessage = combinedErrorMessages?.[ajvKeyword]; if (ajvMessage) { message = ajvMessage; } } if (combinedErrorMessages?.[message]) { message = combinedErrorMessages?.[message]; if (error.messageValues) { message = Object.entries(error.messageValues || {}).reduce((msg, [key, value]) => { return msg.replace(`{${key}}`, value); }, message); } } else if (message.includes('.')) { message = formatMessage(message, error.messageValues); } if (React.isValidElement(message)) { error.formattedMessage = message; } else { error.message = message; } } return ensureErrorMessageObject(error); }; if (Array.isArray(error)) { return new FormError('Error', { errors: error.map(prepare) }); } if (error instanceof FormError) { if (Array.isArray(error.errors)) { error.errors = error.errors.map(prepare); return error; } return prepare(error); } return error; }, [combinedErrorMessages, ensureErrorMessageObject, formatMessage]); contextErrorRef.current = useMemo(() => { if (disabled) { return undefined; } const dataContextError = dataContextErrors?.[identifier]; if (!dataContextError) { return undefined; } const error = prepareError(dataContextError); if (errorChanged(error, contextErrorRef.current)) { return error; } return contextErrorRef.current; }, [dataContextErrors, identifier, prepareError, disabled]); if (localErrorRef.current?.['ajvKeyword'] === 'type' || contextErrorRef.current?.['ajvKeyword'] === 'type') { revealErrorRef.current = true; } const getBufferedError = useCallback(() => { if (initialErrorProp !== 'initial' && typeof errorProp !== 'function') { return prepareError(errorProp); } else if (revealErrorRef.current) { var _ref, _prepareError; if (contextErrorRef.current?.['ajvKeyword'] === 'type') { return contextErrorRef.current; } return (_ref = (_prepareError = prepareError(error)) !== null && _prepareError !== void 0 ? _prepareError : localErrorRef.current) !== null && _ref !== void 0 ? _ref : contextErrorRef.current; } else if (error === null) { return null; } return undefined; }, [error, errorProp, initialErrorProp, prepareError]); const bufferedError = getBufferedError(); const bufferedErrorRef = useRef(bufferedError); bufferedErrorRef.current = bufferedError; const errorIsVisible = Boolean(bufferedError); const hasError = useCallback(() => { var _ref2; return Boolean((_ref2 = error !== null && error !== void 0 ? error : localErrorRef.current) !== null && _ref2 !== void 0 ? _ref2 : contextErrorRef.current); }, [error]); const fieldStateRef = useRef(undefined); const setFieldState = useCallback(state => { fieldStateRef.current = state; setFieldStateDataContext?.(identifier, resolveValidatingState(state)); setFieldStateFieldBlock?.(identifier, resolveValidatingState(state)); if (!validateInitially) { forceUpdate(); } }, [setFieldStateDataContext, identifier, setFieldStateFieldBlock, validateInitially, forceUpdate]); const setErrorState = useCallback(hasError => { setFieldErrorWizard?.(wizardIndex, identifier, handleFieldAsVisible !== false ? hasError : undefined); showFieldErrorFieldBlock?.(identifier, hasError); revealErrorBoundary?.(identifier, hasError); revealErrorDataContext?.(identifier, hasError); }, [identifier, handleFieldAsVisible, revealErrorBoundary, revealErrorDataContext, setFieldErrorWizard, showFieldErrorFieldBlock, wizardIndex]); const revealError = useCallback(() => { if (validateInitially === false && revealErrorRef.current === false) { revealErrorRef.current = undefined; return undefined; } const hasError = Boolean(localErrorRef.current); revealErrorRef.current = true; setErrorState(hasError); }, [validateInitially, setErrorState]); const hideError = useCallback(() => { if (revealErrorRef.current) { revealErrorRef.current = undefined; setErrorState(false); } }, [setErrorState]); const persistErrorState = useCallback((method, initiator, errorArg = undefined) => { const error = prepareError(errorArg); if (!errorChanged(error, localErrorRef.current)) { return undefined; } if (initiator !== 'dataContextError') { localErrorInitiatorRef.current = initiator; } if (method === 'wipe') { errorMethodRef.current = {}; } else { errorMethodRef.current[method] = error; } if (!error && method === 'gracefully' && (errorMethodRef.current?.weak || errorMethodRef.current?.gracefully)) { return undefined; } const currentError = handleFieldAsVisible !== false ? error : undefined; localErrorRef.current = currentError; setFieldErrorDataContext?.(identifier, currentError); setFieldErrorBoundary?.(identifier, currentError); setBlockRecord?.({ stateId, identifier, type: 'error', content: currentError, showInitially: Boolean(inFieldBlock && validateInitially) }); setFieldStateDataContext?.(identifier, error ? 'error' : undefined); if (updateContextDataInSync && !prerenderFieldProps) { setFieldErrorWizard?.(wizardIndex, identifier, Boolean(currentError)); } forceUpdate(); }, [handleFieldAsVisible, identifier, inFieldBlock, prepareError, prerenderFieldProps, setBlockRecord, setFieldErrorBoundary, setFieldErrorDataContext, setFieldErrorWizard, setFieldStateDataContext, stateId, updateContextDataInSync, validateInitially, wizardIndex, forceUpdate]); const clearErrorState = useCallback(() => { persistErrorState('wipe', undefined); localErrorInitiatorRef.current = undefined; const schemaValidator = schemaValidatorRef.current; if (schemaValidator && !hasZodSchema && Array.isArray(schemaValidator?.errors)) { schemaValidator.errors = []; } }, [persistErrorState, hasZodSchema, schemaValidatorRef]); const warningRef = useRef(warning); const infoRef = useRef(info); useMemo(() => { warningRef.current = warning; }, [warning]); useMemo(() => { infoRef.current = info; }, [info]); const handleError = useCallback(() => { if (validateContinuously || validateContinuously !== false && !hasFocusRef.current) { revealError(); } else { hideError(); } }, [validateContinuously, hideError, revealError]); return { revealErrorRef, localErrorRef, localErrorInitiatorRef, contextErrorRef, errorMethodRef, fieldStateRef, warningRef, infoRef, bufferedErrorRef, error, warning, info, errorProp, combinedErrorMessages, bufferedError, errorIsVisible, ensureErrorMessageObject, executeMessage, prepareError, persistErrorState, clearErrorState, revealError, hideError, setErrorState, setFieldState, hasError, handleError, getBufferedError }; } function resolveValidatingState(state) { return state === 'validating' ? 'pending' : state; } export { resolveValidatingState }; //# sourceMappingURL=useFieldError.js.map