Whenever i refetch in react query after mutating a mapped component, the status of the mutation of the next component shows as "success"

Viewed 5911

I'm fetching and displaying an array of posts from my express backend, it displays as a "Blog" component.

list of posts

The component has a button to delete it from the backend and then refetch the posts, whenever I click the delete button, the status changes from "idle" to "loading" to "success" and then it gets removed when the data is refetched.

That works, but when it gets removed, the next post displays "success" as the status. Why?

post "title 2" is deleted and post 3 displays status as "success"

BlogPost.jsx:

import React from "react";
import axios from "axios";
import { useQueryCache, useMutation } from "react-query";

const deleteBlog = ({ postID }) => {
  return axios.delete(`/api/posts/${postID}`, { withCredentials: true });
};

const BlogPost = ({ postID, title, content }) => {
  const [mutation, { status }] = useMutation(deleteBlog);
  const queryCache = useQueryCache();

  return (
    <div style={{ border: "medium solid red", padding: 5 }}>
      {`status: ${status}`}
      <button
        onClick={() => {
          mutation({ postID }).then(() => {
            queryCache.invalidateQueries("posts");
          });
        }}
      >
        delete
      </button>
      <h2>{title}</h2>
      <h3>{postID}</h3>
      <p>{content}</p>
    </div>
  );
};

export default BlogPost;

Landing.jsx:

import React from "react";
import BlogPost from "../components/BlogPost";
import axios from "axios";
import { useQuery } from "react-query";

const posts = () => {
  return axios.get("/api/posts").then((resp) => resp.data);
};

const Landing = () => {
  const { data, isSuccess, refetch } = useQuery("posts", posts);

  return (
    <div>
      <button
        onClick={() =>
          axios
            .post(
              "/api/posts",
              { title: "title", content: "content" },
              { withCredentials: true }
            )
            .then(() => refetch())
        }
      >
        New post
      </button>

      {isSuccess &&
        data.map((post, index) => {
          return (
            <BlogPost
              key={index}
              postID={post._id}
              title={post.title}
              createdDate={post.createdAt}
              author={post.author}
              content={post.content}
            />
          );
        })}
    </div>
  );
};

export default Landing;

Express backend:

let fakedb = [
  {
    _id: "0",
    title: "title",
    createdAt: "1234",
    author: "Lorem",
    content: "453",
  },
];

app.get("/api/posts", (req, res) => {
  res.json(fakedb);
});

app.post("/api/posts", (req, res) => {
  const { title, content } = req.body;
  fakedb.push({
    title,
    _id: fakedb.length.toString(),
    content,
    createdAt: "456",
    author: "1",
  });
  res.sendStatus(200);
});

app.delete("/api/posts/:postID", (req, res) => {
  fakedb = fakedb.filter((i) => i._id !== req.params.postID);
  res.sendStatus(200);
});
1 Answers

There are 2 important points to understand what is going on:

  • status is the status of the mutation request
  • Landing.jsx uses the index as a key for the BlogPost component.

A key is the only thing React uses to identify DOM elements. When the data is refetched after the delete operation, now the response data is an array with 3 blog post objects. Because you are using the index as key, React thinks the 3rd element in the array (before index 3 but now index 2) is the same element as it had before. Since the props are different it re-render the component. At this point the component holds the status of the previous operation.

The React documentation warns against the use of keys because it can display wrong data.

Check out Robin Pokorny’s article for an in-depth explanation on the negative impacts of using an index as a key.

Related