Предупреждение: React Native Draggable Flatlist: не удается выполнить обновление состояния React для отключенного компонента
Я пытаюсь реализовать response-native-draggable-flatlist в своем приложении для iOS. Он работает, но я продолжаю получать это предупреждение после того, как меняю порядок в списке:
Предупреждение: невозможно выполнить обновление состояния React для отключенного компонента. Это не работает, но указывает на утечку памяти в вашем приложении. Чтобы исправить, отмените все подписки и асинхронные задачи в методе componentWillUnmount.
Я пробовал разные вещи, и я не могу заставить его перестать выдавать эту ошибку. Я весь день читаю документы и устраняю неполадки. Любое понимание будет глубоко оценено. Вот код:
Dashboard.js:
import React, { useEffect, useCallback } from "react";
import { View, Dimensions, StyleSheet, ActivityIndicator } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { FontAwesome5 } from "@expo/vector-icons";
import Habit from "../components/Habit";
import { getHabits } from "../api/firebaseMethods";
import CreationModal from "./habitCRUD/CreationModal";
import DraggableFlatList from "react-native-draggable-flatlist";
import CompletedHeader from "../components/CompletedHeader";
import { useStateIfMounted } from "use-state-if-mounted";
export default function Dashboard() {
//initialize state variables
const [habits, setHabits] = useStateIfMounted([]);
const [completedHabits, setCompletedHabits] = useStateIfMounted([]);
const [loading, setLoading] = useStateIfMounted(true);
const [modalVisible, setModalVisible] = useStateIfMounted(false);
const [expanded, setExpanded] = useStateIfMounted(false);
const day = new Date().getDate(); //Current Day
const month = new Date().getMonth() + 1; //Current Month
const year = new Date().getFullYear(); //Current Year
const date = month + "/" + day + "/" + year;
//fetch habits and store in state
useEffect(() => {
let unsubscribe = () => getHabits(date, setHabits, loading, setLoading, setCompletedHabits)
unsubscribe()
}, []);
//modal display switch
const updateModalVisible = () => {
if (modalVisible) {
setModalVisible(false);
} else {
setModalVisible(true);
}
};
//draggable flatlist renderer
const renderHabit = ({ item, index, drag, isActive }) => {
const {
title,
id,
details,
creationDate,
completed,
timesCompleted,
} = item;
return (
<Habit
title={title}
id={id}
details={details}
creationDate={creationDate}
completed={completed}
timesCompleted={timesCompleted}
date={date}
drag={drag}
isActive={isActive}
key={id}
/>
);
};
//handler for the create button, shows the creation modal
const handleCreate = () => {
setModalVisible(true);
};
//handler for expanding habits
const handleExpand = () => {
setExpanded(!expanded);
};
//flatlist component
const HabitList = () => {
const [mounted, setMounted] = useStateIfMounted(false)
if (habits) {
return (
<View style={styles.list}>
<DraggableFlatList
data={habits}
renderItem={renderHabit}
keyExtractor={(item, index) => {
return index.toString();
}}
onDragEnd={({ data }) => {
setHabits(data);
}}
/>
</View>
);
} else {
return (
<View>
<ActivityIndicator size="large" />
</View>
);
}
};
const CompletedHabitList = () => {
if (expanded) {
return (
<View style={[styles.list, styles.list2]}>
<TouchableOpacity
style={[styles.completed]}
onPress={() => handleExpand()}
>
<CompletedHeader expanded={expanded} />
</TouchableOpacity>
<DraggableFlatList
data={completedHabits}
renderItem={renderHabit}
keyExtractor={(item, index) => {
return index.toString();
}}
onDragEnd={({ data }) => {
setHabits(data);
}}
/>
</View>
);
} else {
return (
<View style={[styles.listMin]}>
<TouchableOpacity
style={styles.completed}
onPress={() => handleExpand()}
>
<CompletedHeader expanded={expanded} />
</TouchableOpacity>
</View>
);
}
};
const { container, buttonHolder, button, listsHolder } = styles;
if (loading) {
return (
<View style={container}>
<ActivityIndicator size="large" />
</View>
);
} else {
return (
<View style={container}>
<View style={listsHolder}>
<HabitList />
<CompletedHabitList />
</View>
<View style={buttonHolder}>
<TouchableOpacity style={button} onPress={handleCreate}>
<FontAwesome5 name="plus" size={40} color="red" />
</TouchableOpacity>
</View>
<CreationModal
modalVisible={modalVisible}
updateModalVisible={() => {
updateModalVisible();
}}
currentDate={date}
/>
</View>
);
}
}
let windowWidth = Dimensions.get("window").width;
let windowHeight = Dimensions.get("window").height;
const styles = StyleSheet.create({
container: {
height: windowHeight,
width: windowWidth,
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
paddingTop: "15%",
},
listsHolder: {
height: "100%",
width: "100%",
},
list: {
flex: 1,
padding: 5,
alignItems: "center",
justifyContent: "center",
},
list2: {
paddingBottom: 50,
backgroundColor: "#e9e9e9",
},
listMin: {
position: "absolute",
bottom: 20,
right: 0,
left: 0,
height: 60,
width: windowWidth,
alignItems: "center",
justifyContent: "center",
},
completed: {
height: 50,
width: windowWidth,
alignItems: "center",
justifyContent: "center",
marginBottom: 10,
backgroundColor: "#e9e9e9",
},
buttonHolder: {
position: "absolute",
alignItems: "center",
justifyContent: "center",
bottom: "10%",
right: "2%",
},
button: {
padding: 10,
borderRadius: Math.round(windowWidth + windowHeight) / 2,
width: windowWidth * 0.2,
height: windowWidth * 0.2,
backgroundColor: "black",
alignItems: "center",
justifyContent: "center",
},
});
Habit.js
import React, { useEffect } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
} from "react-native";
import { deleteHabit, completeHabit } from "../api/firebaseMethods";
import { AntDesign, Feather, Entypo } from "@expo/vector-icons";
import EditModal from "../screens/habitCRUD/EditModal";
import { useStateIfMounted } from "use-state-if-mounted";
const Habit = (props) => {
const {
title,
id,
details,
creationDate,
completed,
timesCompleted,
date,
drag,
} = props;
const [expanded, setExpanded] = useStateIfMounted(false);
const [modalVisible, setModalVisible] = useStateIfMounted(false);
const { titleStyle, idStyle, detailsStyle, buttonsHolder } = styles;
//modal display switch
const updateModalVisible = () => {
if (modalVisible) {
setModalVisible(false);
} else {
setModalVisible(true);
}
};
//habit display switch
const handleExpand = () => {
setExpanded(!expanded);
};
const handleDelete = (id) => {
deleteHabit(id);
};
const handleUpdate = () => {
setModalVisible(true);
};
const handleComplete = (id) => {
completeHabit(id, date);
};
let pluralizer = "s";
if (timesCompleted === 1) {
pluralizer = "";
}
if (expanded) {
return (
<View style={styles.container}>
<Text style={titleStyle}>{title}</Text>
<View style={buttonsHolder}>
<TouchableOpacity
style={styles.button}
onPress={() => handleComplete(id)}
>
<AntDesign name="checkcircleo" size={24} color="green" />
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => handleExpand()}
>
<Entypo name="add-to-list" size={24} color="black" />
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => handleUpdate(id, title, details)}
>
<Feather name="edit" size={24} color="blue" />
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => handleDelete(id)}
>
<AntDesign name="close" size={24} color="red" />
</TouchableOpacity>
</View>
<Text style={idStyle}>ID: {id}</Text>
<Text style={idStyle}>Creation Date: {creationDate}</Text>
<Text style={detailsStyle}>{details}</Text>
<Text style={idStyle}>{completed ? "Complete" : "Incomplete"}</Text>
<Text
style={detailsStyle}
>{`Completed ${timesCompleted} time${pluralizer}`}</Text>
<EditModal
id={id}
title={title}
details={details}
modalVisible={modalVisible}
updateModalVisible={() => {
updateModalVisible();
}}
/>
</View>
);
} else {
return (
<View style={[styles.smallContainer, { flexDirection: "row" }]}>
<TouchableOpacity
style={styles.button}
onPress={() => handleComplete(id)}
>
<AntDesign name="checkcircleo" size={24} color="green" />
</TouchableOpacity>
<Text style={titleStyle}>{title}</Text>
<TouchableOpacity style={styles.button} onPress={() => handleExpand()}>
<Entypo name="add-to-list" size={24} color="black" />
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPressIn={drag}>
<Entypo name="dots-three-vertical" size={24} color="black" />
</TouchableOpacity>
</View>
);
}
};
const styles = StyleSheet.create({
container: {
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
borderWidth: 2,
borderRadius: 5,
margin: 5,
padding: 5,
},
smallContainer: {
alignItems: "center",
justifyContent: "flex-start",
},
buttonsHolder: {
flex: 1,
flexDirection: "row",
alignItems: "center",
},
titleStyle: {
fontSize: 34,
color: "black",
margin: 10,
},
button: {
borderRadius:
Math.round(
Dimensions.get("window").width + Dimensions.get("window").height
) / 2,
width: Dimensions.get("window").width * 0.12,
height: Dimensions.get("window").width * 0.12,
backgroundColor: "rgba(0,0,0,0.05)",
justifyContent: "center",
alignItems: "center",
margin: 10,
},
idStyle: {
fontSize: 12,
margin: 5,
},
detailsStyle: {
fontSize: 30,
},
});
export default Habit;
firebaseMethods.js
import * as firebase from "firebase";
import "firebase/firestore";
//user authentication flow
export const registration = async (email, password, lastName, firstName) => {
try {
await firebase.auth().createUserWithEmailAndPassword(email, password);
const currentUser = firebase.auth().currentUser;
const db = firebase.firestore();
await db.collection("users").doc(currentUser.uid).set({
email: currentUser.email,
lastName: lastName,
firstName: firstName,
});
} catch (err) {
console.log("There is something wrong!", err.message);
}
};
export const signIn = async (email, password) => {
try {
await firebase.auth().signInWithEmailAndPassword(email, password);
} catch (err) {
console.log("There is something wrong!", err.message);
}
};
export const loggingOut = async () => {
try {
await firebase.auth().signOut();
} catch (err) {
console.log("There is something wrong!", err.message);
}
};
export const getUserInfo = async () => {
try {
let uid = firebase.auth().currentUser.uid;
let doc = await firebase.firestore().collection("users").doc(uid).get();
if (!doc.exists) {
console.log("No user data found!");
} else {
return doc.data();
}
} catch (err) {
console.log("There is an error.", err.message);
}
};
//habit workflows
export const getHabits = async (
today,
setHabits,
loading,
setLoading,
setCompletedHabits
) => {
try {
let uid = firebase.auth().currentUser.uid;
await firebase
.firestore()
.collection("users")
.doc(uid)
.collection("habits")
.orderBy("order")
.onSnapshot((querySnapshot) => {
let habits = querySnapshot.docs.map((doc) =>
Object.assign({ id: doc.id }, doc.data())
);
habits.forEach((habit) => {
try {
if (habit.completed) {
if (habit.completedDate != today) {
firebase
.firestore()
.collection("users")
.doc(uid)
.collection("habits")
.doc(habit.id)
.update({
completed: false,
});
}
}
} catch (err) {
console.log("There is an error.", err.message);
}
});
let list1 = [];
let list2 = [];
habits.forEach((habit) => {
if (!habit.completed) {
list1.push(habit);
} else {
list2.push(habit);
}
});
setHabits(list1);
setCompletedHabits(list2);
if (loading) {
setLoading(false);
}
});
} catch (err) {
console.log("There is an error.", err.message);
}
};
export const reorderHabits = async (habits) => {
try {
let uid = firebase.auth().currentUser.uid;
habits.forEach(async (habit, index) => {
await firebase
.firestore()
.collection("users")
.doc(uid)
.collection("habits")
.doc(habit.id)
.update({
order: index,
});
});
} catch (err) {
console.log(err.message);
}
};
export const createHabit = async (title, details, date) => {
try {
let uid = firebase.auth().currentUser.uid;
const ref = await firebase
.firestore()
.collection("users")
.doc(uid)
.collection("habits")
.get();
const habits = ref.docs.map((doc) =>
Object.assign({ id: doc.id }, doc.data())
);
let prevMax = 0;
if (habits) {
if (habits.length === 1) {
prevMax = habits.length;
} else {
prevMax = habits.length + 1;
}
}
if (title) {
let uid = firebase.auth().currentUser.uid;
const db = firebase.firestore();
await db.collection("users").doc(uid).collection("habits").add({
title,
details,
timesCompleted: 0,
creationDate: date,
completed: false,
order: prevMax,
});
} else {
console.log("Title is required!");
}
} catch (err) {
console.log("There is something wrong!", err.message);
}
};
export const completeHabit = async (id, date) => {
try {
let uid = firebase.auth().currentUser.uid;
let habit = await firebase
.firestore()
.collection("users")
.doc(uid)
.collection("habits")
.doc(id)
.get();
let completed = habit.data().completed;
let completedDate = habit.data().completedDate;
let previousCompletedDate = habit.data().previousCompletedDate;
let timesCompleted = habit.data().timesCompleted;
if (!completed) {
//not completed
if (!completedDate) {
//never completed
await firebase
.firestore()
.collection("users")
.doc(uid)
.collection("habits")
.doc(id)
.update({
timesCompleted: timesCompleted + 1,
completedDate: date,
completed: true,
});
} else {
//not completed
//completed at least once before
await firebase
.firestore()
.collection("users")
.doc(uid)
.collection("habits")
.doc(id)
.update({
timesCompleted: timesCompleted + 1,
previousCompletedDate: completedDate,
completedDate: date,
completed: true,
});
}
} else {
//is completed
if (!previousCompletedDate) {
//completed once
//get the FieldValue object
const FieldValue = await firebase.firestore.FieldValue;
await firebase
.firestore()
.collection("users")
.doc(uid)
.collection("habits")
.doc(id)
.update({
timesCompleted: timesCompleted - 1,
previousCompletedDate: FieldValue.delete(),
completedDate: FieldValue.delete(),
completed: false,
});
} else {
//completed multiple times
await firebase
.firestore()
.collection("users")
.doc(uid)
.collection("habits")
.doc(id)
.update({
timesCompleted: timesCompleted - 1,
completedDate: previousCompletedDate,
completed: false,
});
}
}
} catch (err) {
console.log("There is something wrong!", err.message);
}
};
export const updateHabit = async (id, title, details) => {
try {
if ((id, title)) {
let uid = firebase.auth().currentUser.uid;
const db = firebase.firestore();
await db
.collection("users")
.doc(uid)
.collection("habits")
.doc(id)
.update({
title,
details,
});
} else {
console.log("There is something wrong!");
}
} catch (err) {
console.log("There is something wrong!", err.message);
}
};
export const deleteHabit = async (id) => {
if (id) {
try {
let uid = firebase.auth().currentUser.uid;
const db = firebase.firestore();
await db
.collection("users")
.doc(uid)
.collection("habits")
.doc(id)
.delete();
} catch (err) {
console.log("There is something wrong!", err.message);
}
} else {
console.log("There is something wrong!");
}
};