• 로그인
  • 장바구니에 상품이 없습니다.

home2 게시판 React 게시판 게시글 조회 오류

게시글 조회 오류

2 글 보임 - 1 에서 2 까지 (총 2 중에서)
  • 글쓴이
  • #98230

    쥬니
    참가자
    안녕하세요. 인스타그램 클론코딩을 하는중에 에러가 해결되지않아 질문드립니다. (3일동안 고민해봤습니다😥)
    현재 메인홈에서 피드 조회하는 과정에서 문제가생겼습니다.
    우선 아래는 피드조회하는 컴포넌트 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 } from "../../recoil/feedModal";
    import axios from "axios";
    const PostContainer = styled.div`
      width: 100%;
      background-color: white;
      border: 1px solid #CCCCCC;
      border-radius: 10px;
    `;
    const PostUserInfo = styled.div`
      position: absolute;
      z-index: 2;
      top: 10px;
      left: 20px;
      display: flex;
      align-items: center;
      gap: 10px;
    `;
    const PostUserName = styled.p`
      font-size: 16px;
      font-weight: 600;
      line-height: 24px;
      letter-spacing: 0em;
      text-align: left;
      color: white;
    `;
    const PostIcons = styled.div`
      display: flex;
      justify-content: space-between;
      padding: 10px;
      align-items: center;
    `;
    const Likes = styled.div`
      display: flex;
      justify-content: space-between;
      padding: 0;
      gap: 15px;
      align-items: center;
    `;
    const LikesCount = styled.p`
      font-size: 14px;
      font-weight: 700;
      line-height: 20px;
      letter-spacing: 0em;
      text-align: left;
      margin: 5px 0 10px 0;
    `;
    const Reactions = styled.div`
      padding: 0 10px;
    `;
    const ViewMore = styled.button`
      background-color: transparent;
      border: none;
      color: #7F7F7F;
      font-size: 14px;
      font-weight: 500;
      line-height: 20px;
      letter-spacing: 0em;
      text-align: left;
    `;
    const CommentsMore = styled.button`
      font-size: 14px;
      font-weight: 500;
      line-height: 20px;
      letter-spacing: 0em;
      text-align: left;
      color: #999999;
      background-color: transparent;
      border: none;
    `;
    const PostComments = styled.div`
      margin-bottom: 10px;
    `;
    const FeedUserId = styled.span`
      font-size: 14px;
      font-weight: 700;
      line-height: 20px;
      letter-spacing: 0em;
      text-align: left;
      margin-right: 10px;
    `;
    const FeedText = styled.span`
      font-size: 14px;
      font-weight: 500;
      line-height: 20px;
      letter-spacing: 0em;
      text-align: left;
    `;
    const CommentBox = styled.div`
      display: flex;
      gap: 5px;
      margin: 10px 0;
    `;
    const CommentId = styled.span`
      font-size: 14px;
      font-weight: 530;
      line-height: 20px;
      letter-spacing: 0em;
      text-align: left;
      margin-right: 5px;
    `;
    const CommentContent = styled.span`
      font-size: 14px;
      font-weight: 500;
      line-height: 20px;
      letter-spacing: 0em;
      text-align: left;
    `;
    const CommentWrapper = styled.div`
      width: 100%;
      height: 60px;
      border-top: 1px solid #B2B2B2;
      background-color: white;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-radius: 0 0 10px 10px;
    `;
    const ModalStyle: ReactModal.Styles = {
      overlay: {
        backgroundColor: 'rgba(69, 69, 69, 0.65)',
        zIndex: 1000,
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        top: 0,
        left: 0,
        width: '100%',
        height: '100%',
        position: "fixed"
      },
      content: {
        width: '1070px',
        height: '600px',
        padding: '0px',
        backgroundColor: 'white',
        borderRadius: '10px',
        margin: '0',
        zIndex: "150",
        position: "absolute",
        top: "50%",
        left: "50%",
        transform: "translate(-50%, -50%)",
        display: 'flex',
        overflow: "hidden"
      },
    };
    const PostImage = styled.div`
      width: 100%;
      height: 520px;
      position: relative;
    `;
    const FeedLoginProfile = styled.div`
      width: 35px;
      height: 35px;
      border-radius: 50%;
      background-image: url(${feedloginprofile});
      background-size: cover;
      background-position: center;
    `;
    const StyledSlider = styled(Slider)`
      height: 520px;
      width: 520px;
      position: relative;</pre>
    <pre>  .slick-prev::before,
      .slick-next::before {
        opacity: 0;
        display: none;
      }
      .slick-slide div {
        cursor: pointer;
      }
      .slick-prev {
        width: 30px;
        height: 30px;
        position: absolute;
        left: 3%;
        z-index: 3;
      }
      .slick-next {
        width: 30px;
        height: 30px;
        position: absolute;
        right: 3%;
        z-index: 3;
      }</pre>
    <pre>  .slick-dots {
        position: absolute;
        bottom: 5%;
     
        button {
          display: block;
          width: 1rem;
          height: 1rem;
          padding: 0;
          border: none;
          border-radius: 100%;
          background-color:#E0E0E0;
          text-indent: -9999px;
        }</pre>
    <pre>    li.slick-active button {
          background-color: #2F80ED;
        }
      }
    `;
    const SliderImg = styled.img`
      width: 100%;
      height: 520px;
      border-radius: 10px 10px 0px 0px;
      border: 0;
    `;
    const Pre = styled.div`
      width: 30px;
      height: 30px;
      position: absolute;
      right: 3%;
      z-index: 3;
    `;
    const NextTo = styled.div`
      width: 30px;
      height: 30px;
      position: absolute;
      right: 3%;
      z-index: 3;
    `;
    const Arrow = styled.img`
      width: 100%;
      height: 100%;
    `;
    const CommentProfile = styled.div`
      width: 30px;
      height: 30px;
      border-radius: 50%;
      background-image: url(${feedloginprofile});
      background-size: cover;
      background-position: center;
    `;
    const Posting = styled.button`
        width: 60px;
        height: 100%;
        font-size: 14px;
        font-weight: 700;
        line-height: 20px;
        letter-spacing: 0em;
        text-align: right;
        color: #B2DDFF;
        text-align: center;
        border: none;
        background-color: transparent;
    }`
    const CommentingUser = styled.div`
      width: 30px;
      height: 30px;
      border-radius: 50%;
      background-image: url(${feedloginprofile});
      background-size: cover;
      background-position: center;
      margin-left: 10px;
    `;
    const CommentInput = styled.button`
      width: 80%;
      height: 90%;
      background-color: transparent;
      border: none;
      outline: none;
      text-align: start;
      cursor: pointer;
    `;
    const HeartIcon = styled(Heart) <{ heart: boolean }>`
      fill: ${(props) => (props.heart ? 'red' : 'white')};
      transition: fill 0.3s ease;
    `;
    const HeartBtn = styled.button`
      background: transparent;
      border: none;
      padding: 0;
      cursor: pointer;
    `;
    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;
      }[];
    }
    function Post() {
      const API_BASE_URL = 'https://api.gridge-test.com';
      const [, setSelectedFeed] = useState<FeedType | null>(null);
      const [dashboardIsOpen, setDashboardIsOpen] = useRecoilState(DashboardModalState);
      const [feeds, setfeeds] = useRecoilState(feedsState);
      const [page,] = useState(1); // 현재 페이지
      const postContainerRef = useRef(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 = feeds.slice().sort((a, b) => {
        // 'createdAt' 속성을 기준으로 비교
        return new Date(b.feedCreatedAt).getTime() - new Date(a.feedCreatedAt).getTime();
      });
      const fetchFeedsData = async () => {
        try {
          const response = await axios.get(`${API_BASE_URL}/app/feeds`, {
            params: {
              'pageIndex': page,
              'size': size
            },
            headers: {
              'x-access-token': `${token}`,
            },
          })
          console.log(response.data.result);
          return response.data.result;
        } catch (error) {
          console.log('페이지 피드 조회 실패: ', error);
          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(() => {
        fetchFeedsData().then((data) => {
          setfeeds(data);
        })
        sortedFeeds.forEach((feed: FeedType) => {
          fetchAndSetComments(feed.feedId);
        });
      }, []);
      return (
        <>
          {sortedFeeds.map(function (feed) {
            const [showMore, setShowMore] = useState(false); //더보기여부
            const [commentsMore, setCommentsMore] = useState(false);
            const [heart, setHeart] = useState(false);
            const [hearCounts, setHeartCounts] = useState(251);
            const sortedComments = (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 = commentDate.diff(today, 'days');
              const hourDiff = commentDate.diff(today, 'hours');
              const minutesDiff = commentDate.diff(today, 'minutes');
              if (dayDiff === 0 && hourDiff === 0) { // 작성한지 1시간도 안지났을때
                const minutes = Math.ceil(-minutesDiff);
                return minutes + '분 전';    // '분' 로 표시
              }
              else if (dayDiff === 0 && hourDiff <= 24) { // 작성한지 1시간은 넘었지만 하루는 안지났을때, 
                const hour = Math.ceil(-hourDiff);
                return hour + '시간 전';    // '시간'으로 표시
              }
              else if (-dayDiff >= 30) {
                const formattedDate = moment(date).format('YYYY년 MM월 DD일');
                return formattedDate;
              }
              else {
                return dayDiff + '일 전';    // '일'로 표시
              }
            };
            const handleHeart = () => {
              if (heart) {
                setHeart(false);
                setHeartCounts(hearCounts - 1);
              } else {
                setHeart(true);
                setHeartCounts(hearCounts + 1)
              }
            }
            const handleOpenModal = () => {
              setSelectedFeed(feed);
              setDashboardIsOpen(true);
            };
    
    
            return (
              <PostContainer ref={postContainerRef} key={feed.feedId}>
                <PostImage>
                  <PostUserInfo>
                    <FeedLoginProfile></FeedLoginProfile>
                    <PostUserName>{feed.feedLoginId}</PostUserName>
                  </PostUserInfo>
                  <StyledSlider {...settings}>
                    {feed.contentsList[0].contentsUrl.map((url, i) => (
                      <SliderImg key={i} src={url} alt={`postImage ${i}`} />
                    ))}
                  </StyledSlider>
                </PostImage>
                <div className="PostActions">
                  <PostIcons>
                    <Likes>
                      <HeartBtn onClick={handleHeart}>
                        <HeartIcon stroke="black" 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>
                      {
                        commentsMore ?
                          sortedComments.map((comment) => (
                            <CommentBox>
                              <CommentProfile></CommentProfile>
                              <div>
                                <CommentId>{comment.loginId}</CommentId><CommentContent>{comment.commentText}</CommentContent>
                                <p>{getDayMinuteCounter(new Date(comment.createdAt))}</p>
                              </div>
                            </CommentBox>
                          ))
                          : 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*/}
                      {!commentsMore && feed.feedCommentCount > 2 && <CommentsMore onClick={() => { setCommentsMore(true) }}>댓글 {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={true} onRequestClose={() => { setDashboardIsOpen(false) }}
                        contentLabel="게시물 상세 모달"
                        style={ModalStyle}
                      >
                        {/* {selectedFeed && (
                          <Dashboard selectedFeed={selectedFeed}></Dashboard>
                        )} */}
                      </Modal>)}
                  </CommentWrapper>
                </div>
              </PostContainer>
            );
          })}
        </>
      );
    }
    export default Post;
    
    코드는 이런식으로 작성했습니다.
    1. 우선 feeds 정보를 recoil로 관리했습니다.
    
    import { atom, selector } from 'recoil';
    import { FeedType } from '../pages/home/post';
    export const feedsState = atom<FeedType[]>({
        key: 'feedsState',
        default: [
            
        ]
    });
    2. fetchFeedsData가 서버에서 피드 정보를 요청하는 함수입니다.
     useEffect(() => {
        fetchFeedsData().then((data) => {
          setfeeds(data);
        })
        sortedFeeds.forEach((feed: FeedType) => {
          fetchAndSetComments(feed.feedId);
        });
      }, []); 
    useEffect 안에서 해당 함수를 호출하여 setfeeds(data);로 feeds state를 새롭게 업데이트하면서 아래의 에러가 발생했습니다.
    ERROR
    Rendered more hooks than during the previous render. 
    at updateWorkInProgressHook (http://localhost:3000/static/js/bundle.js:28449:19) at updateReducer (http://localhost:3000/static/js/bundle.js:28498:18) at updateState (http://localhost:3000/static/js/bundle.js:28867:14) at Object.useState (http://localhost:3000/static/js/bundle.js:29568:20) at useState (http://localhost:3000/static/js/bundle.js:45527:25) at http://localhost:3000/static/js/src_pages_home_index_tsx.chunk.js:2760:86 at Array.map (<anonymous>) at Post (http://localhost:3000/static/js/src_pages_home_index_tsx.chunk.js:2758:30) at renderWithHooks (http://localhost:3000/static/js/bundle.js:28303:22) at updateFunctionComponent (http://localhost:3000/static/js/bundle.js:30405:24) 
    
    해당에러를 찾아보니 반복문, 조건문 혹은 중첩된 함수 내에서 hook을 호출하지 말라고 돼있는데, 
    어느 부분에서 해결해야하는지 도저히 모르겠습니다..ㅠㅠ
    스크린샷 2023-09-17 163425
    서버요청으로 데이터가 잘받아와지는 것까지는 확인했는데, setfeeds(data);만 설정하면 같은에러가 뜹니다.
    어떤식으로 코드를 수정하면 좋은지 혹은 새롭게 작성해야한다면 어떻게해야하는지 알려주시면 감사하겠습니다!!
    #98258

    codingapple
    키 마스터
    반복문이랑 html 안에서 state 만들면 안됩니다 바깥에만듭시다
2 글 보임 - 1 에서 2 까지 (총 2 중에서)
  • 답변은 로그인 후 가능합니다.

About

현재 월 700명 신규수강중입니다.

  (09:00~20:00) 빠른 상담은 카톡 플러스친구 코딩애플 (링크)
  admin@codingapple.com
  이용약관, 개인정보처리방침
ⓒ Codingapple, 강의 예제, 영상 복제 금지
top

© Codingapple, All rights reserved. 슈퍼로켓 에듀케이션 / 서울특별시 강동구 고덕로 19길 30 / 사업자등록번호 : 212-26-14752 온라인 교육학원업 / 통신판매업신고번호 : 제 2017-서울강동-0002 호 / 개인정보관리자 : 박종흠