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;
         }