diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index b469f39c7037..bd6548607cb9 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -24,8 +24,8 @@ import useNativeDriver from '../../libs/useNativeDriver'; import * as Browser from '../../libs/Browser'; function BaseTextInput(props) { - const inputValue = props.value || props.defaultValue || ''; - const initialActiveLabel = props.forceActiveLabel || inputValue.length > 0 || Boolean(props.prefixCharacter); + const initialValue = props.value || props.defaultValue || ''; + const initialActiveLabel = props.forceActiveLabel || initialValue.length > 0 || Boolean(props.prefixCharacter); const [isFocused, setIsFocused] = useState(false); const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry); @@ -145,30 +145,16 @@ function BaseTextInput(props) { [props.autoGrowHeight, props.multiline], ); - useEffect(() => { - // Handle side effects when the value gets changed programatically from the outside - - // In some cases, When the value prop is empty, it is not properly updated on the TextInput due to its uncontrolled nature, thus manually clearing the TextInput. - if (inputValue === '') { - input.current.clear(); - } - - if (inputValue) { - activateLabel(); - } - }, [activateLabel, inputValue]); - - // We capture whether the input has a value or not in a ref. - // It gets updated when the text gets changed. - const hasValueRef = useRef(inputValue.length > 0); + // The ref is needed when the component is uncontrolled and we don't have a value prop + const hasValueRef = useRef(initialValue.length > 0); + const inputValue = props.value || ''; + const hasValue = inputValue.length > 0 || hasValueRef.current; - // Activate or deactivate the label when the focus changes: + // Activate or deactivate the label when either focus changes, or for controlled + // components when the value prop changes: useEffect(() => { - // We can't use inputValue here directly, as it might contain - // the defaultValue, which doesn't get updated when the text changes. - // We can't use props.value either, as it might be undefined. if ( - hasValueRef.current || + hasValue || isFocused || // If the text has been supplied by Chrome autofill, the value state is not synced with the value // as Chrome doesn't trigger a change event. When there is autofill text, keep the label activated. @@ -178,7 +164,16 @@ function BaseTextInput(props) { } else { deactivateLabel(); } - }, [activateLabel, deactivateLabel, inputValue, isFocused]); + }, [activateLabel, deactivateLabel, hasValue, isFocused]); + + // When the value prop gets cleared externally, we need to keep the ref in sync: + useEffect(() => { + // Return early when component uncontrolled, or we still have a value + if (props.value === undefined || !_.isEmpty(props.value)) { + return; + } + hasValueRef.current = false; + }, [props.value]); /** * Set Value & activateLabel @@ -192,9 +187,13 @@ function BaseTextInput(props) { } Str.result(props.onChangeText, value); + if (value && value.length > 0) { hasValueRef.current = true; - activateLabel(); + // When the componment is uncontrolled, we need to manually activate the label: + if (props.value === undefined) { + activateLabel(); + } } else { hasValueRef.current = false; }