Предупреждение: 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!");
  }
};

0 ответов