reactjsmountuse-effectunmount

Can't perform a React state update on an unmounted component useEffect error


I have this code where I am fetching data and passing it to a component. This child component then renders the data I have made it so that when the user pulls down from the top, the component will refresh but whenever I refresh for the first time only, I get the error

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

Here is the code:

function FolloweringScreens({
  data,
  screen,
  username,
  navigation,
  noMoreData,
  setLoadingMore,
  lastVisible,
  setNoMoreData,
  setLastVisible,
  setfollowingData,
  loadingMore,
  setLoading,
  currentuser,
}) {
  const [stateData, setStateData] = useState();
  const [refresh, setRefresh] = useState(false);

  useEffect(() => {
    const setState = () => setStateData(data);
    return setState();
  }, [data, refresh]);

  // Refresh
  const handleFetch = () => {
    setRefresh(true);
    const cleanup = fetchData(
      username,
      setfollowingData,
      setLoading,
      setLastVisible,
      setNoMoreData,
      setRefresh,
      screen,
      currentuser,
    );
    return cleanup;
  };

  return (
    <>
      <FlatList
        refreshing={refresh}
        onRefresh={handleFetch}
        data={stateData}
        keyExtractor={(i, index) => index.toString()}
        renderItem={({item, index}) => {
          return (
            <>
                 { Using my data here }
            </>
          );
        }}
      />
    </>
  );
}

export default FolloweringScreens;

Here is the fetchData function:

export const fetchData = (
  username,
  setfollowingData,
  setLoading,
  setLastVisible,
  setNoMoreData,
  setRefresh,
  screen,
  currentuser,
) => {
  const dataaRef = firestore().collection('usernames');
  setNoMoreData && setNoMoreData(false);

  // If in

  dataaRef // Go to whichever users clicked on data
    .doc(username.toLowerCase())
    .collection(screen) // Go to followers/following
    .orderBy('followedAt')
    .limit(6)
    .get()
    .then((snapshot) => {
      setLoading(true);
      snapshot.empty
        ? null
        : setLastVisible(
            snapshot.docs[snapshot.docs.length - 1].data().followedAt,
          );
      let promises = [];
      // 1b. For each document, return that document data
      snapshot.forEach((doc) => {
        const data = doc.data();
        promises.push(
          data.path.get().then((res) => {
            const userData = res.data();

            // Go to logged in users directory to see
            // if they are following these users in this
            // users following/followers page so we can
            // differentiate whether to display follow/unfollow button
            return dataaRef
              .doc(
                currentuser === undefined
                  ? username.toLowerCase()
                  : currentuser.toLowerCase(),
              )
              .collection('Following')
              .doc(doc.id)
              .get()
              .then((searchedDocs) => {
                return {
                  profileName: doc.id ? doc.id : null,
                  displayName: userData.displayName
                    ? userData.displayName
                    : null,
                  followerCount:
                    userData.followers !== undefined ? userData.followers : 0,
                  followingCount:
                    userData.following !== undefined ? userData.following : 0,
                  imageUrl: userData.imageUrl ? userData.imageUrl : null,
                  isFollowed: searchedDocs.exists ? true : false,
                };
              });
          }),
        );
      });
      // 1c. set All document data to followingData
      Promise.all(promises).then((res) => {
        setfollowingData(res);
        // console.log('res', res);
      });
      setLoading(false);
      setRefresh && setRefresh(false);
    });
};

Solution

  • You can't do that actually. Since the function that returns from useEffect act as a clean up function. You usually clean up your back like removing event listeners and such things when a component dispose.

    useEffect(() => {
    
      document.addEventListenter('click', () => {});
    
      function cleanup() {
        document.removeEventListener('click', () => {});
      };
    
      return cleanup;
    
    })
    

    This is how useEffect works. So in your case it is complaining about an state update when component is getting unmounted which is illegal in react world.

    If you are trying to call it just call it.

    useEffect(() => {
      const setState = () => setStateData(data);
      setState();
    }, [data, refresh]);
    

    Or a better way is to define your function outside of useEffect, and call it inside of it.

    const setState = () => setStateData(data);
    
    useEffect(() => {
      if (!data) return;
      setState();
    }, [data, refresh]);