diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx
index 6c2f3dac4ab..96ffacbcac0 100644
--- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx
@@ -617,4 +617,29 @@ describe('DataContext.Provider', () => {
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})
})
+
+ it('should show provided errorMessages based on outer schema validation with injected value', () => {
+ const schema: JSONSchema7 = {
+ type: 'object',
+ properties: {
+ val: {
+ type: 'string',
+ minLength: 7,
+ },
+ },
+ }
+
+ render(
+
+
+
+ )
+
+ expect(screen.getByText('Minimum 7 chars.')).toBeInTheDocument()
+ })
})
diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts
index 4565a4c73c4..9a3e886987b 100644
--- a/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts
+++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts
@@ -64,6 +64,7 @@ export default function useDataValue<
setPathWithError: dataContextSetPathWithError,
errors: dataContextErrors,
} = dataContext ?? {}
+ const dataContextError = path ? dataContextErrors?.[path] : undefined
const inFieldBlock = Boolean(fieldBlockContext)
const {
setError: setFieldBlockError,
@@ -145,7 +146,9 @@ export default function useDataValue<
// - Local errors are errors based on validation instructions received by
const localErrorRef = useRef()
// - Context errors are from outer contexts, like validation for this field as part of the whole data set
- const contextErrorRef = useRef()
+ const contextErrorRef = useRef(
+ dataContextError
+ )
const showErrorRef = useRef(Boolean(showErrorInitially))
const errorMessagesRef = useRef(errorMessages)
@@ -184,18 +187,19 @@ export default function useDataValue<
return
}
- if (
- error instanceof FormError &&
- typeof error.validationRule === 'string' &&
- errorMessagesRef.current?.[error.validationRule] !== undefined
- ) {
- const message = errorMessagesRef.current[
- error.validationRule
- ].replace(
- `{${error.validationRule}}`,
- props?.[error.validationRule]
- )
- return new FormError(message)
+ if (error instanceof FormError) {
+ const message =
+ (typeof error.validationRule === 'string' &&
+ errorMessagesRef.current?.[error.validationRule]) ||
+ error.message
+
+ const messageWithValues = Object.entries(
+ error.messageValues ?? {}
+ ).reduce((message, [key, value]) => {
+ return message.replace(`{${key}}`, value)
+ }, message)
+
+ return new FormError(messageWithValues)
}
return error
@@ -306,9 +310,12 @@ export default function useDataValue<
forceUpdate()
}, [externalValue, validateValue])
- const dataContextError = path ? dataContextErrors?.[path] : undefined
useEffect(() => {
- contextErrorRef.current = prepareError(dataContextError)
+ const error = prepareError(dataContextError)
+ if (errorChanged(error, contextErrorRef.current)) {
+ contextErrorRef.current = error
+ forceUpdate()
+ }
}, [dataContextError, prepareError])
useEffect(() => {
@@ -444,7 +451,7 @@ export default function useDataValue<
value: toInput(valueRef.current),
error:
!inFieldBlock && showErrorRef.current
- ? errorProp ?? localErrorRef.current ?? dataContextError
+ ? errorProp ?? localErrorRef.current ?? contextErrorRef.current
: undefined,
autoComplete:
props.autoComplete ??
diff --git a/packages/dnb-eufemia/src/extensions/forms/types.ts b/packages/dnb-eufemia/src/extensions/forms/types.ts
index c04192d3307..bd50c59243e 100644
--- a/packages/dnb-eufemia/src/extensions/forms/types.ts
+++ b/packages/dnb-eufemia/src/extensions/forms/types.ts
@@ -1,8 +1,12 @@
import { JSONSchema7 } from 'json-schema'
import { SpacingProps } from '../../components/space/types'
+type ValidationRule = string | string[]
+type MessageValues = Record
+
interface IFormErrorOptions {
- validationRule?: string | string[]
+ validationRule?: ValidationRule
+ messageValues?: MessageValues
}
/**
@@ -12,13 +16,20 @@ export class FormError extends Error {
/**
* What validation rule did the error occur based on? (i.e: minLength, required or maximum)
*/
- validationRule?: string | string[]
+ validationRule?: ValidationRule
+
+ /**
+ * Replacement values relevant for this error.
+ * @example { minLength: 3 } to be able to replace values in a message like "Minimum {minLength} charactes"
+ */
+ messageValues?: MessageValues
constructor(message: string, options?: IFormErrorOptions) {
super(message)
if (options) {
this.validationRule = options.validationRule
+ this.messageValues = options.messageValues
}
}
}
diff --git a/packages/dnb-eufemia/src/extensions/forms/utils/ajv.ts b/packages/dnb-eufemia/src/extensions/forms/utils/ajv.ts
index fbfebeec134..b06413dbb90 100644
--- a/packages/dnb-eufemia/src/extensions/forms/utils/ajv.ts
+++ b/packages/dnb-eufemia/src/extensions/forms/utils/ajv.ts
@@ -36,9 +36,38 @@ function getValidationRule(ajvError: ErrorObject): string {
return ajvError.keyword
}
+function getMessageValues(
+ ajvError: ErrorObject
+): FormError['messageValues'] {
+ const validationRule = getValidationRule(ajvError)
+
+ switch (validationRule) {
+ case 'minLength':
+ case 'maxLength':
+ case 'minimum':
+ case 'maximum':
+ case 'exclusiveMinimum':
+ case 'exclusiveMaximum':
+ return {
+ [validationRule]: ajvError.params?.limit,
+ }
+ case 'multipleOf':
+ return {
+ [validationRule]: ajvError.params?.multipleOf,
+ }
+ case 'pattern':
+ return {
+ [validationRule]: ajvError.params?.pattern,
+ }
+ }
+}
+
function ajvErrorToFormError(ajvError: ErrorObject): FormError {
const error = new FormError(ajvError.message ?? 'Unknown error', {
validationRule: getValidationRule(ajvError),
+ // Keep the message values in the error object instead of injecting them into the message
+ // at once, since an error might be validated one place, and then get a new message before it is displayed.
+ messageValues: getMessageValues(ajvError),
})
return error
}