-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create converter field abstraction (#287)
* Created `useConverterField` hook * Synchronize form value with text state * Created changeset * Added more tests * Added validator in ConverterField * Handled one more test case * Set field touched=true on blur * Created new option "ignoreFormStateUpdatesWhileFocus" * Removed unused dependency * Created forceSetValue function in useConverterField * Wrap all functions with useCallback in useConverterField * Added test case for changing format function * Created test for changing parse function * Fixed entrypoint config in package.json * Clarified useConverterField tests
- Loading branch information
1 parent
e2159eb
commit 524f4d2
Showing
7 changed files
with
448 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@reactive-forms/x': patch | ||
--- | ||
|
||
Created useConverterField hook in @reactive-forms/x package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './plugin'; | ||
export * from './useConverterField'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { useCallback, useEffect, useRef, useState } from 'react'; | ||
import { FieldConfig, FieldContext, FieldError, FieldTouched, useField, useFieldValidator } from '@reactive-forms/core'; | ||
import isObject from 'lodash/isObject'; | ||
|
||
export class ConversionError extends Error { | ||
public constructor(errorMessage: string) { | ||
super(errorMessage); | ||
} | ||
} | ||
|
||
export type ConverterFieldConfig<T> = { | ||
parse: (value: string) => T; | ||
format: (value: T) => string; | ||
} & FieldConfig<T>; | ||
|
||
export type ConverterFieldBag<T> = { | ||
text: string; | ||
onTextChange: (text: string) => void; | ||
onFocus: () => void; | ||
onBlur: () => void; | ||
} & FieldContext<T>; | ||
|
||
export const useConverterField = <T>({ | ||
parse, | ||
format, | ||
...fieldConfig | ||
}: ConverterFieldConfig<T>): ConverterFieldBag<T> => { | ||
const fieldBag = useField(fieldConfig); | ||
|
||
const { | ||
value, | ||
control: { setValue, setError, setTouched }, | ||
} = fieldBag; | ||
|
||
const [isFocused, setIsFocused] = useState(false); | ||
const [text, setText] = useState(() => format(value)); | ||
const textRef = useRef(text); | ||
textRef.current = text; | ||
|
||
const [hasConversionError, setHasConversionError] = useState(false); | ||
|
||
const tryConvert = useCallback( | ||
(text: string) => { | ||
try { | ||
const value = parse(text); // this could throw in case of conversion error | ||
setValue(value); | ||
setHasConversionError(false); | ||
} catch (error) { | ||
if (isObject(error) && error instanceof ConversionError) { | ||
setHasConversionError(true); | ||
setError({ | ||
$error: error.message, | ||
} as FieldError<T>); | ||
} else { | ||
throw error; | ||
} | ||
} | ||
}, | ||
[parse, setError, setValue], | ||
); | ||
|
||
const onTextChange = useCallback( | ||
(newText: string) => { | ||
textRef.current = newText; | ||
setText(newText); | ||
tryConvert(newText); | ||
}, | ||
[tryConvert], | ||
); | ||
|
||
const onFocus = useCallback(() => { | ||
setIsFocused(true); | ||
}, []); | ||
|
||
const onBlur = useCallback(() => { | ||
setIsFocused(false); | ||
setTouched({ $touched: true } as FieldTouched<T>); | ||
tryConvert(text); | ||
}, [setTouched, text, tryConvert]); | ||
|
||
const forceSetValue = useCallback( | ||
(value: T) => { | ||
onTextChange(format(value)); | ||
setValue(value); | ||
}, | ||
[format, onTextChange, setValue], | ||
); | ||
|
||
useFieldValidator({ | ||
name: fieldConfig.name, | ||
validator: () => { | ||
try { | ||
parse(textRef.current); | ||
} catch (error) { | ||
if (isObject(error) && error instanceof ConversionError) { | ||
return error.message; | ||
} | ||
|
||
throw error; | ||
} | ||
|
||
return undefined; | ||
}, | ||
}); | ||
|
||
useEffect(() => { | ||
if (isFocused || hasConversionError) { | ||
return; | ||
} | ||
|
||
const formattedValue = format(value); | ||
textRef.current = formattedValue; | ||
setText(formattedValue); | ||
}, [value, format, hasConversionError, isFocused]); | ||
|
||
const tryConvertRef = useRef(tryConvert); | ||
|
||
useEffect(() => { | ||
if (tryConvertRef.current !== tryConvert) { | ||
tryConvert(textRef.current); // Parse text again when parse function changes | ||
} | ||
|
||
tryConvertRef.current = tryConvert; | ||
}, [tryConvert]); | ||
|
||
return { | ||
text, | ||
onTextChange, | ||
onFocus, | ||
onBlur, | ||
...fieldBag, | ||
control: { ...fieldBag.control, setValue: forceSetValue }, | ||
}; | ||
}; |
Oops, something went wrong.