0

I have a custom InputField component which uses the React Native Text Input. For some fields, the input will require an X button when the field is focused so user can clear the field. This button is absolutely positioned inside the text input which is why I believe this is not working (as per previous Stack Overflow issues, but the solutions haven't worked for me).

In the same component, I have a check for if the input is the password field which has another icon which works perfectly fine when the keyboard opens up and the field is focused, but despite trying to exactly replicate this for other fields that require the X icon, I can't seem to get the same functionality to get a button to work and be clickable when absolutely positioned inside a text input.

Would point out that I believe the differences between the Text Input with the X icon and the password input is the password inputs is in a modal. And because the password fields are in a modal (modal made with BottomSheetComponent), I am using a custom BottomSheetInputField for this instead of the custom Input Field (same code with minor ref differences) - both attached below

Password input = click on the field, opens modal with 3 password text inputs Inputs with X icon = click on the field, no modal, opens keyboard and changes icon from Pencil to X. Clicking X should clear the field (not working.)

InputField Component:

import React, { useState } from 'react';
import {
 View,
 Text,
 StyleSheet,
 TextInput,
 TouchableOpacity,
 StyleProp,
 TextStyle,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { colors, font, fontSize } from '@/src/constants/AppDesign';
import { XIcon } from '@/src/components/icons';
interface Props {
 label?: string;
 value: string;
 onChangeText: (text: string) => void;
 placeholder?: string;
 keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad';
 autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
 autoComplete?: 'email' | 'password' | 'off' | undefined;
 secureTextEntry?: boolean;
 isPassword?: boolean;
 withSuffix?: boolean;
 suffixIcon?: React.ReactNode;
 containerStyle?: StyleProp<TextStyle>;
 onFocus?: () => void;
 onBlur?: () => void;
 onClear?: () => void;
}
export function InputField({
 label,
 value,
 onChangeText,
 placeholder,
 keyboardType = 'default',
 autoCapitalize = 'none',
 autoComplete,
 secureTextEntry,
 isPassword = false,
 withSuffix = false,
 suffixIcon,
 containerStyle,
 onFocus,
 onBlur,
 onClear,
}: Props) {
 const [showPassword, setShowPassword] = useState(false);
 const renderSuffixIcon = () => {
 if (containerStyle && onClear) {
 return (
 <TouchableOpacity onPress={onClear} style={styles.suffixIcon}>
 <View style={styles.clearIconContainer}>
 <XIcon color={colors.textWhite} width={12} height={12} />
 </View>
 </TouchableOpacity>
 );
 }
 return suffixIcon && <View style={styles.suffixIcon}>{suffixIcon}</View>;
 };
 return (
 <View style={styles.formGroup}>
 {label && <Text style={styles.label}>{label}</Text>}
 {isPassword ? (
 <View style={styles.passwordContainer}>
 <TextInput
 style={[styles.passwordInput, containerStyle]}
 value={value}
 onChangeText={onChangeText}
 secureTextEntry={!showPassword}
 placeholder={placeholder}
 autoCapitalize={autoCapitalize}
 autoComplete={autoComplete}
 onFocus={onFocus}
 onBlur={onBlur}
 />
 <TouchableOpacity
 style={styles.eyeIcon}
 onPress={() => setShowPassword(!showPassword)}>
 <Ionicons
 name={showPassword ? 'eye-off-outline' : 'eye-outline'}
 size={24}
 color={colors.textLighter}
 />
 </TouchableOpacity>
 </View>
 ) : withSuffix ? (
 <View style={styles.suffixContainer}>
 <TextInput
 style={[styles.input, containerStyle]}
 value={value}
 onChangeText={onChangeText}
 placeholder={placeholder}
 keyboardType={keyboardType}
 autoCapitalize={autoCapitalize}
 autoComplete={autoComplete}
 secureTextEntry={secureTextEntry}
 onFocus={onFocus}
 onBlur={onBlur}
 />
 {renderSuffixIcon()}
 </View>
 ) : (
 <TextInput
 style={[styles.input, containerStyle]}
 value={value}
 onChangeText={onChangeText}
 placeholder={placeholder}
 keyboardType={keyboardType}
 autoCapitalize={autoCapitalize}
 autoComplete={autoComplete}
 secureTextEntry={secureTextEntry}
 onFocus={onFocus}
 onBlur={onBlur}
 />
 )}
 </View>
 );
}
const styles = StyleSheet.create({
 formGroup: {
 marginBottom: 14,
 width: '100%',
 },
 label: {
 fontSize: fontSize.xs,
 fontFamily: font.roboto,
 marginBottom: 8,
 color: colors.textLight,
 },
 input: {
 height: 50,
 borderRadius: 8,
 paddingHorizontal: 16,
 backgroundColor: colors.backgroundLight,
 width: '100%',
 fontFamily: font.roboto,
 },
 passwordContainer: {
 position: 'relative',
 width: '100%',
 },
 passwordInput: {
 height: 50,
 borderRadius: 8,
 paddingHorizontal: 16,
 backgroundColor: colors.backgroundLight,
 width: '100%',
 fontFamily: font.roboto,
 },
 suffixContainer: {
 position: 'relative',
 width: '100%',
 },
 suffixIcon: {
 position: 'absolute',
 right: 16,
 top: 15,
 },
 eyeIcon: {
 position: 'absolute',
 right: 12,
 top: 13,
 },
 clearIconContainer: {
 height: 18,
 width: 18,
 backgroundColor: colors.primary,
 borderRadius: 10,
 justifyContent: 'center',
 alignItems: 'center',
 },
});

BottomSheetInputField component:

import React, { forwardRef, useCallback, useState } from 'react';
import {
 View,
 Text,
 StyleSheet,
 TextInput,
 TouchableOpacity,
 StyleProp,
 TextStyle,
 NativeSyntheticEvent,
 TextInputFocusEventData,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { colors, font, fontSize } from '@/src/constants/AppDesign';
import { XIcon } from '@/src/components/icons';
import { useBottomSheetInternal } from '@gorhom/bottom-sheet';
interface Props {
 label?: string;
 value: string;
 onChangeText: (text: string) => void;
 placeholder?: string;
 keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad';
 autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
 autoComplete?: 'email' | 'password' | 'off' | undefined;
 secureTextEntry?: boolean;
 isPassword?: boolean;
 withSuffix?: boolean;
 suffixIcon?: React.ReactNode;
 containerStyle?: StyleProp<TextStyle>;
 onFocus?: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
 onBlur?: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
 onClear?: () => void;
}
export const BottomSheetInputField = forwardRef<TextInput, Props>(
 (
 {
 onFocus,
 onBlur,
 label,
 value,
 onChangeText,
 placeholder,
 keyboardType,
 autoCapitalize,
 autoComplete,
 secureTextEntry,
 isPassword,
 withSuffix,
 suffixIcon,
 containerStyle,
 onClear,
 },
 ref,
 ) => {
 const [showPassword, setShowPassword] = useState(false);
 const { shouldHandleKeyboardEvents } = useBottomSheetInternal();
 const handleOnFocus = useCallback(
 (args: NativeSyntheticEvent<TextInputFocusEventData>) => {
 shouldHandleKeyboardEvents.value = true;
 if (onFocus) {
 onFocus(args);
 }
 },
 [onFocus, shouldHandleKeyboardEvents],
 );
 const handleOnBlur = useCallback(
 (args: NativeSyntheticEvent<TextInputFocusEventData>) => {
 shouldHandleKeyboardEvents.value = false;
 if (onBlur) {
 onBlur(args);
 }
 },
 [onBlur, shouldHandleKeyboardEvents],
 );
 const renderSuffixIcon = () => {
 if (containerStyle && onClear) {
 return (
 <TouchableOpacity onPress={onClear} style={styles.suffixIcon}>
 <View style={styles.clearIconContainer}>
 <XIcon color={colors.textWhite} width={12} height={12} />
 </View>
 </TouchableOpacity>
 );
 }
 return suffixIcon && <View style={styles.suffixIcon}>{suffixIcon}</View>;
 };
 return (
 <View style={styles.formGroup}>
 {label && <Text style={styles.label}>{label}</Text>}
 {isPassword ? (
 <View style={styles.passwordContainer}>
 <TextInput
 ref={ref}
 style={[styles.passwordInput, containerStyle]}
 value={value}
 onChangeText={onChangeText}
 secureTextEntry={!showPassword}
 placeholder={placeholder}
 autoCapitalize={autoCapitalize}
 autoComplete={autoComplete}
 onFocus={handleOnFocus}
 onBlur={handleOnBlur}
 />
 <TouchableOpacity
 style={styles.eyeIcon}
 onPress={() => setShowPassword(!showPassword)}>
 <Ionicons
 name={showPassword ? 'eye-off-outline' : 'eye-outline'}
 size={24}
 color={colors.textLighter}
 />
 </TouchableOpacity>
 </View>
 ) : withSuffix ? (
 <View style={styles.suffixContainer}>
 <TextInput
 ref={ref}
 style={[styles.input, containerStyle]}
 value={value}
 onChangeText={onChangeText}
 placeholder={placeholder}
 keyboardType={keyboardType}
 autoCapitalize={autoCapitalize}
 autoComplete={autoComplete}
 secureTextEntry={secureTextEntry}
 onFocus={handleOnFocus}
 onBlur={handleOnBlur}
 />
 {renderSuffixIcon()}
 </View>
 ) : (
 <TextInput
 ref={ref}
 style={[styles.input, containerStyle]}
 value={value}
 onChangeText={onChangeText}
 placeholder={placeholder}
 keyboardType={keyboardType}
 autoCapitalize={autoCapitalize}
 autoComplete={autoComplete}
 secureTextEntry={secureTextEntry}
 onFocus={handleOnFocus}
 onBlur={handleOnBlur}
 />
 )}
 </View>
 );
 },
);
BottomSheetInputField.displayName = 'BottomSheetInputField';
const styles = StyleSheet.create({
 formGroup: {
 marginBottom: 14,
 width: '100%',
 },
 label: {
 fontSize: fontSize.xs,
 fontFamily: font.roboto,
 marginBottom: 8,
 color: colors.textLight,
 },
 input: {
 height: 50,
 borderRadius: 8,
 paddingHorizontal: 16,
 backgroundColor: colors.backgroundLight,
 width: '100%',
 fontFamily: font.roboto,
 },
 passwordContainer: {
 position: 'relative',
 width: '100%',
 },
 passwordInput: {
 height: 50,
 borderRadius: 8,
 paddingHorizontal: 16,
 backgroundColor: colors.backgroundLight,
 width: '100%',
 fontFamily: font.roboto,
 },
 suffixContainer: {
 position: 'relative',
 width: '100%',
 },
 suffixIcon: {
 position: 'absolute',
 right: 16,
 top: 15,
 },
 eyeIcon: {
 position: 'absolute',
 right: 12,
 top: 13,
 },
 clearIconContainer: {
 height: 18,
 width: 18,
 backgroundColor: colors.primary,
 borderRadius: 10,
 justifyContent: 'center',
 alignItems: 'center',
 },
});

firstName and lastName fields (which have the X icon) in the parent file:

 <View style={styles.nameInputContainer}>
 <View style={styles.nameInputItem}>
 <InputField
 label={t('profile.editProfileScreen.nameLabel')}
 value={values.firstName}
 onChangeText={handleChange('firstName')}
 withSuffix
 suffixIcon={
 selectedField !== 'firstName' ? <PencilIcon /> : undefined
 }
 containerStyle={
 selectedField === 'firstName' ? styles.selectedField : undefined
 }
 onFocus={() => setSelectedField('firstName')}
 onBlur={() => setSelectedField(null)}
 onClear={() => setFieldValue('firstName', '')}
 />
 </View>
 <View style={styles.nameInputItem}>
 <InputField
 label={t('profile.editProfileScreen.surnameLabel')}
 value={values.lastName}
 onChangeText={handleChange('lastName')}
 withSuffix
 suffixIcon={
 selectedField !== 'lastName' ? <PencilIcon /> : undefined
 }
 containerStyle={
 selectedField === 'lastName' ? styles.selectedField : undefined
 }
 onFocus={() => setSelectedField('lastName')}
 onBlur={() => setSelectedField(null)}
 onClear={() => setFieldValue('lastName', '')}
 />
 </View>
 </View>

Password field (which is a custom Select Field but triggers the modal when clicked):

 <View style={[styles.customInputContainer, { marginTop: 12 }]}>
 <View style={styles.customInputTextContainer}>
 <SelectField
 label={t('profile.editProfileScreen.passwordLabel')}
 value={values.password}
 isPassword={true}
 onPress={() => {
 setFieldValue('tempCurrentPassword', '');
 setFieldValue('tempNewPassword', '');
 setFieldValue('tempConfirmPassword', '');
 setSelectedField('password');
 setModalTitle(t('profile.editProfileScreen.changePassword'));
 setCustomModalContentType('password');
 setActiveModalType('custom');
 setIsModalVisible(true);
 }}
 containerStyle={
 selectedField === 'password' ? styles.selectedField : undefined
 }
 />
 </View>
 </View>

password fields inside the modal:

} else if (customModalContentType === 'password') {
 return (
 <View style={styles.customModalContainer}>
 <View style={styles.customModalFieldContainer}>
 <Text style={styles.customModalLabel}>
 {t('profile.editProfileScreen.currentPasswordLabel')}
 </Text>
 <BottomSheetInputField
 isPassword={true}
 value={values.tempCurrentPassword}
 onChangeText={text => setFieldValue('tempCurrentPassword', text)}
 />
 <TouchableOpacity
 style={styles.forgotPasswordContainer}
 onPress={() => {}}>
 <Text style={styles.forgotPassword}>
 {t('login.forgotPassword')}
 </Text>
 </TouchableOpacity>
 </View>
 <View style={styles.customModalFieldContainer}>
 <Text style={styles.customModalLabel}>
 {t('profile.editProfileScreen.newPasswordLabel')}
 </Text>
 <BottomSheetInputField
 isPassword={true}
 value={values.tempNewPassword}
 onChangeText={text => setFieldValue('tempNewPassword', text)}
 />
 </View>
 <View style={styles.customModalFieldContainer}>
 <Text style={styles.customModalLabel}>
 {t('profile.editProfileScreen.repeatNewPasswordLabel')}
 </Text>
 <BottomSheetInputField
 isPassword={true}
 value={values.tempConfirmPassword}
 onChangeText={text => setFieldValue('tempConfirmPassword', text)}
 />
 </View>
 </View>
 );
 }

Things I've tried:

  • Removing absolute positioning completely
  • Removing the conditional rendering to test
  • Using the same styles for the X Icon as I did for the eye Icon

None of this worked, would appreciate some support, thanks.

asked Jun 2, 2025 at 10:08
1
  • Hi and welcome to SO. So what exactly is the problem? Is the button not clickable/disabled? Or is it clickable but it doesn't do anything? Is the code for the click event not running at all? Have you set up a breakpoint? Commented Jun 2, 2025 at 17:08

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.