In my provided code I am using a flatlist which renders a row element which contains multiple TextInut components. The problem here is when I try to edit the Textinput components which is at the end of the flatlist the keyboard appears then disappears, but when I edit any other row at the beginning of the flatlist it work normally.
I think this is because when I click on a row at the end of the flatlist the keyboard comes up and covers that area so that the device decides after that, that I am not entering any input so it hides the keyboard again. tried using KeyboardAvoidingView but with no success
import {
StyleSheet,
Text,
View,
TextInput,
FlatList,
Pressable,
Animated,
KeyboardAvoidingView,
Platform,
TouchableOpacity,
SafeAreaView,
ActivityIndicator,
} from "react-native";
import React, { memo } from "react";
import { useNavigation } from "@react-navigation/native";
import { useState } from "react";
import { Entypo, Feather } from "@expo/vector-icons";
import { useRef, useEffect } from "react";
import { Formik } from "formik";
import axios from "axios";
import * as SecureStore from "expo-secure-store";
import { BlurView } from "expo-blur";
function convertToArabicNumbers(input) {
const englishToArabic = {
0: "٠",
1: "١",
2: "٢",
3: "٣",
4: "٤",
5: "٥",
6: "٦",
7: "٧",
8: "٨",
9: "٩",
};
return input.replace(/\d/g, (digit) => englishToArabic[digit]);
}
export default function Customers() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [tableData, setTableData] = useState([]);
const [message, setMessage] = useState();
const shakeAnimation = useRef(new Animated.Value(0)).current;
const borderWidth = useRef(new Animated.Value(1)).current; // Initial border width
const color = useRef(new Animated.Value(0)).current;
const [name, setName] = useState("");
const [phoneNumber, setPhoneNumber] = useState("");
const [loading, setLoading] = useState(true);
useEffect(() => {
const token = SecureStore.getItem("token");
axios
.get("http://54.224.129.156:5001/api/client/", {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
setTableData(res.data.clients);
})
.catch((error) => {
console.error("Error making the request:", error);
})
.finally(() => {
setLoading(false);
});
}, []);
const triggerShake = () => {
shakeAnimation.setValue(0);
Animated.sequence([
Animated.timing(shakeAnimation, {
toValue: 10,
duration: 50,
useNativeDriver: true,
}),
Animated.timing(shakeAnimation, {
toValue: -10,
duration: 50,
useNativeDriver: true,
}),
Animated.timing(shakeAnimation, {
toValue: 10,
duration: 50,
useNativeDriver: true,
}),
Animated.timing(shakeAnimation, {
toValue: 0,
duration: 50,
useNativeDriver: true,
}),
]).start();
};
const handleAddTableData = async () => {
setMessage("");
if (editOn) {
setMessage("الرجاء إنهاء التعديلات أولا");
triggerShake();
return;
}
if (name === "" || phoneNumber === "") {
setMessage("الرجاء ملء جميع الحقول");
triggerShake();
return;
}
try {
const token = await SecureStore.getItemAsync("token");
if (!token) {
setMessage("الرجاء تسجيل الدخول");
triggerShake();
return;
}
const headers = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
};
const createRes = await axios.post(
"http://54.224.129.156:5001/api/client/create",
{},
{ headers }
);
const id = createRes.data.client._id;
await axios.put(
"http://54.224.129.156:5001/api/client/update",
{ name: name, _id: id },
{ headers }
);
await axios.put(
"http://54.224.129.156:5001/api/client/update",
{ phoneNumber: phoneNumber, _id: id },
{ headers }
);
setTableData([...tableData, { _id: id, name, phoneNumber }]);
setName("");
setPhoneNumber("");
flatListRef.current.scrollToEnd();
} catch (error) {
setMessage("حدث خطأ ما");
triggerShake();
}
};
const handlePressIn = () => {
Animated.timing(color, {
toValue: 1,
duration: 150, // Duration of the animation in milliseconds
useNativeDriver: false, // Set to true if using Native Driver (for performance)
}).start();
Animated.timing(borderWidth, {
toValue: 5, // Increased border width on press in
duration: 150,
useNativeDriver: false,
}).start();
};
const handlePressOut = () => {
Animated.timing(color, {
toValue: 0,
duration: 150,
useNativeDriver: false,
}).start();
Animated.timing(borderWidth, {
toValue: 1, // Original border width on press out
duration: 150,
useNativeDriver: false,
}).start();
};
// Interpolate background color
const animatedColor = color.interpolate({
inputRange: [0, 1],
outputRange: ["black", "#2ec089"],
});
const flatListRef = useRef(null);
if (loading) {
return (
<SafeAreaView style={{ flex: 1 }}>
<View
style={{ flex: 1, alignSelf: "center", justifyContent: "center" }}
>
<ActivityIndicator size="large" color="#2ec089" />
<Text>Loading...</Text>
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "white" }}>
<Text
style={{
fontSize: 40,
fontWeight: "bold",
marginRight: 20,
marginTop: 20,
alignSelf: "flex-end",
}}
>
اهلاً {SecureStore.getItem("name").split(" ")[0]}
</Text>
<Text
style={{
marginTop: 20,
fontSize: 15,
paddingHorizontal: 10,
fontWeight: "bold",
color: "#0b935f",
alignSelf: "center",
borderColor: "#2ec089",
borderWidth: 1,
borderBottomWidth: 0,
borderTopRightRadius: 10,
borderTopLeftRadius: 10,
}}
>
عدد العملاء: {convertToArabicNumbers(tableData.length.toString())}
</Text>
<View
style={{
width: "90%",
flexDirection: "row-reverse",
alignSelf: "center",
alignItems: "center",
borderWidth: 1,
padding: 5,
borderTopRightRadius: 10,
borderTopLeftRadius: 10,
height: "auto",
borderColor: "#2ec089",
}}
>
<Text style={styles.rowText}>الترتيب</Text>
<Text style={styles.rowText}>الاسم</Text>
<Text style={styles.rowText}>رقم الهاتف</Text>
<Feather
name="edit"
size={22}
color="#ffffff00"
style={{ marginLeft: 5 }}
/>
</View>
<FlatList
ref={flatListRef}
centerContent={true}
style={[styles.tableContainerStyle]}
data={tableData}
initialNumToRender={12}
maxToRenderPerBatch={12}
windowSize={12}
automaticallyAdjustKeyboardInsets={true}
keyboardShouldPersistTaps="handled"
automaticallyAdjustsScrollIndicatorInsets={true}
removeClippedSubviews={true}
keyboardDismissMode="none"
renderItem={(itemData) => {
return (
<TableRow
item={[itemData.item, itemData.index]}
setTableData={setTableData}
tableData={tableData}
setMessage={setMessage}
triggerShake={triggerShake}
flatListRef={flatListRef}
/>
);
}}
contentContainerStyle={styles.tableContentContainerStyle}
/>
<View
style={{
width: "90%",
flexDirection: "row-reverse",
alignSelf: "center",
alignItems: "center",
borderWidth: 1,
borderTopWidth: 0,
padding: 5,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
height: "auto",
borderColor: "#2ec089",
}}
>
<Text style={styles.rowText}>
{convertToArabicNumbers((tableData.length + 1).toString())}
</Text>
<TextInput
placeholder="ـ ـ ـ"
textAlign="center"
style={styles.rowText}
value={name}
onChangeText={setName}
onEndEditing={(text) => setName(text.nativeEvent.text.trim())}
multiline={true}
/>
<TextInput
placeholder="ـ ـ ـ"
textAlign="center"
style={styles.rowText}
value={phoneNumber}
onChangeText={setPhoneNumber}
onEndEditing={(text) =>
setPhoneNumber(text.nativeEvent.text.trim())
}
multiline={true}
/>
<Feather
name="edit"
size={22}
color="#ffffff00"
style={{ marginLeft: 5 }}
/>
</View>
<Animated.View
style={[
{
borderRadius: 100,
alignSelf: "center",
marginBottom: 10,
marginTop: 20,
},
{ borderWidth },
]}
>
<Pressable
style={{
padding: 20,
borderRadius: 100,
}}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
onPress={handleAddTableData}
>
<Animated.Text style={{ color: animatedColor }}>
<Entypo
name="add-user"
size={32}
style={{ alignSelf: "center" }}
/>
</Animated.Text>
</Pressable>
</Animated.View>
{isSubmitting ? (
<view style={{ alignSelf: "center", marginBottom: 20 }}>
<ActivityIndicator
size="large"
color="#2ec089"
style={{ marginBottom: 20 }}
/>
</view>
) : (
<Animated.View
style={{
transform: [{ translateX: shakeAnimation }],
alignSelf: "center",
marginBottom: 20,
}}
>
<Text style={{ color: "red" }}>{message}</Text>
</Animated.View>
)}
</SafeAreaView>
);
}
let editOn = false;
const TableRow = memo(
({
item,
setTableData,
tableData,
setMessage,
triggerShake,
flatListRef,
}) => {
const navigation = useNavigation();
const [editable, setEditable] = useState(false);
const color = useRef(new Animated.Value(0)).current;
const handleEdit = async () => {
setMessage("");
if (editable) {
if (item[0].name === "" || item[0].phoneNumber === "") {
setMessage("الرجاء ملء جميع الحقول");
triggerShake();
return;
}
Animated.timing(color, {
toValue: 0,
duration: 150,
useNativeDriver: false,
}).start();
const headers = {
Authorization: `Bearer ${SecureStore.getItem("token")}`,
"Content-Type": "application/json",
};
editOn = false;
setEditable(false);
axios.put(
"http://54.224.129.156:5001/api/client/update",
{ name: item[0].name, _id: item[0]._id },
{ headers }
);
axios.put(
"http://54.224.129.156:5001/api/client/update",
{ phoneNumber: item[0].phoneNumber, _id: item[0]._id },
{ headers }
);
} else {
if (!editOn) {
Animated.timing(color, {
toValue: 1,
duration: 150, // Duration of the animation in milliseconds
useNativeDriver: false, // Set to true if using Native Driver (for performance)
}).start();
editOn = true;
setEditable(true);
} else {
setMessage("الرجاء إنهاء التعديلات الحالية أولا");
triggerShake();
}
}
};
const animatedColor = color.interpolate({
inputRange: [0, 1],
outputRange: ["black", "#2ec089"],
});
return (
<View
style={{
width: "90%",
flexDirection: "row-reverse",
alignSelf: "center",
alignItems: "center",
borderWidth: 1,
borderTopWidth: 0,
padding: 5,
height: "auto",
borderColor: "#2ec089",
}}
>
<TouchableOpacity
style={{
flexDirection: "row-reverse",
flexShrink: 1,
alignItems: "center",
}}
onPress={() => {
setMessage("");
if (editOn) {
setMessage("الرجاء إنهاء التعديلات أولا");
triggerShake();
return;
}
navigation.navigate("المشاريع");
SecureStore.setItemAsync("customerId", item[0]._id);
SecureStore.setItemAsync("customerName", item[0].name);
SecureStore.setItemAsync("customerPhone", item[0].phoneNumber);
}}
>
<Text style={styles.rowText}>
{convertToArabicNumbers((item[1] + 1).toString())}
</Text>
<TextInput
placeholder="ـ ـ ـ"
textAlign="center"
style={styles.rowText}
value={item[0].name}
textContentType="name"
onFocus={() => {
flatListRef.current?.scrollToIndex({
index: item[1],
animated: true,
});
}}
editable={editable}
onChangeText={(text) => {
const newData = [...tableData];
newData[item[1]].name = text;
setTableData(newData);
}}
onEndEditing={(text) => {
const newData = [...tableData];
newData[item[1]].name = text.nativeEvent.text.trim();
setTableData(newData);
}}
multiline={true}
/>
<TextInput
placeholder="ـ ـ ـ"
textAlign="center"
style={styles.rowText}
value={item[0].phoneNumber}
textContentType="telephoneNumber"
onFocus={() => {
flatListRef.current?.scrollToIndex({
index: item[1],
animated: true,
});
}}
editable={editable}
onChangeText={(text) => {
const newData = [...tableData];
newData[item[1]].phoneNumber = text;
setTableData(newData);
}}
onEndEditing={(text) => {
const newData = [...tableData];
newData[item[1]].phoneNumber = text.nativeEvent.text.trim();
setTableData(newData);
}}
multiline={true}
/>
</TouchableOpacity>
<Pressable style={{ paddingLeft: 5 }} onPress={handleEdit}>
<Animated.Text style={{ color: animatedColor }}>
<Feather
name={editable ? "save" : "edit"}
size={22}
style={{ marginLeft: 5 }}
/>
</Animated.Text>
</Pressable>
</View>
);
}
);
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "white",
alignItems: "center",
justifyContent: "flex-start",
},
tableContainerStyle: {
width: "100%",
alignSelf: "center",
flexGrow: 0,
flexShrink: 1,
},
tableContentContainerStyle: {
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
},
rowText: {
flex: 1,
textAlign: "center",
textShadowOffset: { width: 0.5, height: 0.5 },
textShadowRadius: 1,
fontSize: 16,
color: "black",
},
});
expected behavior is to be able to edit all elements without the keyboard disappearing.
Try removeClippedSubviews={false}
or use a ScrollView
The FlatList will remove items subviews that covered by keyboard for better performance https://reactnative.dev/docs/flatlist#removeclippedsubviews.