简体   繁体   中英

Memory leak in React component whenever a delete function is run…not using useEffect?

Preview: https://befreebucket.s3.us-east-2.amazonaws.com/beFree-5fc1c2a00cb53d0017972145-kebin1421-hotmail-com-1615773766297.gif

I currently have a Profile component acting as the Parent component; said component has the following code:

const [profile, setProfile] = useState(null)
const [posts, setPosts] = useState([])

useEffect(() => {
  getProfile(match.params.id)
    .then((result) => {
      //  Set the user
      setProfile(result.payload.data)

      // Get posts by user
      getPosts(`?user=${match.params.id}`)
        .then((result) => {
          setPosts(result.payload.data)
        })
        .catch((err) => {
          console.log('Posts error' + err)
        })
    })
    .catch((err) => {
      console.log('User error' + err)
    })
}, []);

All that data is then passed into a Single component which is the children component

posts?.length > 0 ? (
  posts.map((post, index) => (
    <Single
      key={post._id}
      post={post}
      postId={postId}
      setObjects={setPosts}
      objects={posts}
      setTotalResult={setTotalResults}
    />
  ))
)

The problem comes when I trigger a delete function from a dynamic component within the Single component. The compoenent that contains this function looks like this in the Single component:

import React, { useEffect, useState, useContext } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
// ACTIONS
import { deletePost } from '../../../actions/post';
// HELPERS
import ConfirmModal from '../../layout/ConfirmModal';
import ContentLoader from '../../layout/ContentLoader';
import { formatDate } from '../../../helpers/utilities';
import UseImage from '../../layout/UseImage';
// REACTBOOTSTRAP
import Card from 'react-bootstrap/Card';
import Carousel from 'react-bootstrap/Carousel';
import Dropdown from 'react-bootstrap/Dropdown';
import DropdownButton from 'react-bootstrap/DropdownButton';
import Button from 'react-bootstrap/Button';
import Badge from 'react-bootstrap/Badge';

const Single = ({
  deletePost,
  post: {
    _id,
    user,
    postedto,
    postedfrom,
    text,
    images,
    hidden,
    likes,
    sharedby,
    createdAt
  },
  postId = null,
  setObjects,
  objects = [],
  setTotalResults,
  auth
}) => {
  const hasLike = likes?.map((like) => like.user).includes(auth.user.data._id);
  const hasShare = sharedby
    ?.map((share) => share.user)
    .includes(auth.user.data._id);

  const [liked, setLiked] = useState(false); // Done
  const [likedIcon, setLikedIcon] = useState(`fas fa-heart`); // Done
  const [likesQuantity, setLikesQuantity] = useState(0); // Done
  const [, setShared] = useState(false);
  const [sharedIcon, setSharedIcon] = useState(`far fa-share-square`);
  const [sharesQuantity, setSharesQuantity] = useState(0);
  const [hiden, setHidden] = useState(false);
  const [hiddenIcon, setHiddenIcon] = useState(`fas fa-eye`);
  const [, setError] = useState(false);

  useEffect(() => {
    setLiked(hasLike);
    setLikedIcon(hasLike ? `fas fa-heart` : `far fa-heart`);
    setLikesQuantity(likes?.length);
    setShared(hasShare);
    setSharedIcon(hasShare ? `fas fa-share-square` : `far fa-share-square`);
    setSharesQuantity(sharedby?.length);
    setHidden(hidden);
    setHiddenIcon(hidden ? `fas fa-eye-slash` : `fas fa-eye`);
  }, []);

  return _id === null || _id === undefined ? (
    <ContentLoader />
  ) : (
    <article className={`${_id ? _id : postId} mb-3`}>
      <Card>
        <Card.Header>
          <div className={``}>
            <Link
              to={`/profiles/${user._id ? user._id : auth.user.data._id}/posts`}
            >
              <UseImage
                src={`${user.avatar ? user.avatar : auth.user.data.avatar}`}
                alt={`${
                  user.username ? user.username : auth.user.data.username
                }`}
                classGiven={`w-auto mr-2`}
                style={{
                  height: '35px',
                  objectFit: 'cover'
                }}
              />
              {user.username === auth.user.data.username
                ? 'You'
                : user.username
                ? user.username
                : auth.user.data.username}
            </Link>
            {postedto && (
              <>
                {` `}posted to{` `}
                <Link
                  to={`/profiles/${postedto._id}/posts`}
                  className={`ml-1 mr-1`}
                >
                  {postedto.username}
                </Link>
              </>
            )}
            {postedfrom && (
              <>
                {' '}
                shared from{' '}
                <Link to={`/profiles/${postedfrom._id}/posts`}>
                  {postedfrom.username}
                </Link>
              </>
            )}
            <div className={`float-right`}>
              <DropdownButton
                alignRight
                variant={`secondary`}
                size={`sm`}
                drop={`down`}
                id={`dropdown-basic-button`}
                title={<i className={`fas fa-ellipsis-h`} />}
              >
                {user._id === auth.user.data._id && (
                  <>
                    <Dropdown.Divider />
                    <ConfirmModal
                      id={_id ? _id : postId}
                      action={deletePost}
                      classStr={`dropdown-item`}
                      setObjects={setObjects}
                      objects={objects}
                      setTotalResults={setTotalResults}
                    />
                  </>
                )}
              </DropdownButton>
            </div>
          </div>
        </Card.Header>
        <Card.Body
          className={`p-0`}
        >
          {images.length > 1 ? (
            <>
              <Carousel style={{ position: 'sticky' }}>
                {images.slice(0, 5).map((image, index) => (
                  <Carousel.Item key={index} className={`${index}`}>
                    <UseImage
                      src={`${image}`}
                      alt={``}
                      classGiven={`p-0 d-block w-100`}
                      width={`auto`}
                      height={`auto`}
                      style={{ objectFit: 'fill' }}
                    />
                  </Carousel.Item>
                ))}
              </Carousel>
              <h6 className={`position-absolute images-length-badge`}>
                <Badge pill variant={`light`}>
                  {images.length}
                </Badge>
              </h6>
            </>
          )}
        </Card.Body>
        <Card.Footer className={`p-1`}>
          <div className={`float-left`}>
            {user._id !== auth.user.data._id && (
              <Button
                variant={`link`}
                size={`sm`}
                className={`mr-1`}
              >
                <i className={`${likedIcon} mr-1`} />
                <span>{likesQuantity}</span>
              </Button>
            )}
            <Link
              to={`/posts/${_id ? _id : postId}`}
              className={`btn btn-link btn-sm mr-1`}
            >
              <i className={`far fa-comment mr-1`} />
              Comment
            </Link>
            {user._id !== auth.user.data._id && (
              <Button
                variant={`link`}
                size={`sm`}
                className={`mr-1`}
              >
                <i className={`${sharedIcon} mr-1`} />
                <span>{sharesQuantity}</span>
              </Button>
            )}
          </div>
          <div className={`float-right`}>
            {formatDate(createdAt, 'hours') + ' hours ago'}
          </div>
        </Card.Footer>
      </Card>
    </article>
  );
};

Single.propTypes = {
  deletePost: PropTypes.func.isRequired,
  auth: PropTypes.object.isRequired
};

const mapStateToProps = (state) => ({
  auth: state.auth
});

export default connect(mapStateToProps, {
  deletePost,
})(Single);

Finally, the function which triggers the memory leak in the ConfirmModal component is found under the name of deleteObject:

import React, { useState } from 'react';
// ACTIONS
// HELPERS
// REACTBOOTSTRAP
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';

const ConfirmModal = ({
  id = null,
  sId = null,
  location = ``,
  as = `button`,
  classStr = ``,
  action,
  action2,
  setObjects,
  objects = [],
  setTotalResults
}) => {
  const [confirmDeleteModal, setConfirmDeleteModal] = useState(false);
  const [, setError] = useState(false);

  const openDeleteModal = (e) => {
    setConfirmDeleteModal(true);
  };

  const closeDeleteModal = (e) => {
    setConfirmDeleteModal(false);
  };

  const deleteObject = async (e) => {
    e.preventDefault();
    await action(id, sId)
      .then((result) => {
        if (sId) {
          setObjects(objects.filter((object) => object._id !== sId));
        } else {
          setObjects(objects.filter((object) => object._id !== id));
        }
        setTotalResults(objects.length - 1);
        if (typeof action2 === `function`) {
          action2(location)
            .then((result) => {
              console.log(`Done`);
            })
            .catch((err) => {
              setError(true);
            });
        }
      })
      .catch((err) => {
        setError(true);
      });
  };

  return (
    <>
      <Button
        variant={`danger`}
        size={`sm`}
        onClick={openDeleteModal}
        data-target={`deleteModal#${id}`}
        as={as}
        className={classStr}
      >
        <i className={`fas fa-trash-alt mr-1`} />
        Delete
      </Button>
      {confirmDeleteModal && (
        <Modal
          show={true}
          onHide={closeDeleteModal}
          backdrop={true}
          animation={true}
          size={`sm`}
          id={`deleteModal#${id}`}
        >
          <Modal.Header closeButton>
            <Modal.Title>Are you sure?</Modal.Title>
          </Modal.Header>
          <Modal.Body>{id}</Modal.Body>
          <Modal.Footer>
            <Button
              variant={`secondary`}
              size={`sm`}
              onClick={closeDeleteModal}
            >
              Close
            </Button>
            <Button
              type={`submit`}
              size={`sm`}
              onClick={deleteObject}
              variant={`primary`}
            >
              Submit
            </Button>
          </Modal.Footer>
        </Modal>
      )}
    </>
  );
};

export default ConfirmModal;

Do anyone of you knows how to tackle this? I have been with this problem for about 3 weeks and just wanted to make sure where it came from.

Turns out, my mistake is that I forgot to type the s in the prop needed by the Single component and ConfirmModal:

posts?.length > 0 ? (
  posts.map((post, index) => (
    <Single
      key={post._id}
      post={post}
      postId={postId}
      setObjects={setPosts}
      objects={posts}
      // setTotalResult={setTotalResults}
      setTotalResults={setTotalResults}
    />
  ))
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM