From 581c7ccfd46ea270c3302eeb7c772d93f7c5726b Mon Sep 17 00:00:00 2001 From: Aleksandras Sukelovic Date: Wed, 30 Aug 2023 12:16:47 +0300 Subject: [PATCH] Refactored DecimalField custom errors API --- packages/x/src/useDecimalField.ts | 64 ++++++++++++++--------- packages/x/tests/useDecimalField.test.tsx | 50 +++++++++++------- 2 files changed, 68 insertions(+), 46 deletions(-) diff --git a/packages/x/src/useDecimalField.ts b/packages/x/src/useDecimalField.ts index 8c89dff2..7d6f725b 100644 --- a/packages/x/src/useDecimalField.ts +++ b/packages/x/src/useDecimalField.ts @@ -1,5 +1,6 @@ import { useCallback } from 'react'; import { FieldConfig, useFieldValidator } from '@reactive-forms/core'; +import { isFunction, isNil } from 'lodash'; import { ConversionError, ConverterFieldBag, useConverterField } from './useConverterField'; @@ -14,28 +15,23 @@ export const defaultFormat = (value: number | null | undefined, precision: numbe return value.toFixed(precision).toString(); }; -export type DecimalFieldErrorMessages = { - invalidInput: string; - required: string; - lessThanMinValue: (min: number, precision: number) => string; - moreThanMaxValue: (max: number, precision: number) => string; -}; +export const defaultRequiredError = 'Field is required'; +export const defaultInvalidInputError = 'Must be decimal'; +export const defaultMinValueError = (min: number, precision: number) => + `Value should not be less than ${defaultFormat(min, precision)}`; +export const defaultMaxValueError = (max: number, precision: number) => + `Value should not be more than ${defaultFormat(max, precision)}`; -export const defaultErrorMessages: DecimalFieldErrorMessages = { - invalidInput: 'Must be decimal', - required: 'Field is required', - lessThanMinValue: (min, precision) => `Value should not be less than ${defaultFormat(min, precision)}`, - moreThanMaxValue: (max, precision) => `Value should not be more than ${defaultFormat(max, precision)}`, -}; +export type ErrorTuple = [value: T, message: string | ((value: T) => string)]; export type DecimalFieldConfig = FieldConfig & { - required?: boolean; - min?: number; - max?: number; + required?: boolean | string; + invalidInput?: string; + min?: number | ErrorTuple; + max?: number | ErrorTuple; format?: (value: number | null | undefined, precision: number) => string; parse?: (text: string) => number; - errorMessages?: Partial; precision?: number; }; @@ -47,11 +43,11 @@ export const useDecimalField = ({ validator, schema, required, + invalidInput, min, max, format, parse, - errorMessages = defaultErrorMessages, precision = defaultPrecision, }: DecimalFieldConfig): DecimalFieldBag => { const defaultParse = useCallback( @@ -62,10 +58,10 @@ export const useDecimalField = ({ return null; } - const errorMessage = errorMessages.invalidInput ?? defaultErrorMessages.invalidInput; + const parseError = invalidInput ?? defaultInvalidInputError; if (!DECIMAL_REGEX.test(text)) { - throw new ConversionError(errorMessage); + throw new ConversionError(parseError); } const value = Number.parseFloat(text); @@ -76,12 +72,12 @@ export const useDecimalField = ({ return 0; } - throw new ConversionError(errorMessage); + throw new ConversionError(parseError); } return value; }, - [errorMessages.invalidInput], + [invalidInput], ); const formatValue = useCallback( @@ -103,19 +99,35 @@ export const useDecimalField = ({ name, validator: (value) => { if (required && typeof value !== 'number') { - return errorMessages.required ?? defaultErrorMessages.required; + return required === true ? defaultRequiredError : required; } if (typeof value !== 'number') { return undefined; } - if (typeof min === 'number' && value < min) { - return (errorMessages.lessThanMinValue ?? defaultErrorMessages.lessThanMinValue)(min, precision); + if (!isNil(min)) { + if (Array.isArray(min)) { + const [minValue, message] = min; + + if (value < minValue) { + return isFunction(message) ? message(value) : message; + } + } else if (value < min) { + return defaultMinValueError(min, precision); + } } - if (typeof max === 'number' && value > max) { - return (errorMessages.moreThanMaxValue ?? defaultErrorMessages.moreThanMaxValue)(max, precision); + if (!isNil(max)) { + if (Array.isArray(max)) { + const [maxValue, message] = max; + + if (value > maxValue) { + return isFunction(message) ? message(value) : message; + } + } else if (value > max) { + return defaultMaxValueError(max, precision); + } } return undefined; diff --git a/packages/x/tests/useDecimalField.test.tsx b/packages/x/tests/useDecimalField.test.tsx index c8ade33e..817acc7b 100644 --- a/packages/x/tests/useDecimalField.test.tsx +++ b/packages/x/tests/useDecimalField.test.tsx @@ -4,9 +4,12 @@ import { act, renderHook, waitFor } from '@testing-library/react'; import { DecimalFieldConfig, - defaultErrorMessages, defaultFormat, + defaultInvalidInputError, + defaultMaxValueError, + defaultMinValueError, defaultPrecision, + defaultRequiredError, useDecimalField, } from '../src/useDecimalField'; @@ -58,7 +61,7 @@ describe('Decimal field', () => { }); await waitFor(() => { - expect(result.current.meta.error?.$error).toBe(defaultErrorMessages.invalidInput); + expect(result.current.meta.error?.$error).toBe(defaultInvalidInputError); }); await act(() => { @@ -66,7 +69,7 @@ describe('Decimal field', () => { }); await waitFor(() => { - expect(result.current.meta.error?.$error).toBe(defaultErrorMessages.invalidInput); + expect(result.current.meta.error?.$error).toBe(defaultInvalidInputError); }); await act(() => { @@ -74,7 +77,7 @@ describe('Decimal field', () => { }); await waitFor(() => { - expect(result.current.meta.error?.$error).toBe(defaultErrorMessages.invalidInput); + expect(result.current.meta.error?.$error).toBe(defaultInvalidInputError); }); await act(() => { @@ -150,7 +153,7 @@ describe('Decimal field', () => { }); await waitFor(() => { - expect(result.current.meta.error?.$error).toBe(defaultErrorMessages.required); + expect(result.current.meta.error?.$error).toBe(defaultRequiredError); }); }); @@ -162,9 +165,15 @@ describe('Decimal field', () => { }); await waitFor(() => { - expect(result.current.meta.error?.$error).toBe( - defaultErrorMessages.lessThanMinValue(0.5, defaultPrecision), - ); + expect(result.current.meta.error?.$error).toBe(defaultMinValueError(0.5, defaultPrecision)); + }); + + act(() => { + result.current.control.setValue(0.5); + }); + + await waitFor(() => { + expect(result.current.meta.error?.$error).toBeUndefined(); }); }); @@ -176,17 +185,21 @@ describe('Decimal field', () => { }); await waitFor(() => { - expect(result.current.meta.error?.$error).toBe( - defaultErrorMessages.moreThanMaxValue(0.5, defaultPrecision), - ); + expect(result.current.meta.error?.$error).toBe(defaultMaxValueError(0.5, defaultPrecision)); + }); + + act(() => { + result.current.control.setValue(0.5); + }); + + await waitFor(() => { + expect(result.current.meta.error?.$error).toBeUndefined(); }); }); it('Should set custom conversion error correctly', async () => { const [{ result }] = renderUseDecimalField({ - errorMessages: { - invalidInput: 'custom', - }, + invalidInput: 'custom', }); await act(() => { @@ -216,8 +229,7 @@ describe('Decimal field', () => { it('Should set custom error if field is required and empty', async () => { const [{ result }] = renderUseDecimalField({ - required: true, - errorMessages: { required: 'custom' }, + required: 'custom', }); act(() => { @@ -231,8 +243,7 @@ describe('Decimal field', () => { it('Should set custom error if field value is less than min', async () => { const [{ result }] = renderUseDecimalField({ - min: 0.5, - errorMessages: { lessThanMinValue: () => 'custom' }, + min: [0.5, 'custom'], }); act(() => { @@ -246,8 +257,7 @@ describe('Decimal field', () => { it('Should set custom error if field value is more than max', async () => { const [{ result }] = renderUseDecimalField({ - max: 0.5, - errorMessages: { moreThanMaxValue: () => 'custom' }, + max: [0.5, 'custom'], }); act(() => {