2 글 보임 - 1 에서 2 까지 (총 2 중에서)
-
글쓴이글
-
2023년 9월 20일 22:45 #98759
쥬니참가자안녕하세요 게시물 조회하는 코드를 작성중입니다. 이전 문의에서 선생님 덕분에 오류를 잘해결할수있었습니다. 감사합니다! 현재 코드 구성은 이런식입니다. '/'경로에 우선 post.tsx의 post컴포넌트가 있고 그 자식으로 dashboard컴포넌트가 있습니다. post: 게시글목록을 조회할수있는 컴포넌트 dashboard: 게시글 하나하나의 내용을 볼수있는 컴폰넌트 dashboard 모달창을 띄우고, 해당 컴포넌트 안에서 게시글 삭제를 실행하면 아래의 코드가 실행됩니다.
const postdelete = async (feedIdToDelete: number) => { console.log('삭제할피드', feedIdToDelete); //해당피드아이디로 게시물 삭제 api호출 try { const response = await axios.patch(`${API_BASE_URL}/app/feeds/${feedIdToDelete}/delete-status`, null, { headers: { 'x-access-token': `${token}`, }, } ); console.log(response.data); if (response.data.code === 1000) { // 3가지 state 모두 전역(recoil)으로 관리중입니다 setFeeds([]); //post컴포넌트의 feeds목록을 초기화 setPage(0); //page를 0으로 설정하여 데이터를 처음부터 새롭게 불러오기 setDashboardModified(true); //게시글 수정, 삭제여부를 true로 설정 props.onRequestClose(false); } else { alert('게시글 삭제 불가'); setPopupIsOpen(false); setModalIsOpen(false); } } catch (error) { console.log('게시글 삭제 실패: ', error); throw error; } }
그리고 아래는 post.tsx입니다.
import React, { useState, useEffect, useRef } from "react"; import Modal from 'react-modal'; import moment from "moment"; import Slider from "react-slick"; import { ReactComponent as Bookmark } from '../../assets/bookmark.svg'; import { ReactComponent as Comment } from '../../assets/comment.svg'; import { ReactComponent as Heart } from '../../assets/heart.svg'; import feedloginprofile from "../../images/default_profile.png"; import PrevArrow from '../../assets/prev-arrow.svg'; import NextArrow from '../../assets/next-arrow.svg'; import Dashboard from '../dashboard/Dashboard'; import styled from "styled-components"; import { useRecoilState } from 'recoil'; import { feedsState } from "../../recoil/feedsState"; import { DashboardModalState, DashboardModifiedState, PageState, PostedState } from "../../recoil/feedModal"; import axios from "axios";
export type FeedType = { feedId: number; feedLoginId: string; feedText: string; feedCreatedAt: string; feedUpdatedAt: string; feedCommentCount: number;//TypeScript에서 숫자 데이터 타입: number contentsList: { contentsId: number; contentsUrl: string;//ypeScript에서 배열 데이터 타입 : [] createdAt: string; updatedAt: string; }[]; comments: { id: number; loginId: string; commentText: string; createdAt: string; updatedAt: string; }[]; }
export type CommentType = { id: number; loginId: string; commentText: string; createdAt: string; updatedAt: string; }
function Post() { const API_BASE_URL = 'https://api.gridge-test.com'; const [selectedFeed, setSelectedFeed] = useState<FeedType | null>(null); const [dashboardIsOpen, setDashboardIsOpen] = useRecoilState(DashboardModalState); const [Posted, setPosted] = useRecoilState(PostedState); // 현재 페이지 const [DashboardModified, setDashboardModified] = useRecoilState(DashboardModifiedState); // 현재 페이지 const [feeds, setfeeds] = useRecoilState(feedsState); const [page, setPage] = useRecoilState(PageState); // 현재 페이지 const [loading, setLoading] = useState(true);//로딩여부 로딩중:true, 로딩끝:false const [hasMore, setHasMore] = useState(true); // 다음 페이지가 있는지 여부 const target = useRef<HTMLDivElement | null>(null); const size = 10; // 한 페이지당 표시할 아이템 수 const token = localStorage.getItem('token'); const settings = { dots: true, infinite: false, arrow: true, speed: 500, slidesToShow: 1, slidesToScroll: 1, nextArrow: ( <NextTo> <Arrow src={NextArrow} /> </NextTo> ), prevArrow: (
<Arrow src={PrevArrow} />
), };
const postComment = () => { //게시하면 새롭게 댓글불러오기 }
// feeds 배열을 최신순으로 정렬 const sortedFeeds: FeedType[] = (feeds.length > 0) ? feeds.slice().sort((a, b) => { // 'createdAt' 속성을 기준으로 비교 return new Date(b.feedCreatedAt).getTime() - new Date(a.feedCreatedAt).getTime(); }) : [];
const fetchFeedsData = async () => { setLoading(true); try { const response = await axios.get(`${API_BASE_URL}/app/feeds`, { params: { 'pageIndex': page, 'size': size }, headers: { 'x-access-token': `${token}`, }, }) console.log('page: ',page,'조회결과', response.data.result); const data = response.data.result; console.log('prev: ', feeds, 'data: ',data) ; setfeeds(prev => [...prev, ...data]);
// 각 피드에 대한 댓글 데이터 가져오기 data.forEach((feed: FeedType) => { fetchAndSetComments(feed.feedId); });
if (data.length < size) { setHasMore(false); }
setLoading(false); } catch (error) { console.log('페이지 피드 조회 실패: ', error); setLoading(false); throw error; } };
const fetchAndSetComments = async (feedId: number) => { try { const response = await axios.get(`${API_BASE_URL}/app/feeds/${feedId}/comments`, { params: { 'pageIndex': 0, 'size': size }, headers: { 'x-access-token': `${token}`, }, })
const commentsData = response.data.result; console.log('CommentsData', commentsData);
//해당피드를 찾아서 comments설정 setfeeds((prevfeeds) => { const updatedfeeds = prevfeeds.map((feed) => { if (feed.feedId === feedId) { return { ...feed, comments: commentsData }; } return feed; }); return updatedfeeds; }); } catch (error) { console.log('페이지 댓글설정 실패: ', error); } };
// 페이지 로딩 시 댓글 데이터를 초기 로드 useEffect(() => { console.log('DashboardModified', DashboardModified); if (DashboardModified || Posted) {//게시글 수정 및 삭제 또는 feed게시 console.log('dashboard가 수정 및 삭제되거나 게시글이 새롭게 업로드외서 실행되는 fetchFeedData'); setLoading(true); fetchFeedsData(); }
if (DashboardModified) { setDashboardModified(false); } else if (Posted) { setPosted(false);
} }, [DashboardModified, Posted]);
useEffect(() => { console.log('페이지가 바뀌어서실행횓는 fetchFeedData'); setLoading(true); fetchFeedsData(); },[page]);
useEffect(() => { if (target.current && !loading) { console.log('페이지 + 1'); //로딩완료되었을때만 실행(로딩중=target을 찾는중) const observer = new IntersectionObserver( entries => { if (entries[0].isIntersecting) { setPage(prev => prev + 1) } }, { threshold: 1 } ); observer.observe(target.current); } }, [loading]);
const [showMore, setShowMore] = useState(false); //더보기여부 const [heart, setHeart] = useState(false); const [hearCounts, setHeartCounts] = useState(251);
const handleCloseModal = (newState: boolean) => { setSelectedFeed(null); setDashboardIsOpen(newState); }
return ( <> {sortedFeeds && sortedFeeds.map(function (feed) { const sortedComments = feed.comments ? (feed.comments.slice().sort((a, b) => { return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); })) : []
const fullText = feed.feedLoginId + ' ' + feed.feedText; const getDayMinuteCounter = (date?: Date): number | string => { if (!date) { return ''; }
const today = moment(); const commentDate = moment(date); const dayDiff = today.diff(commentDate, 'days'); const hourDiff = today.diff(commentDate, 'hours'); const minutesDiff = today.diff(commentDate, 'minutes'); if (dayDiff === 0 && hourDiff === 0) { // 작성한지 1시간도 안지났을때 const minutes = Math.abs(minutesDiff);// 절대값 사용 return minutes + '분 전'; // '분' 로 표시 }
else if (dayDiff === 0 && hourDiff <= 24) { // 작성한지 1시간은 넘었지만 하루는 안지났을때, const hour = Math.abs(hourDiff); return hour + '시간 전'; // '시간'으로 표시 }
else if (-dayDiff >= 30) { const formattedDate = moment(date).format('YYYY년 MM월 DD일'); return formattedDate; } else { return Math.abs(dayDiff) + '일 전'; // '일'로 표시 } };
const handleHeart = () => { if (heart) { setHeart(false); setHeartCounts(hearCounts - 1); } else { setHeart(true); setHeartCounts(hearCounts + 1) } }
const handleOpenModal = () => { setSelectedFeed(feed); setDashboardIsOpen(true); };
return ( <PostContainer key={feed.feedId}> <PostImage> <PostUserInfo> <FeedLoginProfile></FeedLoginProfile> <PostUserName>{feed.feedLoginId}</PostUserName> </PostUserInfo> <StyledSlider {...settings}> <SliderImg src={feed.contentsList[0].contentsUrl} alt={`postImage`} /> </StyledSlider> </PostImage> <div className="PostActions"> <PostIcons> <Likes> <HeartBtn onClick={handleHeart}> <HeartIcon width="24" height="24" heart={heart}></HeartIcon> </HeartBtn> <Comment width="24" height="24"></Comment> </Likes> <Bookmark stroke="black" width="24" height="24"></Bookmark> </PostIcons> <Reactions> <LikesCount>좋아요 {hearCounts}개</LikesCount> <div className="PostText"> { showMore ? <> <FeedUserId>{feed.feedLoginId} </FeedUserId><FeedText>{feed.feedText}</FeedText> </> : <> <FeedUserId>{feed.feedLoginId}</FeedUserId> <FeedText>{feed.feedText.slice(0, (100 - feed.feedLoginId.length))}</FeedText> </> } {/*펼쳤을때는 더보기가 안보이게 -> !showmore*/} {!showMore && fullText.length > 100 && <ViewMore onClick={() => { setShowMore(true) }} className="ViewMore">...더보기</ViewMore>} </div> <PostComments> { sortedComments.slice(0, 2).map((comment) => ( <CommentBox> <CommentProfile></CommentProfile> <div> <CommentId>{comment.loginId}</CommentId><CommentContent>{comment.commentText}</CommentContent> <p>{getDayMinuteCounter(new Date(comment.createdAt))}</p> </div> </CommentBox> )) } {/*펼쳤을때는 더보기가 안보이게 -> !showmore*/} {feed.feedCommentCount > 2 && <CommentsMore onClick={handleOpenModal}>댓글 {feed.feedCommentCount}개 모두 보기</CommentsMore>} <p style={{ fontSize: "12px", color: "#B2B2B2" }}>{getDayMinuteCounter(new Date(feed.feedCreatedAt))}</p> </PostComments> </Reactions> <CommentWrapper> <CommentingUser></CommentingUser> <CommentInput onClick={handleOpenModal}>댓글 달기...</CommentInput> <Posting onClick={postComment}>게시</Posting> {dashboardIsOpen && ( <Modal isOpen={dashboardIsOpen} onRequestClose={() => { handleCloseModal(false) }} contentLabel="게시물 상세 모달" style={ModalStyle} > {selectedFeed && ( <Dashboard selectedFeed={selectedFeed} isOpen={dashboardIsOpen} onRequestClose={handleCloseModal}></Dashboard> )} </Modal>)} </CommentWrapper> </div> </PostContainer> ); })} {hasMore && <div ref={target}>loading...</div>} </> ); }
export default Post;
우선 현재 페이징 무한 스크롤을(게시글 10개마다 스크롤이 되도록) 구현중입니다. 위 코드에서 문제는 하나의 게시글을 삭제했을 때 새롭게 게시글데이터 10개를 가져오면 끝나야할것을 page를 1증가시켜 그다음 게시글데이터 10개까지 가져와 총 20개가 로드됩니다.
처음 페이지가 렌더링될때는 10개의 데이터만 가져와집니다.
또한 가지 문제점은 게시글을 한번 삭제했을때는 해당 게시글이 삭제된 게시글목록이 잘 나타나지만, 한번더 게시글을 삭제하면 fetchFeedsData가 2번씩 호출돼서그런지,, 같은 피드목록이 2번씩 중복되어 나타납니다.. 알고리즘을 복잡하게 짜다보니 코드가 꼬이게 된것같습니다..ㅜ 어떤식으로 수정하면 좋을까요
2023년 9월 21일 09:48 #98803
codingapple키 마스터글없애면 그만큼 화면 높이가 줄어들어서 그럴수도요 화면높이 최솟값을 설정해주거나 글없애면 무한스크롤기능 잠깐 정지시키거나 그래야할듯요 fetchFeedsData()는 다른 useEffect로 빼봅시다
-
글쓴이글
2 글 보임 - 1 에서 2 까지 (총 2 중에서)
- 답변은 로그인 후 가능합니다.