Skip to content

Commit d9f704f

Browse files
authored
TextField - replace withFieldState HOC with useFieldState hook (#925)
* create useFieldState, an alternative for withFieldState HOC * Declare validate typings explicitly on TextField for react-docgen * Code review fixes
1 parent bb89f28 commit d9f704f

File tree

4 files changed

+157
-12
lines changed

4 files changed

+157
-12
lines changed

generatedTypes/incubator/TextField/index.d.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { ValidationMessagePosition } from './types';
55
import { InputProps } from './Input';
66
import { ValidationMessageProps } from './ValidationMessage';
77
import { LabelProps } from './Label';
8-
import { FieldStateProps } from './withFieldState';
8+
import { Validator } from './useFieldState';
99
import { FloatingPlaceholderProps } from './FloatingPlaceholder';
1010
import { CharCounterProps } from './CharCounter';
11-
interface TextFieldProps extends InputProps, LabelProps, FloatingPlaceholderProps, FieldStateProps, ValidationMessageProps, Omit<CharCounterProps, 'maxLength'> {
11+
interface TextFieldProps extends InputProps, LabelProps, FloatingPlaceholderProps, ValidationMessageProps, Omit<CharCounterProps, 'maxLength'> {
1212
/**
1313
* Pass to render a leading button/icon
1414
*/
@@ -25,6 +25,22 @@ interface TextFieldProps extends InputProps, LabelProps, FloatingPlaceholderProp
2525
* Custom style for the floating placeholder
2626
*/
2727
floatingPlaceholderStyle?: TextStyle;
28+
/**
29+
* A single or multiple validator. Can be a string (required, email) or custom function.
30+
*/
31+
validate?: Validator | Validator[];
32+
/**
33+
* Should validate when the TextField mounts
34+
*/
35+
validateOnStart?: boolean;
36+
/**
37+
* Should validate when the TextField value changes
38+
*/
39+
validateOnChange?: boolean;
40+
/**
41+
* Should validate when losing focus of TextField
42+
*/
43+
validateOnBlur?: boolean;
2844
/**
2945
* The position of the validation message (top/bottom)
3046
*/
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { TextInputProps } from 'react-native';
2+
import validators from './validators';
3+
export declare type Validator = Function | keyof typeof validators;
4+
export interface FieldStateProps extends TextInputProps {
5+
validateOnStart?: boolean;
6+
validateOnChange?: boolean;
7+
validateOnBlur?: boolean;
8+
/**
9+
* A single or multiple validator. Can be a string (required, email) or custom function.
10+
*/
11+
validate?: Validator | Validator[];
12+
}
13+
export default function useFieldState({ validate, validateOnBlur, validateOnChange, validateOnStart, ...props }: FieldStateProps): {
14+
onFocus: (...args: any) => void;
15+
onBlur: (...args: any) => void;
16+
onChangeText: (text: any) => void;
17+
fieldState: {
18+
value: string | undefined;
19+
hasValue: boolean;
20+
isValid: boolean;
21+
isFocused: boolean;
22+
};
23+
};

src/incubator/TextField/index.tsx

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React, {useMemo} from 'react';
22
import {ViewStyle, TextStyle} from 'react-native';
3-
import _ from 'lodash';
43
import {
54
asBaseComponent,
65
forwardRef,
@@ -14,10 +13,7 @@ import AccessoryButton from './AccessoryButton';
1413
import ValidationMessage, {ValidationMessageProps} from './ValidationMessage';
1514
import Label, {LabelProps} from './Label';
1615
import FieldContext from './FieldContext';
17-
import withFieldState, {
18-
FieldStateInjectedProps,
19-
FieldStateProps
20-
} from './withFieldState';
16+
import useFieldState, {Validator/* , FieldStateProps */} from './useFieldState';
2117
import FloatingPlaceholder, {
2218
FloatingPlaceholderProps
2319
} from './FloatingPlaceholder';
@@ -27,7 +23,8 @@ interface TextFieldProps
2723
extends InputProps,
2824
LabelProps,
2925
FloatingPlaceholderProps,
30-
FieldStateProps,
26+
// We're declaring these props explicitly here for react-docgen
27+
// FieldStateProps,
3128
ValidationMessageProps,
3229
Omit<CharCounterProps, 'maxLength'> {
3330
/**
@@ -46,6 +43,22 @@ interface TextFieldProps
4643
* Custom style for the floating placeholder
4744
*/
4845
floatingPlaceholderStyle?: TextStyle;
46+
/**
47+
* A single or multiple validator. Can be a string (required, email) or custom function.
48+
*/
49+
validate?: Validator | Validator[];
50+
/**
51+
* Should validate when the TextField mounts
52+
*/
53+
validateOnStart?: boolean;
54+
/**
55+
* Should validate when the TextField value changes
56+
*/
57+
validateOnChange?: boolean;
58+
/**
59+
* Should validate when losing focus of TextField
60+
*/
61+
validateOnBlur?: boolean;
4962
/**
5063
* The position of the validation message (top/bottom)
5164
*/
@@ -62,7 +75,7 @@ interface TextFieldProps
6275

6376
interface InternalTextFieldProps
6477
extends TextFieldProps,
65-
Omit<FieldStateInjectedProps, keyof InputProps>,
78+
// Omit<FieldStateInjectedProps, keyof InputProps>,
6679
ForwardRefInjectedProps {}
6780

6881
interface StaticMembers {
@@ -100,13 +113,13 @@ const TextField = (
100113
// Char Counter
101114
showCharCounter,
102115
charCounterStyle,
103-
// Field State
104-
fieldState,
105116
// Input
106117
placeholder,
107118
...props
108119
}: InternalTextFieldProps
109120
) => {
121+
const {onFocus, onBlur, onChangeText, fieldState} = useFieldState(props);
122+
110123
const context = useMemo(() => {
111124
return {...fieldState, disabled: props.editable === false};
112125
}, [fieldState, props.editable]);
@@ -141,6 +154,9 @@ const TextField = (
141154
)}
142155
<Input
143156
{...props}
157+
onFocus={onFocus}
158+
onBlur={onBlur}
159+
onChangeText={onChangeText}
144160
placeholder={floatingPlaceholder ? undefined : placeholder}
145161
hint={hint}
146162
/>
@@ -173,5 +189,5 @@ TextField.displayName = 'Incubator.TextField';
173189
TextField.validationMessagePositions = ValidationMessagePosition;
174190

175191
export default asBaseComponent<TextFieldProps, StaticMembers>(
176-
forwardRef(withFieldState(TextField as any))
192+
forwardRef(TextField as any)
177193
);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import {useCallback, useState, useEffect, useMemo} from 'react';
2+
import {TextInputProps} from 'react-native';
3+
import _ from 'lodash';
4+
import validators from './validators';
5+
6+
export type Validator = Function | keyof typeof validators;
7+
8+
export interface FieldStateProps extends TextInputProps {
9+
validateOnStart?: boolean;
10+
validateOnChange?: boolean;
11+
validateOnBlur?: boolean;
12+
/**
13+
* A single or multiple validator. Can be a string (required, email) or custom function.
14+
*/
15+
validate?: Validator | Validator[];
16+
}
17+
18+
export default function useFieldState({
19+
validate,
20+
validateOnBlur,
21+
validateOnChange,
22+
validateOnStart,
23+
...props
24+
}: FieldStateProps) {
25+
const [value, setValue] = useState(props.value);
26+
const [isFocused, setIsFocused] = useState(false);
27+
const [isValid, setIsValid] = useState(true);
28+
29+
useEffect(() => {
30+
if (validateOnStart) {
31+
validateField();
32+
}
33+
}, []);
34+
35+
const validateField = useCallback(
36+
(valueToValidate = value) => {
37+
let _isValid = true;
38+
if (_.isFunction(validate)) {
39+
_isValid = validate(valueToValidate);
40+
} else if (_.isString(validate)) {
41+
_isValid = _.invoke(validators, validate, valueToValidate);
42+
}
43+
44+
setIsValid(_isValid);
45+
},
46+
[value, validate]
47+
);
48+
49+
const onFocus = useCallback(
50+
(...args: any) => {
51+
setIsFocused(true);
52+
_.invoke(props, 'onFocus', ...args);
53+
},
54+
[props.onFocus]
55+
);
56+
57+
const onBlur = useCallback(
58+
(...args: any) => {
59+
setIsFocused(false);
60+
_.invoke(props, 'onBlur', ...args);
61+
if (validateOnBlur) {
62+
validateField();
63+
}
64+
},
65+
[props.onBlur, validateOnBlur, validateField]
66+
);
67+
68+
const onChangeText = useCallback(
69+
(text) => {
70+
setValue(text);
71+
_.invoke(props, 'onChangeText', text);
72+
73+
if (validateOnChange) {
74+
validateField(text);
75+
}
76+
},
77+
[props.onChangeText, validateOnChange, validateField]
78+
);
79+
80+
const fieldState = useMemo(() => {
81+
return {value, hasValue: !_.isEmpty(value), isValid, isFocused};
82+
}, [value, isFocused, isValid]);
83+
84+
return {
85+
onFocus,
86+
onBlur,
87+
onChangeText,
88+
fieldState
89+
};
90+
}

0 commit comments

Comments
 (0)