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:
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.
I suggest storing the current post id in the "show" state and conditionally check/match that to open a specific popover.
Start with null
initial state:
const [show, setShow] = useState(null);
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);
}
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>
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>
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
}
}