Skip to content

[New Arch] TextInput text change flickers if within Portal #4580

Open

Description

@codan84

Current behaviour

Under new arch any TextInput component (be it from react-native-paper or from react-native) within Portal (modal or dialog) will flicker when text is being changed.

Expected behaviour

Editing text is smooth.

How to reproduce?

Clone this repo: https://github.com/codan84/text-input-flicker

npm install
npm start

Open in Expo Go.

Preview

ScreenRecording_12-19-2024.20-43-14_1.mov

What have you tried so far?

Nothing, I see loads of issues related to Portal under new arch so I guess this is another one?

Your Environment

software version
ios 18.1.1
android 10
react-native 0.76.5
react-native-paper 5.12.5
expo sdk 52

Activity

  1. Ademivaldo commented on Dec 20, 2024

    @Ademivaldo

    same problem

  2. BrandonHowe commented on Dec 26, 2024

    @BrandonHowe

    Same problem. Does anyone know a fix?

  3. codan84 commented on Dec 26, 2024

    @codan84
    Author

    There's no fix atm afaik

  4. codan84 commented on Jan 2, 2025

    @codan84
    Author

    For the time being I decided to just use standard react-native modal and add semi-transparrent background etc (bugs like that must not stop from going to prod), might actually stick to this as it's much simpler than paper approach:

    import { PropsWithChildren } from "react";
    import { Modal, StyleSheet, View, ViewStyle } from 'react-native'
    import Animated, { useAnimatedKeyboard, useAnimatedStyle } from "react-native-reanimated";
    import { useSafeAreaInsets } from "react-native-safe-area-context";
    import { ThemedView } from "./ThemedView";
    
    type ModalProps = {
      visible: boolean,
      hideDialog: () => void,
      contentStyle?: ViewStyle
    }
    type Props = PropsWithChildren<ModalProps>
    
    export const AnimatedModal = ({ visible, hideDialog, children, contentStyle }: Props) => {
      const insets = useSafeAreaInsets()
      const keyboard = useAnimatedKeyboard()
    
      const animatedStyles = useAnimatedStyle(() => ({
        transform: [{ translateY: -keyboard.height.value }]
      }))
    
      return (
        <Modal
          animationType='fade'
          transparent={true}
          visible={visible}
          onDismiss={hideDialog}
        >
          <View style={styles.wrapper} onTouchEnd={hideDialog}>
            <Animated.View style={[ animatedStyles, styles.inner ]}>
              <ThemedView style={[ styles.content, { paddingBottom: insets.bottom }, contentStyle ]} onTouchEnd={(e) => e.stopPropagation() }>
                {children}
              </ThemedView>
            </Animated.View>
          </View>
        </Modal>
      )
    }
    
    const styles = StyleSheet.create({
      wrapper: {
        position: 'absolute',
        bottom: 0,
        top: 0,
        left: 0,
        right: 0
      },
      inner: {
        width: '100%',
        height: '100%',
        flexDirection: 'row',
        alignItems: 'flex-end',
        backgroundColor: 'rgba(230, 230, 230, 0.7)'
      },
      content: {
        minHeight: '30%',
        width: '100%',
        padding: 10,
        paddingTop: 15,
        borderTopLeftRadius: 15,
        borderTopRightRadius: 15
      }
    })
    
  5. tonihm96 commented on Jan 3, 2025

    @tonihm96

    Have you tried using a ref to store text input instead?

    export default function HomeScreen() {
      const [ showModal, setShowModal ] = useState(false)
      const [ showDialog, setShowDialog ] = useState(false)
      const [ text, setText ] = useState('')
    
      const dialogText = useRef('')
      const modalText = useRef('')
    
      const handleDialogTextChange = useCallback((text: string) => {
        dialogText.current = text;
      }, []);
    
      const handleModalTextChange = useCallback((text: string) => {
        modalText.current = text;
      }, []);
    
      return (
        <SafeAreaProvider>
          <PaperProvider>
            <SafeAreaView>
              <Text>This input works fine:</Text>
              <TextInput value={text} onChangeText={setText} />
              <Button mode='outlined' onPress={() => setShowModal(true)}>Show modal</Button>
              <Button mode='outlined' onPress={() => setShowDialog(true)}>Show dialog</Button>
            </SafeAreaView>
            <Portal>
              <Modal
                visible={showModal}
                onDismiss={() => setShowModal(false)}
                contentContainerStyle={styles.modalStyle}>
                <Text>This input flickers:</Text>
                <TextInput defaultValue='' onChangeText={handleModalTextChange} />
              </Modal>
              <Dialog visible={showDialog} onDismiss={() => setShowDialog(false)}>
                <Dialog.Content>
                  <Text>This input flickers:</Text>
                  <TextInput defaultValue='' onChangeText={handleDialogTextChange} />
                </Dialog.Content>
              </Dialog>
            </Portal>
          </PaperProvider>
        </SafeAreaProvider>
      );
    }
  6. codan84 commented on Jan 4, 2025

    @codan84
    Author

    Have you tried using a ref to store text input instead?

    export default function HomeScreen() {
      const [ showModal, setShowModal ] = useState(false)
      const [ showDialog, setShowDialog ] = useState(false)
      const [ text, setText ] = useState('')
    
      const dialogText = useRef('')
      const modalText = useRef('')
    
      const handleDialogTextChange = useCallback((text: string) => {
        dialogText.current = text;
      }, []);
    
      const handleModalTextChange = useCallback((text: string) => {
        modalText.current = text;
      }, []);
    
      return (
        <SafeAreaProvider>
          <PaperProvider>
            <SafeAreaView>
              <Text>This input works fine:</Text>
              <TextInput value={text} onChangeText={setText} />
              <Button mode='outlined' onPress={() => setShowModal(true)}>Show modal</Button>
              <Button mode='outlined' onPress={() => setShowDialog(true)}>Show dialog</Button>
            </SafeAreaView>
            <Portal>
              <Modal
                visible={showModal}
                onDismiss={() => setShowModal(false)}
                contentContainerStyle={styles.modalStyle}>
                <Text>This input flickers:</Text>
                <TextInput defaultValue='' onChangeText={handleModalTextChange} />
              </Modal>
              <Dialog visible={showDialog} onDismiss={() => setShowDialog(false)}>
                <Dialog.Content>
                  <Text>This input flickers:</Text>
                  <TextInput defaultValue='' onChangeText={handleDialogTextChange} />
                </Dialog.Content>
              </Dialog>
            </Portal>
          </PaperProvider>
        </SafeAreaProvider>
      );
    }

    Thanks for suggestion. No I haven't. And I probably won't. I am reporting the bug here so that it can be tracked. I won't be using paper's Modals/Portals. I decided in favour of my alternative approach above.

  7. Prajwaltechversant commented on Jan 30, 2025

    @Prajwaltechversant

    I am having the same issue with the new architecture,

    "react-native-paper": "^5.13.1",
    "react-native": "0.76.6",

  8. MarcusLorenzana commented on Feb 20, 2025

    @MarcusLorenzana

    Same issue with me on latest react-native-paper.

  9. Blubl commented on Mar 12, 2025

    @Blubl

    I had the same issue, for me the flickering does not happen, when not passing value to the TextInput instead only passing onChangeText as in the example from @tonihm96 but is also works without using a ref

  10. maxymczech commented on Mar 30, 2025

    @maxymczech

    I remember encountering this issue about a year ago, I opted for uncontrolled TextInput back then. Just ran into this in another application. I guess it hasn't been solved yet...

  11. FSpark commented on May 22, 2025

    @FSpark

    It seems that this won't happen after upgrading to Expo 53.

  12. jfries289 commented on May 30, 2025

    @jfries289

    Trying to "upgrade" my text input modals to match our other modal implementations with react-native-paper and then spent the day debugging why my text inputs are continuously flickering:

    flicker.mov

    The flicker does not occur if I don't use a Portal component. Nor does it happen if I use Portal.Host.

    onChangeText is being called repeatedly with the new value, then the original, then the new, ad infinitum.

    My component code:

    export const TextAreaModal: FC<TextAreaModalProps> = (props) => {
      const {
        isVisible,
        setModalVisible,
        onSaveText,
        value = "",
      } = props
    
      const [localText, setLocalText] = useState(value)
    
      const onChangeText = (text) => {
        console.log("onChangeText", text)
        setLocalText(text)
      }
      
      const closeModal = () => {
        onSaveText?.(localText)
        setModalVisible?.(false)
      }
    
      return (
        <Portal>
          <Modal
            visible={isVisible}
            onDismiss={closeModal}
            contentContainerStyle={styles.modalContentContainer}
            testID="modal-component"
            style={styles.modal}
            theme={{ isV3: false }}
          >
            <View style={styles.modalBody}>
              <TextInput
                onChangeText={onChangeText}
                value={localText}
                autoFocus
                placeholder="Write a note..."
                multiline
                style={styles.textWrapper}
                inputStyle={styles.textInput}
              />
            </View>
          </Modal>
        </Portal>
      )
    }
  13. jfries289 commented on Jul 15, 2025

    @jfries289

    @lukewalczak Can we get some 👀 on this? This issue keeps popping up for us.
    One further detail, it appears to only happen when multiline is on.

  14. jerrym7 commented on Aug 31, 2025

    @jerrym7

    Try autoCorrect={false}

  15. markendy commented on May 25, 2026

    @markendy

    Progress never stands still, so perhaps the new solution described in this question will help you! https://stackoverflow.com/questions/79253174

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