피드관리 - Part 3 피드 수정 및 삭제

Project Diary/React + MariaDB (PicShare WebApp)

사용자가 작성한 피드를 수정하고 삭제할 수 있는 기능을 구현합니다. 이 글에서는 피드 수정 및 삭제를 위한 프론트엔드와 백엔드 로직을 상세히 설명합니다.


 

프론트엔드

 

1. 피드 수정 컴포넌트( FeedUpdateSection )

 

1-1 초기 상태 및 데이터 가져오기

import React, { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import axios from "axios";
import { useSelector } from "react-redux";

const serverUrl = import.meta.env.VITE_API_URL;

const FeedUpdateSection = () => {
  const user = useSelector((state) => state.members.user);
  const navigate = useNavigate();
  const { postId } = useParams();
  const [content, setContent] = useState("");
  const [hashtags, setHashtags] = useState([]);
  const [photo, setPhoto] = useState([]);

  useEffect(() => {
    const fetchPostData = async () => {
      try {
        const response = await axios.get(`${serverUrl}/feed/postbypostid`, {
          params: { postId },
        });
        const post = response.data;
        setContent(post.content);
        setHashtags(post.hashtags);
        setPhoto(post.images);
      } catch (err) {
        console.error(err);
      }
    };

    fetchPostData();
  }, [postId]);
  • useEffect : 컴포넌트가 마운트될 때 postId에 해당하는 피드 데이터를 서버에서 가져와 상태에 저장합니다.
  • axios.get : 서버에 GET 요청을 보내 피드 데이터를 가져옵니다.

1-2 수정된 내용을 서버에 저장 요청

  const handleSave = async () => {
    try {
      await axios.put(`${serverUrl}/feed/update/${postId}`, {
        content,
      });
      navigate("/feed");
    } catch (err) {
      console.error(err);
    }
  };
  • handleSave: 사용자가 입력한 내용을 서버에 PUT 요청으로 전송하여 피드를 수정합니다.
  • axios.put: 서버에 PUT 요청을 보내 수정된 피드 데이터를 저장합니다.

1-3 뒤로 가기 버튼

  const handleBack = () => {
    handleSave();
    navigate(-1);
  };
  • 뒤로 가기 버튼을 눌렀을 때 수정된 내용을 저장하고 이전 페이지로 돌아갑니다.

 

2. 피드 상세보기 및 수정/삭제 버튼( PersonalFeedDetailSection )

본이이 작성판 피드만 수정/삭제할 수 있어야 하기때문에 갱니 피드 상세보기에 수정/삭제 기능을 구현합니다.

 

2-1 초기 상태 설정

상세보기 페이지로 이동할 때 필요한 상태를 설정합니다.

import React, { useState } from "react";
import { useParams, useLocation, useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import axios from "axios";
import { fetchAllFeed } from "@/store/feed";

const serverUrl = import.meta.env.VITE_API_URL;

const PersonalFeedDetailSection = () => {
  const { postId } = useParams();
  const location = useLocation();
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const { filteredFeeds } = location.state || { filteredFeeds: [] };
  const [showEditDelete, setShowEditDelete] = useState({});
  const user = useSelector((state) => state.members.user);

  const postIndex = filteredFeeds.findIndex(
    (feed) => feed.postId === parseInt(postId)
  );
  const postsToDisplay = filteredFeeds.slice(postIndex);
  • useParams, useLocation, useNavigate: React Router의 훅을 사용하여 라우팅과 관련된 정보를 가져옵니다.
  • useSelector : Redux 상태에서 사용자 정보를 가져옵니다.

2-2  수정/삭제 버튼 토글 핸들러

  const toggleEditDelete = (postId) => {
    setShowEditDelete((prev) => ({
      ...prev,
      [postId]: !prev[postId],
    }));
  };
  • toggleEditDelete : 해당 포스트 ID에 대한 수정/삭제 버튼의 표시 상태를 토글합니다.

2-3 피드 삭제 핸들러

  const handleDelete = (postId) => {
    const confirmDelete = window.confirm("정말 삭제하시겠습니까?");
    if (confirmDelete) {
      axios
        .delete(`${serverUrl}/feed/delete`, { params: { postId } })
        .then((res) => {
          if (res.data === "포스트 및 관련 데이터 삭제 완료") {
            dispatch(fetchAllFeed());
            navigate(-1);
          } else {
            alert("삭제하지 못했습니다.");
          }
        })
        .catch((err) => console.log(err));
    }
  };
  • handleDelete : 삭제 확인 후 서버에 DELETE 요청을 보내 포스트를 삭제합니다.
  • 삭제가 성공하면 모든 피드를 다시 가져와 갱신하고, 이전 페이지로 돌아갑니다.

3. Redux Slice: 피드 상태 관리

import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";

const serverUrl = import.meta.env.VITE_API_URL;

const feedSlice = createSlice({
  name: "feed",
  initialState: {
    feed: [],
    feeds: [],
    loading: false,
    error: null,
  },
  reducers: {
    initFeedStart(state) {
      state.loading = true;
      state.error = null;
    },
    initFeedSuccess(state, action) {
      state.feeds = action.payload;
      state.loading = false;
    },
    initFeedFail(state, action) {
      state.error = action.payload;
      state.loading = false;
    },
    initPostByPostid(state, action) {
      const post = action.payload;
      const index = state.feeds.findIndex(
        (feed) => feed.postId === post.postId
      );
      if (index === -1) {
        state.feeds.push(post);
      } else {
        state.feeds[index] = post;
      }
      state.loading = false;
    },
    initUpdateFeed(state, action) {
      const updatedPost = action.payload;
      const index = state.feeds.findIndex(
        (post) => post.postId === updatedPost.postId
      );
      if (index !== -1) {
        state.feeds[index] = updatedPost;
      }
    },
  },
});

export const {
  initFeedStart,
  initFeedSuccess,
  initFeedFail,
  initUpdateFeed,
  initPostByPostid,
} = feedSlice.actions;

export default feedSlice.reducer;
  • initFeedStart, initFeedSuccess, initFeedFai  : 피드 데이터를 가져오는 상태를 관리합니다.
  • initPostByPostid : 특정 포스트 데이터를 상태에 추가하거나 업데이트합니다.
  • initUpdateFeed : 수정된 포스트 데이터를 상태에 업데이트합니다.

> 피드 데이터를 가져오고 업데이트하는 Thunk 함수

// 모든 피드를 가져오는 Thunk 함수
export const fetchAllFeed = (filter) => async (dispatch) => {
  dispatch(initFeedStart());
  try {
    let url = `${serverUrl}/feed/all`;
    if (filter && filter.type === "following") {
      url += `?userNos=${filter.userNos.join(",")}`;
    }
    const response = await axios.get(url);
    dispatch(initFeedSuccess(response.data));
  } catch (error) {
    dispatch(initFeedFail(error.toString()));
  }
};

// 특정 포스트를 가져오는 Thunk 함수
export const fetchPostByPostid = (postId) => async (dispatch) => {
  dispatch(initFeedStart());
  try {
    const response = await axios.get(`${serverUrl}/feed/postbypostid?postId=${postId}`);
    dispatch(initPostByPostid(response.data));
  } catch (error) {
    dispatch(initFeedFail(error.toString()));
  }
};

// 피드를 업데이트하는 Thunk 함수
export const updateFeed = (postId, updatedData) => async (dispatch) => {
  try {
    const response = await axios.put(`${serverUrl}/feed/update/${postId}`, updatedData);
    dispatch(initUpdateFeed(response.data));
  } catch (error) {
    console.error("Error updating post:", error);
  }
};
  • fetchAllFeed : 필터 조건에 따라 모든 피드를 가져옵니다.
  • fetchPostByPostid : 특정 포스트를 가져옵니다.
  • updateFeed : 포스트를 업데이트합니다.

 

백엔드

 

1. 피드 수정  API엔드포인트

import express from "express";
import { db } from "../db.js";

const feedRouter = express.Router();

feedRouter.put("/update/:postId", (req, res) => {
  const { postId } = req.params;
  const { content } = req.body;

  db.query(
    `UPDATE posts SET content = ?, updated_at = ? WHERE postId = ?`,
    [content, new Date(), postId],
    (err, result) => {
      if (err) {
        console.error("Error updating post:", err);
        return res.status(500).send("실패");
      }
      res.send({ message: "피드가 성공적으로 수정되었습니다." });
    }
  );
});
  • PUT /update/:postId : 요청된 포스트 ID에 대한 데이터를 수정합니다.

 

2. 피드 삭제  API엔드포인트

feedRouter.delete("/delete", (req, res) => {
  const { postId } = req.query;

  // 피드 삭제
  db.query("DELETE FROM posts WHERE postId=?", [postId], (err, postResult) => {
    if (err) {
      console.error("포스트 삭제 중 에러:", err);
      res.status(500).send("포스트 삭제 실패");
      return;
    }

    // 이미지 삭제
    db.query(
      "DELETE FROM images WHERE postId=?",
      [postId],
      (err, imageResult) => {
        if (err) {
          console.error("이미지 삭제 중 에러:", err);
          res.status(500).send("이미지 삭제 실패");
          return;
        }

        // 해시태그 관계 삭제
        db.query(
          "DELETE FROM post_hashtags WHERE postId=?",
          [postId],
          (err, hashtagResult) => {
            if (err) {
              console.error("해시태그 관계 삭제 중 에러:", err);
              res.status(500).send("해시태그 관계 삭제 실패");
              return;
            }

            // 좋아요 정보 삭제
            db.query(
              "DELETE FROM postlike WHERE postId=?",
              [postId],
              (err, likeResult) => {
                if (err) {
                  console.error("좋아요 정보 삭제 중 에러:", err);
                  res.status(500).send("좋아요 정보 삭제 실패");
                  return;
                }

                console.log("포스트 및 관련 데이터 삭제 완료");
                res.send("포스트 및 관련 데이터 삭제 완료");
              }
            );
          }
        );
      }
    );
  });
});

export default feedRouter;

 


피드 수정

 

피드 삭제