如何获得分配给删除功能的正确 ID

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

我在创建一个测试应用程序时正在学习,该应用程序允许您从表单创建 posts。我一直卡在删除功能上五天了。

我能够正确映射 posts 以将 post_body 打印到每张卡片上,但是在尝试删除它时总是删除数据库中的最后一项。

我猜这与道具有关,我花了几天时间尝试不同的方法通过功能组件传递道具,但没有成功。

如屏幕截图所示,在 return 中,我为每张卡打印了 post_id,因此您可以看到为每张卡分配了正确的 ID。但是,一旦进入弹出窗口组件,post_ID 似乎总是采用最底部的值 post。

欢迎任何指导。

(注意:我确定这段代码很草率,我可能不应该映射这么大的代码块。一旦我弄清楚这些道具应该如何工作,我可能会尝试重构)

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>
  )
};

截图如下:

post list with post Id's

你可以尝试使用 currying 来完成这个吗?

下面是您的代码,我做了一些小调整。当我们声明 deletePost 时,您会注意到它将 id 作为参数,然后调用另一个函数。然后,当您调用此删除函数时,您不再需要 () => before deletePost.

TLDR:柯里化让您可以在执行时间之前传入值。

const ListPosts = (props) => {

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

  // Delete Post
  const deletePost = id => async => {
    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>
  )
};

问题

我怀疑这是因为您只有一个 show 状态,切换后会打开 all 弹出窗口。所有弹出窗口都打开,但它们都相对于 target 元素定位,它们都相互重叠,最后一个在顶部。

解决方案

我建议将当前 post id 存储在“显示”状态,并有条件地 check/match 打开特定弹出窗口。

  1. null初始状态开始:

    const [show, setShow] = useState(null);
    
  2. 更新点击处理程序以使用 post id 并柯里化事件对象。将 show 状态设置为当前点击的 post id。

    const handleClick = (postId) => (event) => {
      setShow(postId);
      setTarget(event.target);
    }
    
  3. 映射按钮时传递post.post_id

    <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. 在映射 overlay/popover 时检查当前 post id。如果当前 post id 与存储在 show 状态中的内容匹配,则评估为 true,否则为 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. 删除操作完成后清除show状态。功能状态更新以从先前状态正确更新(不是触发回调的渲染周期中的状态。

    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
      }
    }
    

我认为问题在于您正在循环内创建 Overlay/Popover 组件,请尝试将其移出循环。然后,您可以使用 const [selectedPost, selectPost] = useState() 来跟踪应该在叠加层中呈现的数据。按钮被点击。同时调整 onClick={handleClick} 调用 selectPost(post).