diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useDataValue.test.tsx b/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useDataValue.test.tsx index 7c0d904f044..ccd7bb64cc6 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useDataValue.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useDataValue.test.tsx @@ -123,6 +123,47 @@ describe('useDataValue', () => { }) }) + describe('value manipulation', () => { + it('should call fromInput and toInput', () => { + const fromInput = jest.fn((v) => v + 1) + const toInput = jest.fn((v) => v - 1) + const onChange = jest.fn() + + const { result } = renderHook(() => + useDataValue({ + value: 1, + onChange, + fromInput, + toInput, + }) + ) + + const { handleChange } = result.current + + act(() => { + handleChange(2) + }) + + expect(fromInput).toHaveBeenCalledTimes(1) + expect(toInput).toHaveBeenCalledTimes(2) + expect(fromInput).toHaveBeenLastCalledWith(2) + expect(toInput).toHaveBeenLastCalledWith(3) + + act(() => { + handleChange(4) + }) + + expect(fromInput).toHaveBeenCalledTimes(2) + expect(toInput).toHaveBeenCalledTimes(3) + expect(fromInput).toHaveBeenLastCalledWith(4) + expect(toInput).toHaveBeenLastCalledWith(5) + + /** + * NB: "forceUpdate" is initiator that "toInput" is called more often. + */ + }) + }) + describe('updating internal value', () => { it('should update the internal value, but not call any event handler', () => { const onFocus = jest.fn() @@ -132,7 +173,6 @@ describe('useDataValue', () => { const { result } = renderHook(() => useDataValue({ value: 'foo', - emptyValue: '', onFocus, onBlur, onChange, @@ -164,6 +204,58 @@ describe('useDataValue', () => { expect(onBlur).toHaveBeenCalledTimes(2) }) + it('should not call fromInput', () => { + const fromInput = jest.fn((v) => v) + const toInput = jest.fn((v) => v) + const onChange = jest.fn() + + const { result } = renderHook(() => + useDataValue({ + value: 'foo', + onChange, + fromInput, + toInput, + }) + ) + + const { updateValue, handleChange } = result.current + + act(() => { + updateValue('') + }) + + expect(fromInput).toHaveBeenCalledTimes(0) + + act(() => { + updateValue('bar') + }) + + expect(fromInput).toHaveBeenCalledTimes(0) + expect(onChange).toHaveBeenCalledTimes(0) + + act(() => { + handleChange('') + }) + + expect(fromInput).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenCalledTimes(1) + + act(() => { + updateValue('unchanged') + }) + + expect(fromInput).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenCalledTimes(1) + + act(() => { + handleChange('unchanged') + }) + + expect(fromInput).toHaveBeenCalledTimes(2) + expect(toInput).toHaveBeenCalledTimes(5) + expect(onChange).toHaveBeenCalledTimes(1) + }) + it('should update the internal value and run error validation', async () => { const onFocus = jest.fn() const onBlur = jest.fn() diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts index fd356efaf04..e0bd9a823f2 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts @@ -50,8 +50,8 @@ export default function useDataValue< validateInitially, validateUnchanged, continuousValidation, - toInput = (value) => value, - fromInput = (value) => value, + toInput = (value: Value) => value, + fromInput = (value: Value) => value, } = props const [, forceUpdate] = useReducer(() => ({}), {}) const { startProcess } = useProcessManager() @@ -373,9 +373,7 @@ export default function useDataValue< const handleBlur = useCallback(() => setHasFocus(false), [setHasFocus]) const updateValue = useCallback( - (argFromInput) => { - const newValue = fromInput(argFromInput) - + (newValue: Value) => { if (newValue === valueRef.current) { // Avoid triggering a change if the value was not actually changed. This may be caused by rendering components // calling onChange even if the actual value did not change. @@ -396,6 +394,7 @@ export default function useDataValue< // When changing the value, hide errors to avoid annoying the user before they are finished filling in that value hideError() } + // Always validate the value immediately when it is changed validateValue() @@ -408,7 +407,6 @@ export default function useDataValue< [ continuousValidation, dataContextHandlePathChange, - fromInput, hideError, path, showError, @@ -417,7 +415,7 @@ export default function useDataValue< ) const handleChange = useCallback( - (argFromInput) => { + (argFromInput: Value) => { const newValue = fromInput(argFromInput) if (newValue === valueRef.current) { @@ -426,7 +424,7 @@ export default function useDataValue< return } - updateValue(argFromInput) + updateValue(newValue) changedRef.current = true onChange?.(newValue)