devcisive / solumon-frontend

0 stars 3 forks source link

페이지네이션 적용 후 url 쿼리 값을 받아오지 못하는 오류 해결 #26

Closed chaeeunj closed 10 months ago

chaeeunj commented 11 months ago
// PostCategory.jsx
import { useState, useEffect } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import styled, { ThemeProvider } from 'styled-components';
import theme from '../style/theme';
import PropTypes from 'prop-types';
import { StyledEngineProvider } from '@mui/styled-engine';

import { CiSearch } from 'react-icons/ci';
import TabsComponent from '../components/TabsComponent';
import SortSelector from '../components/SortSelector';
import PostCard from '../components/PostCard';
import Pagination from '../components/Pagination';

function PostCategory() {
  const [postData, setPostData] = useState([]);
  // const [isLoading, setIsLoading] = useState(true);
  const [searchParams, setSearchParams] = useSearchParams();

  const postType = searchParams.get('postType');
  const postStatus = searchParams.get('postStatus');
  const postOrder = searchParams.get('postOrder');
  const currentPage = parseInt(searchParams.get('page')) || 1; // 현재 페이지 가져오기

  const postPerPage = 10; // 한 페이지당 렌더링할 게시글 수
  const indexOfLastPost = currentPage * postPerPage; // 한 페이지의 마지막 게시글의 인덱스
  const indexOfFirstPost = indexOfLastPost - postPerPage; // 한 페이지의 첫 번째 게시글의 인덱스
  const currentPosts = postData.slice(indexOfFirstPost, indexOfLastPost); // 해당 페이지에서 보여줄 데이터 나누기

  let categoryTitle = '';

  if (postType === 'general' || postType === 'interest') {
    if (postOrder === 'mostChatParticipants') {
      categoryTitle =
        (postType === 'interest' ? '[관심주제] ' : '') +
        '채팅 참여자가 많은 고민';
    } else if (postOrder === 'mostVotes') {
      categoryTitle =
        (postType === 'interest' ? '[관심주제] ' : '') +
        '투표 참여자가 많은 고민';
    } else if (postStatus === 'ongoing') {
      if (postOrder === 'latest') {
        categoryTitle =
          (postType === 'interest' ? '[관심주제] ' : '') +
          '아직 결정하지 못한 고민';
      } else if (postOrder === 'imminentDeadline') {
        categoryTitle =
          (postType === 'interest' ? '[관심주제] ' : '') +
          '결정 시간이 임박한 고민';
      }
    } else if (postStatus === 'completed' && postOrder === 'latest') {
      categoryTitle =
        (postType === 'interest' ? '[관심주제] ' : '') + '결정이 완료된 고민';
    }
  }

  useEffect(() => {
    // setIsLoading(true);
    fetchData();
  }, [postType, postStatus, postOrder, currentPage]);

  const onPageChange = (newPage) => {
    setSearchParams((prevSearchParams) => ({
      ...prevSearchParams,
      page: newPage,
    }));
  };

  const fetchData = () => {
    fetch(
      `https://jsonplaceholder.typicode.com/posts?postType=${postType}&postStatus=${postStatus}&postOrder=${postOrder}&page=${currentPage}`,
    )
      .then((response) => {
        return response.json();
      })
      .then((json) => {
        setPostData(json);
        // setIsLoading(false);
      })
      .catch((error) => {
        console.log(`Something Wrong: ${error}`);
        // setIsLoading(false);
      });
  };

  return (
    <ThemeProvider theme={theme}>
      <StyledEngineProvider injectFirst>
        <Wrapper>
          {/* {isLoading ? (
            <div>로딩 중입니다.</div>
          ) : (
            <> */}
          <PostSection>
            <TitleWrapper>
              <CategoryTitle>{categoryTitle}</CategoryTitle>
              <Link to={'/posts/search'}>
                <SearchIcon />
              </Link>
            </TitleWrapper>
            <SortWrapper>
              <TabsComponent
                tabLabels={['진행중인 고민', '결정이 완료된 고민']}
                defaultTab={0}
              />
              <SortSelector />
            </SortWrapper>
            <PostCard postData={currentPosts} />
          </PostSection>
          <Pagination
            totalPages={Math.ceil(postData.length / postPerPage)}
            currentPage={currentPage}
            onPageChange={onPageChange}
          />
          {/* </>
          )} */}
        </Wrapper>
      </StyledEngineProvider>
    </ThemeProvider>
  );
}

export default PostCategory;

const Wrapper = styled.div`
  display: flex;
  flex-direction: column;
`;

const PostSection = styled.div`
  display: flex;
  flex-direction: column;
  margin: 40px auto;
`;

const TitleWrapper = styled.div`
  display: flex;
  justify-content: space-between;
  margin-bottom: 20px;
`;

const CategoryTitle = styled.h1`
  font-size: 26px;
  font-weight: 600;
  color: ${({ theme }) => theme.dark_purple};
  margin-bottom: 10px;
`;

const SearchIcon = styled(CiSearch)`
  width: 28px;
  height: 28px;
  color: ${({ theme }) => theme.dark_purple};
`;

const SortWrapper = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
`;


지정된 카테고리에 해당하는 게시물을 전부 불러오는 PostCategory 코드에 페이지네이션을 적용하여 한 페이지 당 10개의 게시물을 보여주도록 했습니다.

첫 번째 페이지에서는 게시물이 잘 렌더링 되지만

image

두 번째 페이지 부터는 게시물과 타이틀이 렌더링되지 않고, 전체 페이지 수는 2 페이지 뿐인데 5번 페이지까지 렌더링되는 문제가 있습니다. image

게시물을 렌더링해주는 PostCard 컴포넌트에서 props로 받은 데이터를 출력하게 한 다음 콘솔창을 확인해보니 image 데이터는 또 잘 받아오고 있는 상황...🙄

추가로 url 에도 문제가 있음을 확인했습니다.

첫 페이지에서는 해당 url로 연결되어있는데 'localhost/posts?postType=general&postStatus=ongoing&postOrder=latest&page=1'

두 번째 페이지로 이동하면 url이 아래와 같이 변경되며 postType, postStatus, postOrder 의 쿼리 값을 받아오지 못하는 것을 확인했습니다. 'localhost/posts?page=2'


// 페이지네이션 컴포넌트에 전달하는 함수
const onPageChange = (newPage) => {
    setSearchParams((prevSearchParams) => ({
      ...prevSearchParams,
      page: newPage,
    }));
  };

  // data fetch 함수
   fetch(
      `https://jsonplaceholder.typicode.com/posts?postType=${postType}&postStatus=${postStatus}&postOrder=${postOrder}&page=${currentPage}`,
    )

페이지를 변경할 때 마다 이전의 쿼리 값을 가져오고 page 값만 갱신하여 데이터를 요청하도록 했는데, 두 번째 페이지의 url 에서 postType, postStatus, postOrder 의 쿼리 값을 받아오지 못하고 있기 때문에 스프레드 연산자를 사용하여 쿼리 값을 복사하지 않고 직접 명시하여 설정하도록 수정했습니다.

// 수정한 onPageChange 함수
 const onPageChange = (newPage) => {
    setSearchParams({
      postType: postType,
      postStatus: postStatus,
      postOrder: postOrder,
      page: newPage,
    });
  };


코드 수정 후 잘 동작하는 모습 image image

위와 같이 쿼리 값을 직접 명시하여 페이지네이션이 잘 동작하지 않는 문제를 해결했지만, 아직 정확한 원인이 무엇인지 제대로 이해하지 못했습니다😣

oinochoe commented 11 months ago

@chaeeunj qs라는 라이브러리도 있으니 query-string은 해당 라이브러리로 관리해보시는 것도 추천드립니다 : ) 우선 이전 query-string을 유지시키기 위한 searchParams가 정상동작을 하지 않은 것 같군요.

또, 코드 중에 2중 if문이 있는데, 이 부분은 현업에서 지양하는 방법입니다.

switch문이나 객체 매핑을 추천드립니다.

const generateCategoryTitle = ({ postType, postOrder, postStatus }) => {
  const prefix = postType === 'interest' ? '[관심주제] ' : '';

  const titles = {
    general: {
      mostChatParticipants: '채팅 참여자가 많은 고민',
      mostVotes: '투표 참여자가 많은 고민',
      ongoing: {
        latest: '아직 결정하지 못한 고민',
        imminentDeadline: '결정 시간이 임박한 고민',
      },
      completed: {
        latest: '결정이 완료된 고민',
      },
    },
  };

  let categoryTitle;
  if (postStatus) {
    categoryTitle = titles[postType][postStatus][postOrder];
  } else {
    categoryTitle = titles[postType][postOrder];
  }

  return prefix + (categoryTitle || '');
};

const categoryTitle = generateCategoryTitle({
  postType, postOrder, postStatus
});
chaeeunj commented 11 months ago

안그래도 조건문이 너무 복잡한 것 같아 줄이는 방법을 고민중이었는데 이렇게도 사용할 수 있네요...감사합니다🤗 qs 라이브러리도 서치해보겠습니다!!