Where is my memory leak coming from? useEffect in React

Viewed 61

I'm using a useEffect hook in React that has to update state based on the dependencies, and I added a variable isMounted that is set to false on cleanup, and any functions that are async or affect state are only called if isMounted evaluated to true. I'm not sure where the memory leak is coming from aside from that it's being caused by the useEffect hook in this component.

Here are the parts of the component that might be causing the memory leak.

(i know i should be using constant variables for the rating color its a WIP)

const ContentItem = ({ contentItem, type, showComment, allowDelete, isList }) => {
  const [ratingColor, setRatingColor] = useState('rgb(58,58,58)');
  const [content, setContent] = useState({});
  const [awaitingItem, setAwaitingItem] = useState(true);
  const [owner, setOwner] = useState(null);

  const user = useSelector(state => state.userReducer.user);
  const token = useSelector(state => state.userReducer.token);

  const dispatch = useDispatch();
  const history = useHistory();

  const getOwner = useCallback(
    async (id) => {
      try {
        const { user } = await YetiApi.getUserById(id);
        setOwner(user);
      }
      catch (errs) {
        dispatch(showErrors(errs));
      }
    }, [dispatch]);

  const getPost = useCallback(
    async () => {
      try {
        const { post } = await YetiApi.getPost(token, contentItem.id);
        setContent({
          id: post.id,
          body: post.body,
          userId: post.user_id,
          rating: post.rating,
          owner
        });
        getOwner(post.user_id);
        setAwaitingItem(false);
      }
      catch (errs) {
        dispatch(showErrors(errs));
      }
    }, [dispatch, contentItem.id, getOwner, owner, token]);


  const getComment = useCallback(
    async () => {
      try {
        const { comment } = await YetiApi.getComment(token, contentItem.id);
        setContent({
          id: comment.id,
          body: comment.comment,
          userId: comment.user_id,
          postId: comment.post_id,
          rating: comment.rating,
          owner
        });
        getOwner(comment.user_id);
        setAwaitingItem(false);
      }
      catch (errs) {
        dispatch(showErrors(errs));
      }
    }, [dispatch, contentItem.id, getOwner, owner, token]);


  // memory leak for some reason, can't figure out why

  useEffect(() => {
    let isMounted = true;

    const getRatingColor = () => {
      switch (content.rating) {
        case content.rating < 0:
          setRatingColor('red');
          break;
        case content.rating > 0:
          setRatingColor('green');
          break;
        default:
          setRatingColor('rgb(58,58,58)');
          break;
      }
    }

    if (content.body && isMounted) getRatingColor();
    if (!owner && isMounted) getOwner(contentItem.user_id);

    if (isMounted && !content.body) {
      switch (type) {
        case 'post':
          getPost();
          break;
        case 'comment':
          getComment();
          break;
        default:
          break;
      }
    }

    return () => {
      isMounted = false;
    }

  }, [
    contentItem.rating, contentItem.user_id,
    owner, dispatch,
    contentItem, content.body,
    content.rating,
    getComment, getPost,
    getOwner, type
  ]);

  // do something

 return (<SOME_JSX />);
}

There's a lot going on I know so if there's anything that I can definitely remove from the code snippet, or explain more thoroughly please let me know!

1 Answers

That is not how you can use isMounted, the callback to the async function asyncFunction.then(callback) has isMounted as a stale closure

You should use this hook instead.

Here is a demo how to use it:

const MySecureComponent = () => {
  const isMounted = useIsMounted();
  const [results, setResults] = useState(null);

  useEffect(() => {
    myService.getResults().then(val => {
      if (isMounted.current) {
        setResults(val);
      }
    });
  }, [myService.getResults]);

  return results ? <ResultsView results={results} /> : <Loading />;
};
Related