@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
360 lines (359 loc) • 12.9 kB
JavaScript
"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