Skip to content

Keyboard Flickering on TextInput with secureTextEntry #39411

Description

@gcon97

Description

If you have 2 text inputs, (One with secure text entry enabled and the other set to disabled), When typing, highlighting and scrolling in the non-secure text entry, the keyboard will flicker.
This is an iOS issue only, I have tested this using iOS 17, on an iPhone 13 mini. I cannot repeat this issue in the simulator, only on the device.

It appears to be when iOS recognizes it as a login field, so it presents the auto-fill passwords UI. See the attached video on the flickering issue described.

RPReplay_Final1694522683.mov

React Native Version

0.72.4

Output of npx react-native info

System:
OS: macOS 13.4.1
CPU: (10) arm64 Apple M1 Pro
Memory: 115.16 MB / 16.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 20.3.1
path: /opt/homebrew/bin/node
Yarn:
version: 1.22.19
path: /usr/local/bin/yarn
npm:
version: 9.6.7
path: /opt/homebrew/bin/npm
Watchman:
version: 2023.06.12.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.11.3
path: /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 22.4
- iOS 16.4
- macOS 13.3
- tvOS 16.4
- watchOS 9.4
Android SDK:
API Levels:
- "28"
- "31"
- "32"
- "33"
Build Tools:
- 30.0.2
- 30.0.3
- 32.0.0
- 33.0.0
- 34.0.0
System Images:
- android-30 | Google Play ARM 64 v8a
- android-33 | Google APIs ARM 64 v8a
- android-33 | Google Play ARM 64 v8a
- android-34 | Google APIs ARM 64 v8a
Android NDK: Not Found
IDEs:
Android Studio: 2022.1 AI-221.6008.13.2211.9619390
Xcode:
version: 14.3.1/14E300b
path: /usr/bin/xcodebuild
Languages:
Java:
version: 18.0.1.1
path: /usr/bin/javac
Ruby:
version: 2.6.10
path: /usr/bin/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.2.0
wanted: 18.2.0
react-native:
installed: 0.72.4
wanted: 0.72.4
react-native-macos: Not Found
npmGlobalPackages:
"react-native": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: true
newArchEnabled: false

Steps to reproduce

Have 2 TextInput components inside a view, one set with secureTextEntry, one without.

This issue is only seen on device, not in simulator

Snack, screenshot, or link to a repository

https://github.com/gcon97/KeyboardFlickeringDemo

Activity

  1. tianlinjill commented on Sep 15, 2023

    @tianlinjill

    I also encountered the similar issue when in login screen

  2. ShadReyes commented on Sep 19, 2023

    @ShadReyes

    I am experiencing the same on login screens. It's a newer issue. It seems to be caused by the suggested autofill.

    The problem is reduce by using default value prop passed to TextInput component that holds its own text ref and passes out ref value on change.

  3. ShadReyes commented on Sep 19, 2023

    @ShadReyes

    I figured out the problem

    It appears to be that at some point recently (pre iOS 17) a bug was introduced. Whenever secureTextEntry={false} and textContentType is set, various Text Input changes (typing, focus, etc...) will fire a native keyboardWillChangeFrame event that causes the keyboard to re-render. This also causes iOS's AutoFill QuickType Bar (forgive me if my terminology is incorrect) to flicker.

    This can be especially problematic for any RN apps using KeyboardAvoidingView with iOS behavior={'position'} since it will cause a layout flicker as it adjusts to the disappearing and reappearing AutoFill QuickType Bar.

    You can actually see the issue in native iOS apps:

    1. Go to the login screen of a native iOS app.
    2. Change focus from username and password fields.
    3. In many apps you can see a slight bounce as the keyboard animations handle the AutoFill QuickType Bar flicker.

    For those who are using KeyboardAvoidingView (RN V0.72.* Patch Fix)

    For those who are wrapping their text inputs in a <KeyboardAvoidingView behavior= 'position'}> and <ScrollView>, you are likely dealing with your screen jumping on text change and focus change.

    This isn't a pretty solution (and keep in mind it does not fix the root issue of the keyboard re-render), but you can do the following **TEMPORARY and NOT fully tested** fix for this issue:

    1. Go to the file at: node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
    2. Add ignoreIOSKeyboardWillChangeEvents?: boolean | undefined; to the KeyboardAvoidingView props.
    3. Go to the types file at: node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.d.ts
    4. Add ignoreIOSKeyboardWillChangeEvents?: boolean | undefined; to the KeyboardAvoidingView props.
    5. Replace the const {duration, easing, endCoordinates} = this._keyboardEvent; line with the following:
    const {duration, easing, endCoordinates} = this._keyboardEvent;
    
    if(Platform.OS === 'ios' && duration === 0 && !!this.props.ignoreIOSKeyboardWillChangeEvents ) {
          return;
    }
    
    1. Use patch package to save the changes.

    be sure to set ignoreIOSKeyboardWillChangeEvents={true} with care. I, for example, am only using it on a login screen for a very specific keyboard avoiding use case scenario that I detail above. Hope this helps.

  4. ThorANilsson commented on Oct 3, 2023

    @ThorANilsson
    RPReplay-Final1696326197.mov

    I began seeing this after updating the device i test on (iPhone 14 Pro) to iOS 17. Using Expo Go. Very annoying...

    What's interesting is that I am not experiencing this on another iPhone 11 Pro Max, also running iOS 17.

  5. ThorANilsson commented on Oct 3, 2023

    @ThorANilsson

    I believe this is caused by the "Passwords"-thing (see attached image) that appears on iOS when the OS believes you want to use a password from your keychain.

    When I test on a phone that does not have a keychain, there is no jumping or flickering.
    IMG-4401

  6. gcon97 commented on Oct 3, 2023

    @gcon97
    Author

    I can confirm that it is an issue with the Passwords option in the Keyboard toolbar. Seems to be an issue across iOS 17 rather than a ReactNative issue. (It also isn't fixed in iOS 17.1, so we may be waiting a while for a fix).

    Closing as it isn't a React Native-specific issue.

  7. ThorANilsson commented on Oct 4, 2023

    @ThorANilsson

    Adding textContentType='oneTimeCode' to your TextInput disables the Password toolbar, which also eliminates the described issue.

  8. Shubham0850 commented on Oct 12, 2023

    @Shubham0850

    @ThorANilsson I tried this, but it's not working for me.

  9. mkhoussid commented on Oct 12, 2023

    @mkhoussid

    Here's what I did.

    TL;DR: If it's a password field, on each change update the actual string value in a map, and then render the value as bullets ('•'). What we're essentially doing here is saying that all of our fields are just regular fields without autofill, but we can manipulate the value of the field, if it's a password field, if we pass a certain flag.

    Somewhere created a file called maskMap.ts with the following:

    export const maskMap = new Map<string, string>();

    And in your custom TextField component (I'm assuming most of you have wrapped TextInput into one of your own TextField components):

    import * as React from 'react';
    import { TextInput } from 'react-native-paper';
    import { maskMap } from './maskMap';
    
    const PASSWORD_MASK_CHAR = '•';
    . . .
    // secureTextEntry determines whether or not this is a password field
    const TextField = React.memo(({ onChange, secureTextEntry, ...props }: YourTextFieldProps) => {
      const [textFieldId] = React.useState(uuid()); // use whatever uuid generator you'd like
    . . .
      React.useEffect(() => {
    	  maskMap.set(textFieldId, '');
    
    	  return () => {
    		  maskMap.delete(textFieldId);
    	  };
      }, [secureTextEntry]);
    
      const handleChange = React.useCallback((e: string) => {
    	  if (secureTextEntry) {
    		  const previousValue = maskMap.get(textFieldId) as string;
    		  const formattedNewValue = e.replace(new RegExp(PASSWORD_MASK_CHAR, 'g'), '');
    
    		  const newValue = previousValue + formattedNewValue;
    		  onChange(newValue);
    		  maskMap.set(textFieldId, previousValue + formattedNewValue);
    	  } else {
    		  onChange(e);
    	  }
      }, [secureTextEntry]);
    
      const getValue = React.useCallback(() => {
    	  if (!secureTextEntry) {
    		  return value;
    	  }
    
    	  return value.replaceAll(/./g, PASSWORD_MASK_CHAR);
      }, [value, secureTextEntry, showText, maskMap]);
    . . .
    
        return (
            . . .
                <TextInput
                    value={getValue()}
                    onChangeText={handleChange}
                    textContentType='none' // add this
                    . . .
                />
            . . .
        )
    })
  10. Engazan commented on Nov 17, 2023

    @Engazan

    how this can be closed if its still happening ?

    it happends all the time when you have textContentType="password" or secureTextEntry

  11. 84 remaining items

  12. KoenCa commented on Apr 15, 2025

    @KoenCa

    This Flutter issue also is about this iOS bug and apparently they recently fixed this. Looks like they set autoCorrect to false when username or password related autofill hints are enabled. I've also been debugging this issue for some time and adding autoCorrect={false} to my username input also fixed the flickering and UI shifting. Still not a real fix, because it is an iOS issue.

  13. Xavirocha09 commented on Apr 18, 2025

    @Xavirocha09

    maybe this will help someone but I had this nightmare of an issue for 3 days straight tried everything but the solution was to remove any unmounted effects that were left from other screens I was pushing from. The fix in my case was that instead of using .push() I used .replace() this unmounted any unattended effects that were causing the screen to flicker. Seriously as soon as I replaced it it worked flawlessly.

  14. ababol commented on Jul 22, 2025

    @ababol

    Hello folks 👋

    I also ran into this issue and wanted to preserve the ability to show the "Passwords" autofill from iOS.

    I couldn't find any relevant workarounds online, so I ended up vibe coding my own KeyboardAvoidingView based on the React Native one (behavior="padding").

    It's essentially the same code but with debouncing added 👍

    It's working for me now, and I hope it can help others!

    With the react-native KeyboardAvoidingView:

    keyboardavoidingview_rvz0jTXS.mov

    With the new KeyboardPasswordsAvoidingView:

    keyboardpasswordsavoidingview_sNcU7REh.mov
    import { useEffect, useRef, useState } from "react";
    import {
      AccessibilityInfo,
      Keyboard,
      LayoutAnimation,
      Platform,
      KeyboardAvoidingView as RNKeyboardAvoidingView,
      View,
      ViewProps,
    } from "react-native";
    
    interface KeyboardMetrics {
      screenX: number;
      screenY: number;
      width: number;
      height: number;
    }
    
    interface KeyboardEvent {
      duration?: number;
      easing?: string;
      endCoordinates: KeyboardMetrics;
    }
    
    interface ViewLayout {
      x: number;
      y: number;
      width: number;
      height: number;
    }
    
    interface KeyboardPasswordsAvoidingViewProps extends ViewProps {
      enabled?: boolean;
      keyboardVerticalOffset?: number;
    }
    
    const DEBOUNCE_DELAY = 50; // ms
    
    /**
     * Custom KeyboardAvoidingView specifically designed to handle iOS password
     * autofill bar flicker issues. Uses debouncing to prevent layout jumps
     * caused by rapid keyboard frame changes when iOS shows/hides the password bar.
     *
     * Based on React Native's KeyboardAvoidingView with debouncing logic.
     * iOS only - falls back to regular View on other platforms.
     * Padding behavior only.
     */
    export const KeyboardPasswordsAvoidingView = ({
      enabled = true,
      keyboardVerticalOffset = 0,
      style,
      onLayout,
      children,
      ...props
    }: KeyboardPasswordsAvoidingViewProps) => {
      const [bottom, setBottom] = useState(0);
    
      const frameRef = useRef<ViewLayout | null>(null);
      const keyboardEventRef = useRef<KeyboardEvent | null>(null);
      const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
      const isKeyboardVisibleRef = useRef<boolean>(false);
    
      const relativeKeyboardHeight = async (
        keyboardFrame: KeyboardMetrics,
      ): Promise<number> => {
        const frame = frameRef.current;
        if (!frame || !keyboardFrame) {
          return 0;
        }
    
        // Handle iOS Prefer Cross-Fade Transitions accessibility setting
        if (
          Platform.OS === "ios" &&
          keyboardFrame.screenY === 0 &&
          (await AccessibilityInfo.prefersCrossFadeTransitions())
        ) {
          return 0;
        }
    
        const keyboardY = keyboardFrame.screenY - keyboardVerticalOffset;
    
        // Calculate the displacement needed for the view such that it
        // no longer overlaps with the keyboard (padding behavior only)
        return Math.max(frame.y + frame.height - keyboardY, 0);
      };
    
      const updateBottomIfNecessary = async () => {
        if (!keyboardEventRef.current) {
          setBottom(0);
          return;
        }
    
        const { duration, easing, endCoordinates } = keyboardEventRef.current;
        const height = await relativeKeyboardHeight(endCoordinates);
    
        if (bottom === height) {
          return;
        }
    
        setBottom(height);
    
        // Apply layout animation if enabled and duration is provided
        if (enabled && duration && easing) {
          LayoutAnimation.configureNext({
            duration: duration > 10 ? duration : 10,
            update: {
              duration: duration > 10 ? duration : 10,
              type: (LayoutAnimation.Types as any)[easing] || "keyboard",
            },
          });
        }
      };
    
      const debouncedUpdateBottom = () => {
        // Clear existing timeout
        if (debounceTimeoutRef.current) {
          clearTimeout(debounceTimeoutRef.current);
        }
    
        // Debounce the update
        debounceTimeoutRef.current = setTimeout(() => {
          updateBottomIfNecessary();
        }, DEBOUNCE_DELAY);
      };
    
      const onKeyboardChange = (event: KeyboardEvent | null) => {
        keyboardEventRef.current = event;
    
        // Filter out problematic events with zero duration (common cause of flicker)
        if (event && event.duration === 0) {
          return;
        }
    
        isKeyboardVisibleRef.current = true;
        debouncedUpdateBottom();
      };
    
      const onKeyboardDidShow = (event: KeyboardEvent | null) => {
        // Only trigger if keyboard was already visible - this indicates a focus change
        if (isKeyboardVisibleRef.current && event) {
          keyboardEventRef.current = event;
          // Use debounced update for focus changes to prevent visual glitch
          debouncedUpdateBottom();
        }
      };
    
      const onKeyboardHide = () => {
        keyboardEventRef.current = null;
        isKeyboardVisibleRef.current = false;
        debouncedUpdateBottom();
      };
    
      const handleLayout = async (event: any) => {
        const oldFrame = frameRef.current;
        frameRef.current = event.nativeEvent.layout;
    
        // Update bottom height for the first time or when the height changes
        if (
          !oldFrame ||
          (frameRef.current && oldFrame.height !== frameRef.current.height)
        ) {
          await updateBottomIfNecessary();
        }
    
        if (onLayout) {
          onLayout(event);
        }
      };
    
      useEffect(() => {
        // Only setup listeners on iOS
        if (Platform.OS !== "ios") {
          return;
        }
    
        // Initialize keyboard state
        const keyboardVisible = Keyboard.isVisible();
        isKeyboardVisibleRef.current = keyboardVisible;
        if (!keyboardVisible) {
          keyboardEventRef.current = null;
          setBottom(0);
        }
    
        // Setup iOS keyboard listeners
        const subscriptions = [
          // iOS uses keyboardWillShow/keyboardWillHide for better handling
          // of undocked, split, or floating keyboards
          Keyboard.addListener("keyboardWillHide", onKeyboardHide),
          Keyboard.addListener("keyboardWillShow", onKeyboardChange),
          // Listen for keyboardDidShow to detect focus changes
          Keyboard.addListener("keyboardDidShow", onKeyboardDidShow),
        ];
    
        return () => {
          // Clear debounce timeout on cleanup
          if (debounceTimeoutRef.current) {
            clearTimeout(debounceTimeoutRef.current);
          }
    
          // Remove event listeners
          subscriptions.forEach(subscription => {
            subscription.remove();
          });
        };
      }, []);
    
      // On non-iOS platforms, just return the regular KeyboardAvoidingView
      if (Platform.OS !== "ios") {
        return (
          <RNKeyboardAvoidingView
            style={style}
            keyboardVerticalOffset={keyboardVerticalOffset}
            {...props}
          >
            {children}
          </RNKeyboardAvoidingView>
        );
      }
    
      // Apply padding bottom based on keyboard height
      const bottomHeight = enabled ? bottom : 0;
    
      return (
        <View
          style={[style, { paddingBottom: bottomHeight }]}
          onLayout={handleLayout}
          {...props}
        >
          {children}
        </View>
      );
    };

    and then you can use it just like a basic KeyboardAvoidingView:

    <KeyboardPasswordsAvoidingView keyboardVerticalOffset={20}>
      <SignupForm />
    </KeyboardPasswordsAvoidingView>
  15. Ario99 commented on Aug 28, 2025

    @Ario99

    Still waiting on this issue being properly resolved. I use this patch and update it every time I upgrade to the next react-native version.

  16. ShadReyes commented on Aug 29, 2025

    @ShadReyes

    Still waiting on this issue being properly resolved. I use this patch and update it every time I upgrade to the next react-native version.

    @Ario99 Same, though I am glad the solution still works for you!

  17. Ario99 commented on Aug 29, 2025

    @Ario99

    @ShadReyes thanks for the solution! Would you happen to be using expo sdk 53? I started to upgrade from expo sdk 52 but haven't been able to properly test it yet.

  18. ShadReyes commented on Sep 4, 2025

    @ShadReyes

    @ShadReyes thanks for the solution! Would you happen to be using expo sdk 53? I started to upgrade from expo sdk 52 but haven't been able to properly test it yet.

    @Ario99 I stopped contracting for the company that had the problem described in this issue, so I have not had a chance to test in on Expo 53. Though, I would say it is unlikely that the new Expo version would fix the issues. This is a problem stemming from a native iOS bug that was introduced a little before iOS 17. Though, when I get a second, I can spin up a test app and test it out.

  19. DanielAraldi commented on Sep 19, 2025

    @DanielAraldi

    In my RN project version 0.79.3, using iOS 18.7, this problem persists.

  20. matinzd commented on Nov 9, 2025

    @matinzd
    Contributor

    I can confirm that is also related to Passwords app suggestion on my side as well. When I disable Password & Autofill service in settings everything works properly. For now I have set textContentType="oneTimeCode" for some of the text inputs in the app.

    Simulator.Screen.Recording.-.iPhone.16.-.2025-11-09.at.21.30.22.mov
  21. chizirumizuhara269-rgb commented on Mar 24, 2026

    @chizirumizuhara269-rgb

    @ThorANilsson I tried this, but it's not working for me.

    Yes, that didn't work for me either.

  22. LarsDanau commented on Mar 31, 2026

    @LarsDanau

    Having the same issue, no resolutions yet using Expo SDK 54 using react-native-keyboard-controller.

  23. apolyanov commented on Apr 6, 2026

    @apolyanov

    Issue still persists with latest IOS and Expo SDK

  24. chizirumizuhara269-rgb commented on Apr 10, 2026

    @chizirumizuhara269-rgb

    @apolyanov Flicker was triggered due to autofill in secure entry field. Below is how I resolved the same in my app.

    Set textContentType="none" and autoCorrect={false} on the TextInput to prevent iOS from treating the fields as login credentials and showing the auto-fill bar:

    <TextInput
      placeholder="Username"
      textContentType="none"
      autoCorrect={false}
      // ... other props
    />
    
    <TextInput
      placeholder="Password"
      secureTextEntry
      textContentType="none"
      autoCorrect={false}
      // ... other props
    />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions