react-native-paper
react-native-paper copied to clipboard
[New Arch] TextInput text change flickers if within Portal
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
https://github.com/user-attachments/assets/444d7ce3-c29b-4893-98b4-1322e6b846a9
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 |
same problem
Same problem. Does anyone know a fix?
There's no fix atm afaik
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
}
})
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>
);
}
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.
I am having the same issue with the new architecture,
"react-native-paper": "^5.13.1", "react-native": "0.76.6",
Same issue with me on latest react-native-paper.
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
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...
It seems that this won't happen after upgrading to Expo 53.
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:
https://github.com/user-attachments/assets/f3ec1874-ce1d-4846-a0e3-0ae7f3208ac0
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>
)
}
@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.
Try autoCorrect={false}