[React] DELETE 요청 이후 state를 변경해주지 않으면 UI에는 삭제된 데이터가 여전히 남는다 (feat. 자식 컴포넌트에서 부모 컴포넌트의 state를 어떻게 수정할 것인가?)

느려도 꾸준한 발걸음·2024년 7월 18일
0
post-thumbnail

1. 상황 설명

게시판 삭제 기능을 구현하고 있었습니다.
axios로 서버에 DELETE 요청을 보내면
백엔드의 DRF view가 DB에서 해당 게시글을 찾아 삭제하는 구조입니다.

삭제 버튼을 누르면, DB에서는 데이터가 잘 삭제되는데,
화면에는 여전히 해당 게시글이 남아있는 문제를 마주하였습니다.

새로고침을 하면 게시글이 사라지지만,
새로고침을 하기 전 까진 여전히 해당 게시글이 화면에 남아있는 상황입니다.

이번 포스팅에선, 제가 어떻게 이 문제를 해결하였는지 정리해보려 합니다.

2. 소스코드

아래는 모든 서비스 사용자들이 업로드한 게시글이 로드되는 화면의 전체 소스코드입니다.

자신이 작성한 게시글에 한하여, 삭제하기 버튼이 렌더링됩니다.

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import DeletePost from "./DeletePost";
import axiosInstance from "../axios";

const Post = () => {
  //전체 게시글 담을 state
  const [post, setPost] = useState([]);
  const [currentUser, setCurrentUser] = useState(null);

  //사용자 불러오기
  useEffect(() => {
    const token = localStorage.getItem("access_token");
    if (token) {
      axiosInstance
        .get("user/current_user/")
        .then((res) => setCurrentUser(res.data));
    }
  }, []);

  //전체 게시글을 불러오는 함수
  useEffect(() => {
    const postDataOriginUrl = "http://127.0.0.1:8000/api/";
    fetch(postDataOriginUrl)
      .then((resopnse) => resopnse.json())
      .then((data) => setPost(data));
    console.log(post);
  }, []);

  return (
    <div>
      <button>
        <Link to={"/post/posting"}>내 여행 후기 작성하기</Link>
      </button>
      {post.map((posting) => (
        <Link
          to={`/${posting.id}`}
          style={{ textDecoration: "none", color: "black" }}
        >
          <div
            key={posting.id}
            style={{
              border: "2px solid black",
              margin: "5px",
              borderRadius: "10px",
              width: "100vh",
            }}
          >
            <ul style={{ listStyleType: "none" }}>
              <li style={{ fontSize: "30px", fontWeight: "bold" }}>
                {posting.title}
              </li>
              <li>{posting.content}</li>
              <li>작성자: {posting.author}</li>
              {currentUser && currentUser.id === posting.author ? (
                <DeletePost postId={posting.id} setPost={setPost} />
              ) : null}
            </ul>
          </div>
        </Link>
      ))}
    </div>
  );
};

export default Post;

아래와 같이,
현재 로그인 한 사용자와 게시글의 작성자가 같다면, 삭제 버튼인
DeletePost 컴포넌트가 렌더링 되고 있습니다.

  {currentUser && currentUser.id === posting.author ? (
                <DeletePost postId={posting.id} setPost={setPost} />
              ) : null}

여기서 DeletePost 컴포넌트는 Post 컴포넌트의 자식 컴포넌트입니다.

저의 경우, 서버로 데이터를 지워달라는 요청을 보내는 로직은 DeletePost 컴포넌트에 따로 분리하여 작성하였습니다.

그럼 이제, DeletePost 컴포넌트의 코드도 살펴보겠습니다.

아래는 DeletePost 컴포넌트의 전체 코드입니다.

import React from "react";
import axiosInstance from "../axios";

const DeletePost = ({ postId, setPost }) => {
  const handleClick = (e) => {
    e.preventDefault();
    try {
      axiosInstance.delete(`delete/${postId}/`);
      setPost((prevPosts) => prevPosts.filter((post) => post.id !== postId));
    } catch (e) {
      console.log(e);
    }
  };

  return <button onClick={handleClick}>삭제하기</button>;
};

export default DeletePost;

axios를 이용해 DELETE 요청을 보내고 있습니다.
이때, 부모 컴포넌트인 Post 컴포넌트에서 prop으로 전달받은 postId 값과 동일한 id를 갖는 게시물 데이터를 삭제하도록 엔드포인트를 동적으로 설정하였습니다.

위의 코드를 완성하기까지 겪었던 문제와 고민을 순차적 흐름에 맞추어 정리해보면 다음과 같습니다.

  1. DB에서 삭제된 게시물 객체는 UI에서도 지워야 한다. (새로고침 없이 즉각반영 되게 만들어야 한다)
  2. 게시물 객체는 Post 컴포넌트에서 렌더링된다.
  3. 고로, 게시물 UI에서 하나의 게시물을 삭제한다는 것은, Post컴포넌트의 state를 변경해야 하는 것이다.
  4. 이 로직은 삭제 버튼을 클릭했을 때 일어나기에 Post컴포넌트의 자식 컴포넌트인 DeletePost컴포넌트 내에서 Post컴포넌트의 state에서 해당 게시물을 제거해야 한다.
  5. 그렇다면.. 어떻게 자식 컴포넌트에서 부모 컴포넌트의 값을 변경할 것인가?

해답은, setState 함수를 prop으로 전달하는 것이었습니다.

<DeletePost postId={posting.id} setPost={setPost} />

이렇게 setState함수인 setPost를 setPost라는 이름의 prop으로 전달하면, 자식 컴포넌트 내에서 부모 컴포넌트의 state변수에 접근할 수 있습니다.

이후 아래와 같이 필터링을 통해 UI에서도 새로고침 없이 즉각적으로 삭제 사항을 반영할 수 있습니다.

 setPost((prevPosts) => prevPosts.filter((post) => post.id !== postId));

여기서 prevPosts는 부모 컴포넌트인 Post 컴포넌트의 상태입니다.
기억을 더듬어보며 Post 컴포넌트의 Post state가 어떤 값을 저장하는지 다시 살펴보겠습니다.

아래는 Post 컴포넌트의 일부 코드입니다.

const Post = () => {
  //전체 게시글 담을 state
  const [post, setPost] = useState([]);
  const [currentUser, setCurrentUser] = useState(null);

  //중략

  //전체 게시글을 불러오는 함수
  useEffect(() => {
    const postDataOriginUrl = "http://127.0.0.1:8000/api/";
    fetch(postDataOriginUrl)
      .then((resopnse) => resopnse.json())
      .then((data) => setPost(data));
    console.log(post);
  }, []);

전체 게시글을 저장하고 있는 state였네요.
게시물 객체를 요소로 하는 배열입니다.

이를 filter로 순회하고 있으니, filter함수 내의 post는 서비스 유저들이 업로드한 게시글 객체 하나 하나가 됩니다.

post.id 는 해당 게시글의 고유 id가 되겠네요.
postId는 prop으로 전달받은 "삭제할 게시물의 id" 입니다.

이제, 삭제한 게시물의 id도 포함하고 있는 post state을 돌며,
삭제된 id와 일치하지 않는 녀석들만 남기도록 코드를 구현했습니다.

3. 마무리 및 교훈

이번 포스팅의 범위 내에서,
새로 배운 내용을 정리해보면 크게 아래의 두 개가 되겠습니다.

  1. Delete 메소드를 구현하더라도, DB에서는 데이터가 삭제되지만, UI에는 새로고침을 하기 전까진 여전히 해당 데이터가 남아있는 문제점이 있다
  2. 이를 해결하기 위해 DB삭제 요청을 보낸 이후, setState 함수 내에서 filter작업을 진행해 UI에도 삭제 사항을 반영해주어야 한다.
profile
웹 풀스택 개발자를 준비하고 있습니다. MERN스택을 수상하리만큼 사랑합니다.

0개의 댓글