Search code examples
javascriptreactjsgoogle-cloud-firestoreuse-effect

Unmounted component warning when trying to filter through pulled database information


I have figured out how to pull the notifications from the database but having trouble creating a filter that allows the user that asked the question to only get notifications when their questions have been answered.

The code I wrote is getting an unmounted error on line 31 with how I'm performing the filter. Here is the error message:

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:

 import React, { useEffect, useState } from "react";
    import { makeStyles } from "@material-ui/core/styles";
    import Popper from "@material-ui/core/Popper";
    import NotificationsIcon from "@material-ui/icons/Notifications";
    import "../Style/Header.css";
    import db, { auth } from "../firebase";
    
    const useStyles = makeStyles((theme) => ({
      paper: {
        border: "1px solid",
        padding: theme.spacing(1),
        backgroundColor: theme.palette.background.paper,
        zIndex: "10",
      },
    }));
    
    
    export default function SimplePopper() {
          const classes = useStyles();
          const [anchorEl, setAnchorEl] = React.useState(null);
          const [notifications, setNotifications] = useState([]);
        
          const handleClick = (event) => {
            setAnchorEl(anchorEl ? null : event.currentTarget);
          };
        
          const open = Boolean(anchorEl);
          const id = open ? "simple-popper" : undefined;
        
           useEffect(() => {
    let mounted = true;
    db.collection("notifications")
      .where(auth.askerUserId, "==", auth.currentUser.uid)
      .orderBy("timestamp", "desc")
      .onSnapshot((snapshot) => {
        if (mounted) {
          setNotifications(
            snapshot.docs.map((doc) => ({
              id: doc.id,
              content: doc.data().content,
            }))
          );
        }
      });
    return () => (mounted = false);
  }, []);
        
          return (
            <div className="header__icon">
              <NotificationsIcon
                aria-describedby={id}
                type="button"
                onClick={handleClick}
              />
              <Popper id={id} open={open} anchorEl={anchorEl} style={{ zIndex: 100 }}>
                <div className={classes.paper}>
                  <ul className="notifications">
                    {notifications.map((notification) => (
                      <li key={notification.id}>{notification.content}</li>
                    ))}
                  </ul>
                </div>
              </Popper>
            </div>
          );
        }

Solution

  • You should unsubscribe when the component gets unmounted:

    import React, { useEffect, useState } from "react";
        import { makeStyles } from "@material-ui/core/styles";
        import Popper from "@material-ui/core/Popper";
        import NotificationsIcon from "@material-ui/icons/Notifications";
        import "../Style/Header.css";
        import db, { auth } from "../firebase";
        
        const useStyles = makeStyles((theme) => ({
          paper: {
            border: "1px solid",
            padding: theme.spacing(1),
            backgroundColor: theme.palette.background.paper,
            zIndex: "10",
          },
        }));
        
        
        export default function SimplePopper() {
              const classes = useStyles();
              const [anchorEl, setAnchorEl] = React.useState(null);
              const [notifications, setNotifications] = useState([]);
            
              const handleClick = (event) => {
                setAnchorEl(anchorEl ? null : event.currentTarget);
              };
            
              const open = Boolean(anchorEl);
              const id = open ? "simple-popper" : undefined;
            
               useEffect(() => {
        let mounted = true;
        let unsub=db.collection("notifications")
          .where(auth.askerUserId, "==", auth.currentUser.uid)
          .orderBy("timestamp", "desc")
          .onSnapshot((snapshot) => {
            if (mounted) {
              setNotifications(
                snapshot.docs.map((doc) => ({
                  id: doc.id,
                  content: doc.data().content,
                }))
              );
            }
          });
        return () => {unsub()};
      }, []);
            
              return (
                <div className="header__icon">
                  <NotificationsIcon
                    aria-describedby={id}
                    type="button"
                    onClick={handleClick}
                  />
                  <Popper id={id} open={open} anchorEl={anchorEl} style={{ zIndex: 100 }}>
                    <div className={classes.paper}>
                      <ul className="notifications">
                        {notifications.map((notification) => (
                          <li key={notification.id}>{notification.content}</li>
                        ))}
                      </ul>
                    </div>
                  </Popper>
                </div>
              );
            }