Skip to content

Commit f953cf9

Browse files
authored
fix(Forms): hide info, warning or error prop when undefined is given to a Field (#4634)
Fixes #4611
1 parent 05e30b6 commit f953cf9

File tree

5 files changed

+159
-28
lines changed

5 files changed

+159
-28
lines changed

packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx

+17
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,23 @@ describe('Field.Number', () => {
171171
).toBeInTheDocument()
172172
})
173173

174+
it('should hide error when undefined is returned by error function', () => {
175+
const { rerender } = render(
176+
<Field.Number
177+
error={() => new Error('This is what went wrong')}
178+
/>
179+
)
180+
expect(
181+
screen.getByText('This is what went wrong')
182+
).toBeInTheDocument()
183+
184+
rerender(<Field.Number error={() => undefined} />)
185+
186+
expect(
187+
document.querySelector('.dnb-form-status')
188+
).not.toBeInTheDocument()
189+
})
190+
174191
it('renders error given as a function with value', () => {
175192
render(
176193
<Field.Number

packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx

+19-14
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
FieldProps,
3333
SubmitState,
3434
Identifier,
35+
UseFieldProps,
3536
} from '../types'
3637
import type { FormLabelAllProps } from '../../../components/FormLabel'
3738
import HelpButtonInline, {
@@ -159,7 +160,7 @@ function FieldBlock(props: Props) {
159160
required,
160161
info,
161162
warning,
162-
error: errorProp,
163+
error,
163164
disableStatusSummary,
164165
fieldState,
165166
disabled,
@@ -175,16 +176,18 @@ function FieldBlock(props: Props) {
175176
const hasCustomWidth = /\d(rem)$/.test(String(width))
176177
const hasCustomContentWidth = /\d(rem)$/.test(String(contentWidth))
177178

179+
const infoRef = useRef<UseFieldProps['info']>()
180+
const warningRef = useRef<UseFieldProps['warning']>()
181+
const errorRef = useRef<UseFieldProps['error']>()
182+
178183
const blockId = useId(props.id)
179184
const [salt, forceUpdate] = useReducer(() => ({}), {})
180185
const mountedFieldsRef = useRef<MountedFieldsRef>({})
181186
const fieldStateRef = useRef<SubmitState>(null)
182187
const stateRecordRef = useRef<StateRecord>({})
183188
const fieldStateIdsRef = useRef<FieldErrorIdsRef>(null)
184189
const contentsRef = useRef<HTMLDivElement>(null)
185-
const hasInitiallyErrorProp = useMemo(() => {
186-
return Boolean(errorProp)
187-
}, []) // eslint-disable-line react-hooks/exhaustive-deps
190+
const hasInitiallyErrorPropRef = useRef(Boolean(error))
188191

189192
const label = useIterateItemNo({
190193
label: labelProp,
@@ -273,16 +276,18 @@ function FieldBlock(props: Props) {
273276
)
274277

275278
const statusContent = useMemo(() => {
276-
if (typeof errorProp !== 'undefined') {
279+
if (typeof error !== 'undefined' || (errorRef.current && !error)) {
280+
errorRef.current = error
277281
setInternalRecord({
278282
identifier: blockId,
279-
showInitially: hasInitiallyErrorProp,
283+
showInitially: hasInitiallyErrorPropRef.current,
280284
type: 'error',
281-
content: errorProp,
285+
content: error,
282286
})
283287
}
284288

285-
if (typeof warning !== 'undefined') {
289+
if (typeof warning !== 'undefined' || warningRef.current !== warning) {
290+
warningRef.current = warning
286291
setInternalRecord({
287292
identifier: blockId,
288293
showInitially: true,
@@ -291,7 +296,8 @@ function FieldBlock(props: Props) {
291296
})
292297
}
293298

294-
if (typeof info !== 'undefined') {
299+
if (typeof info !== 'undefined' || infoRef.current !== info) {
300+
infoRef.current = info
295301
setInternalRecord({
296302
identifier: blockId,
297303
showInitially: true,
@@ -405,13 +411,12 @@ function FieldBlock(props: Props) {
405411
return acc
406412
}, salt) as StatusContent
407413
}, [
408-
errorProp,
414+
error,
409415
warning,
410416
info,
411417
salt,
412418
setInternalRecord,
413419
blockId,
414-
hasInitiallyErrorProp,
415420
props.id,
416421
forId,
417422
label,
@@ -420,9 +425,9 @@ function FieldBlock(props: Props) {
420425
// Handle the error prop from outside
421426
useEffect(() => {
422427
if (!nestedFieldBlockContext) {
423-
showFieldError(blockId, Boolean(errorProp))
428+
showFieldError(blockId, Boolean(error))
424429
}
425-
}, [errorProp, blockId, showFieldError, nestedFieldBlockContext])
430+
}, [error, blockId, showFieldError, nestedFieldBlockContext])
426431

427432
useEffect(
428433
() => () => {
@@ -519,7 +524,7 @@ function FieldBlock(props: Props) {
519524
setBlockRecord,
520525
setFieldState,
521526
showFieldError,
522-
hasErrorProp: Boolean(errorProp),
527+
hasErrorProp: Boolean(error),
523528
fieldStateIdsRef,
524529
mountedFieldsRef,
525530
composition,

packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/FieldBlock.test.tsx

+88
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,34 @@ describe('FieldBlock', () => {
730730
expect(element).toHaveClass('dnb-height-animation--is-visible')
731731
expect(element).toHaveTextContent(blockInfo)
732732
})
733+
734+
it('should show and hide the message when info prop gets "undefined"', () => {
735+
const { rerender } = render(<FieldBlock info="message" />)
736+
737+
expect(
738+
document.querySelector('.dnb-form-status')
739+
).toBeInTheDocument()
740+
741+
rerender(<FieldBlock info={undefined} />)
742+
743+
expect(
744+
document.querySelector('.dnb-form-status')
745+
).not.toBeInTheDocument()
746+
})
747+
748+
it('should show and hide the message when info prop gets "undefined" (using Field.String in order to include useFieldProps)', () => {
749+
const { rerender } = render(<Field.String info="message" />)
750+
751+
expect(
752+
document.querySelector('.dnb-form-status')
753+
).toBeInTheDocument()
754+
755+
rerender(<Field.String info={undefined} />)
756+
757+
expect(
758+
document.querySelector('.dnb-form-status')
759+
).not.toBeInTheDocument()
760+
})
733761
})
734762

735763
describe('warning prop', () => {
@@ -758,6 +786,34 @@ describe('FieldBlock', () => {
758786
expect(element).toHaveClass('dnb-height-animation--is-visible')
759787
expect(element).toHaveTextContent(blockWarning)
760788
})
789+
790+
it('should show and hide the message when warning prop gets "undefined"', () => {
791+
const { rerender } = render(<FieldBlock warning="message" />)
792+
793+
expect(
794+
document.querySelector('.dnb-form-status')
795+
).toBeInTheDocument()
796+
797+
rerender(<FieldBlock warning={undefined} />)
798+
799+
expect(
800+
document.querySelector('.dnb-form-status')
801+
).not.toBeInTheDocument()
802+
})
803+
804+
it('should show and hide the message when warning prop gets "undefined" (using Field.String in order to include useFieldProps)', () => {
805+
const { rerender } = render(<Field.String warning="message" />)
806+
807+
expect(
808+
document.querySelector('.dnb-form-status')
809+
).toBeInTheDocument()
810+
811+
rerender(<Field.String warning={undefined} />)
812+
813+
expect(
814+
document.querySelector('.dnb-form-status')
815+
).not.toBeInTheDocument()
816+
})
761817
})
762818

763819
describe('error prop', () => {
@@ -773,6 +829,38 @@ describe('FieldBlock', () => {
773829
expect(element).toHaveClass('dnb-height-animation--is-visible')
774830
expect(element).toHaveTextContent(blockError)
775831
})
832+
833+
it('should show and hide the message when error prop gets "undefined"', () => {
834+
const { rerender } = render(
835+
<FieldBlock error={new Error('message')} />
836+
)
837+
838+
expect(
839+
document.querySelector('.dnb-form-status')
840+
).toBeInTheDocument()
841+
842+
rerender(<FieldBlock error={undefined} />)
843+
844+
expect(
845+
document.querySelector('.dnb-form-status')
846+
).not.toBeInTheDocument()
847+
})
848+
849+
it('should show and hide the message when error prop gets "undefined" (using Field.String in order to include useFieldProps)', () => {
850+
const { rerender } = render(
851+
<Field.String error={new Error('message')} />
852+
)
853+
854+
expect(
855+
document.querySelector('.dnb-form-status')
856+
).toBeInTheDocument()
857+
858+
rerender(<Field.String error={undefined} />)
859+
860+
expect(
861+
document.querySelector('.dnb-form-status')
862+
).not.toBeInTheDocument()
863+
})
776864
})
777865

778866
describe('summarize errors', () => {

packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useFieldProps.test.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,12 @@ describe('useFieldProps', () => {
292292
const { result } = renderHook(useFieldProps, {
293293
initialProps: {
294294
value: '',
295-
error: new Error('Error message'),
295+
onChangeValidator: () => {
296+
return new Error('Error message')
297+
},
298+
onBlurValidator: () => {
299+
return new Error('Error message')
300+
},
296301
validateInitially: false,
297302
},
298303
})
@@ -2076,6 +2081,7 @@ describe('useFieldProps', () => {
20762081

20772082
expect(result.current.htmlAttributes).toEqual({
20782083
'aria-describedby': expect.stringMatching(/id-.*-form-status/),
2084+
'aria-invalid': 'true',
20792085
})
20802086

20812087
rerender({})

packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts

+28-13
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export default function useFieldProps<Value, EmptyValue, Props>(
126126
disabled: disabledProp,
127127
info: infoProp,
128128
warning: warningProp,
129-
error: errorProp,
129+
error: initialErrorProp = 'initial',
130130
errorMessages,
131131
onFocus,
132132
onBlur,
@@ -444,7 +444,11 @@ export default function useFieldProps<Value, EmptyValue, Props>(
444444
[getFieldByPath, getValueByPath]
445445
)
446446

447-
const error = executeMessage<UseFieldProps['error']>(errorProp)
447+
const errorProp =
448+
initialErrorProp === 'initial' ? undefined : initialErrorProp
449+
const error = executeMessage<UseFieldProps['error'] | 'initial'>(
450+
errorProp
451+
)
448452
const warning = executeMessage<UseFieldProps['warning']>(warningProp)
449453
const info = executeMessage<UseFieldProps['info']>(infoProp)
450454

@@ -728,13 +732,24 @@ export default function useFieldProps<Value, EmptyValue, Props>(
728732
revealErrorRef.current = true
729733
}
730734

731-
const bufferedError = revealErrorRef.current
732-
? prepareError(error) ??
733-
localErrorRef.current ??
734-
contextErrorRef.current
735-
: error === null
736-
? null
737-
: undefined
735+
const getBufferedError = useCallback(() => {
736+
if (
737+
initialErrorProp !== 'initial' &&
738+
typeof errorProp !== 'function'
739+
) {
740+
return prepareError(errorProp)
741+
} else if (revealErrorRef.current) {
742+
return (
743+
prepareError(error as FormError) ??
744+
localErrorRef.current ??
745+
contextErrorRef.current
746+
)
747+
} else if (error === null) {
748+
return null
749+
}
750+
}, [error, errorProp, initialErrorProp, prepareError])
751+
752+
const bufferedError = getBufferedError()
738753

739754
const hasVisibleError =
740755
Boolean(bufferedError) ||
@@ -2350,12 +2365,12 @@ export default function useFieldProps<Value, EmptyValue, Props>(
23502365

23512366
const infoRef = useRef<UseFieldProps['info']>(info)
23522367
const warningRef = useRef<UseFieldProps['warning']>(warning)
2353-
if (typeof info !== 'undefined') {
2368+
useMemo(() => {
23542369
infoRef.current = info
2355-
}
2356-
if (typeof warning !== 'undefined') {
2370+
}, [info])
2371+
useMemo(() => {
23572372
warningRef.current = warning
2358-
}
2373+
}, [warning])
23592374

23602375
const connections = useMemo(() => {
23612376
return {

0 commit comments

Comments
 (0)