Search code examples
javascriptreactjsreact-props

How can I get the correct ID assigned to the delete function


I am learning while creating a test app that lets you create posts from a form. I have been stuck on the delete function going on five days now.

I was able to correctly map posts to print out the post_body to each card but when trying to delete it is always removing the last item in the database.

My guess is this has something to do with props and I have spent several days trying out different ways to pass props down through a functional component but no luck.

As seen in the screenshot, In the return I printed out the post_id for each card so you can see the proper ID is assigned for each card. However, once you get into the popover component the post_ID seems to always take on the value of the very bottom post.

Any direction is appreciated.

(note: I'm sure this code is pretty sloppy and I probably shouldn't be mapping over such a huge block of code. I may try refactoring once I figure out how these props should be working)

const ListPosts = (props) => {

  const [posts, setPosts] = useState([]);

  // Delete Post
  const deletePost = async id => {
    try {
        const deletePost = await fetch(`http://localhost:3000/posts/${id}`, {
          method: "DELETE"
      });
  
      setPosts(posts.filter(post => post.post_id !== id));  
      console.log(deletePost);
    } catch (err) {
      console.error(err.message);
    }
  }

  // Options Popover
  const [show, setShow] = useState(false);
  const [target, setTarget] = useState(null);
  const ref = useRef(null);

  const handleClick = (event) => {
    setShow(!show);
    setTarget(event.target);
  }
  

  // Get Posts Function
  const getPosts = async() => {
    try {
      
      const response = await fetch("http://localhost:3000/posts")
      const jsonData = await response.json()

      setPosts(jsonData);

    } catch (err) {
      console.error(err.message)
    }
  };

  useEffect(() => {
    getPosts();
  }, []);

  console.log(posts);


  return (
    <Fragment>
      {/* Map Post Text */}
      {posts.map(post => (
      <Card className="post-card" style={{ marginTop: "15px" }} key={post.post_id}> 
        <Card.Body>
          <div className="post-container">
            <div className="post-header row">
              <div className="user-photo-icon col-1"></div>
              <div className="user-names col-9"></div>
              <div className="options-menu col-2 ">
                <div ref={ref}>
                  <Button className="options-btn-popover" onClick={handleClick}>
                    <FontAwesomeIcon icon={ faEllipsisH } color="#848484" size="1x" className="options-icon" />
                  </Button>
                  
                  {/* Placed to show the point at which the ID's are still correct */}
                  {post.post_id}

                  <Overlay
                    show={show}
                    target={target}
                    placement="left"
                    container={ref.current}
                  >
                    <Popover className="shadow-sm" id="popover-contained" >

                      {/* Placed to show that now all id's show the post_id of the last post */}
                      {post.post_id}

                      <Popover.Content>
                        <div className="mb-2">
                          <Button className="options-btn-popover">
                            <FontAwesomeIcon icon={ faPencilAlt } size="1x" className="post-options-icon"/> 
                            Edit Post
                          </Button>
                        </div>
                        <div>
                          <Button className="options-btn-popover" onClick={() => deletePost(post.post_id)}>
                          <FontAwesomeIcon icon={ faTrashAlt } color="" size="1x" className="post-options-icon" /> 
                            Delete Post
                          </Button>
                        </div>
                      </Popover.Content>
                    </Popover>  
                  </Overlay>
                </div>
              </div>
            </div>
            <div className="post-text">{post.post_body}</div>
            <div className="post-media"></div>
            <div className="post-actions"></div>
            <div className="post-comments">
              <div className="post-subcomments"></div>
            </div>
          </div>
        </Card.Body>  
      </Card>
      ))}
    </Fragment>
  )
};

Here's a screenshot:

post list with post Id's


Solution

  • Issue

    I suspect it is because you've only a single show state that when toggled opens all the popovers. All the popovers open but they are all positioned relative to the target element, they all overlay one another and the last one is on top.

    Solution

    I suggest storing the current post id in the "show" state and conditionally check/match that to open a specific popover.

    1. Start with null initial state:

      const [show, setShow] = useState(null);
      
    2. Update the click handler to consume a post id and curry the event object. Set the show state to the currently clicked on post id.

      const handleClick = (postId) => (event) => {
        setShow(postId);
        setTarget(event.target);
      }
      
    3. Pass the post.post_id when mapping the buttons.

      <Button
        className="options-btn-popover"
        onClick={handleClick(post.post_id)} // <-- pass post id
      >
        <FontAwesomeIcon
          icon={faEllipsisH}
          color="#848484"
          size="1x"
          className="options-icon"
        />
      </Button>
      
    4. Check the current post id when mapping the overlay/popover. If the current post id matched what is stored in show state then evaluate true, otherwise false.

      <Overlay
        show={show === post.post_id} // <-- compare post id to show state
        target={target}
        placement="left"
        container={ref.current}
      >
        <Popover className="shadow-sm" id="popover-contained" >
          <Popover.Content>
            ...
          </Popover.Content>
        </Popover>  
      </Overlay>
      
    5. Clear the show state when delete operation has completed. Functional state update to correctly update from the previous state (not the state from the render cycle the callback was triggered in.

      const deletePost = async id => {
        try {
          const deletePost = await fetch(`http://localhost:3000/posts/${id}`, {
            method: "DELETE"
          });
      
          setPosts(posts => posts.filter(post => post.post_id !== id)); // <-- functional update 
          console.log(deletePost);
        } catch (err) {
          console.error(err.message);
        } finally {
          setShow(null); // <-- reset back to null
        }
      }