Skip to content

Add useDebounce hook and timeoutOnChange for TextField #3365

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/textField/textField.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
},
{"name": "validateOnStart", "type": "boolean", "description": "Should validate when the TextField mounts"},
{"name": "validateOnChange", "type": "boolean", "description": "Should validate when the TextField value changes"},
{"name": "validationDebounceTime", "type": "number", "description": "Add a debounce timeout when sending validateOnChange"},
{"name": "validateOnBlur", "type": "boolean", "description": "Should validate when losing focus of TextField"},
{
"name": "onChangeValidity",
Expand Down
5 changes: 5 additions & 0 deletions src/components/textField/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type Validator = ((value?: string) => boolean) | keyof typeof formValidat
export interface FieldStateProps extends InputProps {
validateOnStart?: boolean;
validateOnChange?: boolean;
validationDebounceTime?: number;
validateOnBlur?: boolean;
/**
* Callback for when field validated and failed
Expand Down Expand Up @@ -241,6 +242,10 @@ export type TextFieldProps = MarginModifiers &
* Should validate when the TextField value changes
*/
validateOnChange?: boolean;
/**
* Add a debounce timeout when sending validateOnChange
*/
validationDebounceTime?: number;
/**
* Should validate when losing focus of TextField
*/
Expand Down
47 changes: 29 additions & 18 deletions src/components/textField/useFieldState.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useCallback, useState, useEffect, useMemo} from 'react';
import _ from 'lodash';
import * as Presenter from './Presenter';
import {useDidUpdate} from 'hooks';
import {useDidUpdate, useDebounce} from 'hooks';
import {FieldStateProps} from './types';
import {Constants} from '../../commons/new';

Expand All @@ -10,6 +10,7 @@ export default function useFieldState({
validationMessage,
validateOnBlur,
validateOnChange,
validationDebounceTime,
validateOnStart,
onValidationFailed,
onChangeValidity,
Expand Down Expand Up @@ -42,12 +43,32 @@ export default function useFieldState({
}
}, []);

const validateField = useCallback((valueToValidate = value) => {
const [_isValid, _failingValidatorIndex] = Presenter.validate(valueToValidate, validate);

setIsValid(_isValid);
setFailingValidatorIndex(_failingValidatorIndex);

if (!_isValid && !_.isUndefined(_failingValidatorIndex)) {
onValidationFailed?.(_failingValidatorIndex);
}

return _isValid;
},
[value, validate, onValidationFailed]);

const debouncedValidateField = useDebounce(validateField, validationDebounceTime);

useEffect(() => {
if (propsValue !== value) {
setValue(propsValue);

if (validateOnChange) {
validateField(propsValue);
if (validationDebounceTime) {
debouncedValidateField(propsValue);
} else {
validateField(propsValue);
}
}
}
/* On purpose listen only to propsValue change */
Expand All @@ -65,20 +86,6 @@ export default function useFieldState({
return _isValid;
}, [value, validate]);

const validateField = useCallback((valueToValidate = value) => {
const [_isValid, _failingValidatorIndex] = Presenter.validate(valueToValidate, validate);

setIsValid(_isValid);
setFailingValidatorIndex(_failingValidatorIndex);

if (!_isValid && !_.isUndefined(_failingValidatorIndex)) {
onValidationFailed?.(_failingValidatorIndex);
}

return _isValid;
},
[value, validate, onValidationFailed]);

const onFocus = useCallback((...args: any) => {
setIsFocused(true);
//@ts-expect-error
Expand All @@ -101,10 +108,14 @@ export default function useFieldState({
props.onChangeText?.(text);

if (validateOnChange) {
validateField(text);
if (validationDebounceTime) {
debouncedValidateField(text);
} else {
validateField(text);
}
}
},
[props.onChangeText, validateOnChange, validateField]);
[props.onChangeText, validateOnChange, debouncedValidateField, validateField]);

const fieldState = useMemo(() => {
return {
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export {default as useScrollReached} from './useScrollReached';
export {default as useScrollToItem} from './useScrollToItem';
export {default as useScrollTo} from './useScrollTo';
export {default as useThemeProps} from './useThemeProps';
export {default as useDebounce} from './useDebounce';
export * from './useScrollTo';

20 changes: 20 additions & 0 deletions src/hooks/useDebounce/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {useCallback, useRef} from 'react';

/**
* This hook is used to debounce a function call
*/
function useDebounce<A>(func: (args: A) => void, timeout = 300) {
const handler = useRef<NodeJS.Timeout>();
const debouncedFunction = useCallback((args: A) => {
if (handler.current) {
clearTimeout(handler.current);
}
handler.current = setTimeout(() => {
func(args);
}, timeout);
}, [func, timeout]);

return debouncedFunction;
}

export default useDebounce;