Home > Mobile >  Re-render List after deleting item in child component
Re-render List after deleting item in child component

Time:01-23

I want to build a dashboard for a blog. I have a page, listing all blog posts using a component for each list item. Now, inside each list item, I have a button to delete the post. So far, everything is working. The post gets deleted, and if I reload the page, it is gone from the list. But I can't get it to re-render the page automatically, after deleting a post. I kind of cheated here using window.location.reload() but there has to be a better way?

This is my Page to build the list of all Posts

import {
  CCol,
  CContainer,
  CRow,
  CTable,
  CTableHead,
  CTableRow,
  CTableHeaderCell,
  CTableBody,
} from "@coreui/react";
import { useState, useEffect } from "react";
import DashboardSidebar from "../../components/dashboard/Sidebar";
import { getAllBlogPosts } from "../../services/blogService";
import BlogListItem from "../../components/dashboard/blog/BlogListItem";
import "./Dashboard.scss";

const AdminBlogListView = () => {
  const [blogposts, setBlogposts] = useState([]);

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

  async function getBlogPosts() {
    const response = await getAllBlogPosts();
    setBlogposts(response.data);
  }
  // console.log(blogposts);
  return (
    <div className="adminContainer">
      <div className="adminSidebar">
        <DashboardSidebar />
      </div>
      <div className="adminContent">
        <CContainer fluid>
          <CRow className="mb-3">
            <CCol>
              <CTable>
                <CTableHead>
                  <CTableRow>
                    <CTableHeaderCell scope="col">#</CTableHeaderCell>
                    <CTableHeaderCell scope="col">Titel</CTableHeaderCell>
                    <CTableHeaderCell scope="col">Content</CTableHeaderCell>
                    <CTableHeaderCell scope="col"></CTableHeaderCell>
                  </CTableRow>
                </CTableHead>
                <CTableBody>
                  {blogposts.map((post) => {
                    return <BlogListItem key={post._id} post={post} />;
                  })}
                </CTableBody>
              </CTable>
            </CCol>
          </CRow>
        </CContainer>
      </div>
    </div>
  );
};

export default AdminBlogListView;

And this is the BlogListItem Component

import { useState, useEffect } from "react";
import {
  CTableRow,
  CTableHeaderCell,
  CTableDataCell,
} from "@coreui/react";
import CIcon from "@coreui/icons-react";
import * as icon from "@coreui/icons";
import {
  deleteBlogPost,
  getBlogPostById,
  // updateBlogPost,
} from "../../../services/blogService";
import { useNavigate } from "react-router-dom";

const BlogListItem = (props) => {
  const id = props.post._id;
  const [visible, setVisible] = useState(false);
  const [post, setPost] = useState({
    title: "",
    content: "",
  });

  useEffect(() => {
    getBlogPostById(id)
      .then((response) => setPost(response.data))
      .catch((error) => console.log(error));
  }, []);

  const handleDelete = async (event) => {
    event.preventDefault();
    const choice = window.confirm("Are you sure you want to delete this post?");
    if (!choice) return;
    await deleteBlogPost(post._id);
    window.location.reload();
  };

  return (
    <>
      <CTableRow>
        <CTableHeaderCell scope="row">1</CTableHeaderCell>
        <CTableDataCell>{post.title}</CTableDataCell>
        <CTableDataCell>{post.content}</CTableDataCell>
        <CTableDataCell>
          <CIcon
            icon={icon.cilPencil}
            size="lg"
            onClick={() => setVisible(!visible)}
          />
          <CIcon
            icon={icon.cilTrash}
            className="deleteButton"
            size="lg"
            color=""
            onClick={handleDelete}
          />
        </CTableDataCell>
      </CTableRow>
    </>
  );
};

export default BlogListItem;

What can I do instead of window.location.reload() to render the AdminBlogListView after deleting an item? I tried using useNavigate() but that doesn't do anything

Thanks in advance :)

CodePudding user response:

You can pass a reference to a function from the parent component AdminBlogListView into the child component BlogListItem, such that it is invoked when a blog post is deleted. That function will have the effect of either repopulating the blog posts or manually removing it from the data (that implementation bit is up to you).

Solution 1: Repopulate all blog posts on deletion

This is a quick fix with a bit of code smell (because you're essentially querying the server twice: once to delete the post and another to fetch posts again). However it is an escape-hatch type of situation and is simple to implement.

When you are rendering BlogListItem, we can pass a function, say onDelete, which will invoke getBlogPosts() to manually repopulate the blog posts from your server:

<BlogListItem key={post._id} post={post} onDelete={getBlogPosts} />

Then it is a matter of ensuring BlogListItem invokes onDelete() when deleting a blog post:

const handleDelete = async (event) => {
  event.preventDefault();
  const choice = window.confirm("Are you sure you want to delete this post?");
  if (!choice) return;
  await deleteBlogPost(post._id);
  
  // Invoke the passed in `onDelete` function in component props
  props.onDelete();
};

Solution 2: Delete a specific blog post by ID in the parent

Similar to the solution above, but ensure that you are passing a function from the parent that can delete a post by a specific ID (from the argument). This saves you an additional trip to the server.

In your component AdminBlogListView, define a function that can mutate the blogposts state by removing a blog post by ID. This can be done by leveraging functional updates:

const onDelete = (id) => {
  setBlogposts((currentBlogPosts) => {
    const foundBlogPostIndex = currentBlogPosts.findIndex(entry => entry._id === id);
    
    // If we find the blog post with matching ID, remove it
    if (foundBlogPostIndex !== -1) currentBlogPosts.splice(foundBlogPostIndex, 1);

    return currentBlogPosts;
  })
}

NOTE: The code above assumes that the blog post ID is stored in the _id key. I have simply inferred that from your code, since you have not shared the shape of the data.

Then in your BlogListItem component, it's the same logic as solution #1, but you need to pass the ID into it when invoking it:

const handleDelete = async (event) => {
  event.preventDefault();
  const choice = window.confirm("Are you sure you want to delete this post?");
  if (!choice) return;
  await deleteBlogPost(post._id);
  
  // Invoke the passed in `onDelete` function in component props with post ID as an argument
  props.onDelete(post._id);
};
  •  Tags:  
  • Related