[Next.js] 검색 페이지에서 발생한 오류 해결

박민형·2023년 7월 31일
0
post-thumbnail

담당 페이지였던 도서 검색 페이지를 제작 후 다양한 상황에서 사용해보며 오류들과 사용자 입장에서 불편할 수 있는 것들을 찾는 시간을 가졌다. 어떤 문제가 발생하였고 어떻게 해결하였는지 포스팅 하려한다!

📌 해결해야 하는 문제

  • 다른 페이지 갔다 왔을 때 input에 입력한 검색어 유지안됨
  • 검색 후 다른 페이지 갔다가오면 검색 안됨
  • 무한 스크롤 리스트에서 어느 시점 부터 데이터를 render 하지 못하는 문제

📌 다른 페이지 갔다 왔을 때 input에 입력한 검색어 유지안됨

  • 사용자가 검색 후 다른 페이지에 갔다가 다시 왔을 때 검색창에 키워드가 유지되는 것이 UX 향상에 도움이 될 것이라 판단했다.

👉 검색어가 유지되지 않았던 이유

  • SearchInput 공통 컴포넌트는 사용자가 입력하는 검색어 데이터를 const [inputData, setInputData] = useState(''); 형식으로 관리
  • 다른 페이지 갔다가 다시 페이지가 re-render 되면 inputData state가 ' '로 초기화 됨으로써 검색어 유지 안 됨

👉 해결 방법

  • 상위 컴포넌트에서 SearchInput 공통 컴포넌트에 props를 통해 검색어 데이터(전역 state) 전달
  • 전역 state는 컴포넌트가 re-render 되어도 데이터가 유지되기 때문에 검색어 유지
  • 수정 전 코드
// SearchInput 공통 컴포넌트

const [searchBookTitle, setSearchBookTitle] = useRecoilState(searchBookTitleAtom);

<SearchInput onSubmit={onSubmit} />

// 도서 검색 페이지

const SearchInput = ({ onSubmit }: Props) => {
  const [inputData, setInputData] = useState('');
  ... 
}
  • 수정 후 코드
// SearchInput 공통 컴포넌트

const [searchBookTitle, setSearchBookTitle] = useRecoilState(searchBookTitleAtom);

<SearchInput onSubmit={onSubmit} inputText={searchBookTitle} />

// 도서 검색 페이지

const SearchInput = ({ onSubmit, inputText }: Props) => {
  const [inputData, setInputData] = useState(inputText);
  ... 
}

📌 검색 후 다른 페이지 갔다가오면 검색 안됨

  • 특정 키워드를 통해 검색하고 다른 페이지에 갔다 온 후 다른 키워드로 검색하면 검색이 안되는 문제가 있었다.

👉 문제 해결 과정

  • 검색이 안되는 이유는 TanStack Query의 option중 하나인 enabled 값이 false이기 때문이라고 생각을 했다.(enabled: !!searchKeyword && !bookSearchData.current)
    • searchKeyword : 검색어 state
    • bookSearchData.current : 현재 검색어에 해당하는 무한 스크롤 리스트 데이터
  • 새로운 키워드로 검색 하는 상황에서 콘솔에 찍어보니 bookSearchData.current 값이 존재했다. 해당 데이터가 존재하니까 query 활성화가 안되고 그에따라 검색이 안된다고 예상했다.
  • 그 예상에 따라 새롭게 검색 할 때 이전 쿼리값을 삭제하는 방법을 시도해보았다.(queryClient.removeQueries)
    • 그러나 여전히 검색이 되지 않았고 오히려 이전에 검색한 데이터가 캐싱되지 않으니 UX 저하 문제가 발생했다.

👉 새로운 키워드로 검색이 되지 않았던 이유

  • 위의 과정에서 문제 해결이 안되던 와중에 문득 useRef의 current 값은 컴포넌트가 mount 될 때 설정된 값을 re-render가 되어도 계속 유지 한다는 말이 생각났다.
  • 그로인해 검색어가 업데이트 되어도 bookSearchData가 이전 데이터를 가지고 있으므로 쿼리가 비 활성화 되어 검색이 안되었던 것이었다.

🔎 useRef

  • ref 객체를 생성하고 current 속성을 통해 값을 수정 및 조회할 수 있음
  • 컴포넌트가 re-render 되어도 컴포넌트가 마운트 될 때의 초기값을 유지(ref 객체는 컴포넌트의 라이프사이클 동안 일관된 값을 유지)
  • current 값이 수정되어도 컴포넌트가 re-render 되지 않는다.

👉 해결 방법

  • useRef를 제거하니 정상적으로 동작했다!
  • 수정 전 코드
// useRef 사용

const bookSearchData = useRef(
  queryClient.getQueryData([
    'book',
    'search',
    'result',
    'list',
    searchKeyword,
  ]),
);

const {
  data: bookSearchResultLists,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage,
  status,
} = useInfiniteQuery(
  ['book', 'search', 'result', 'list', searchKeyword],
  ({ pageParam = 1 }) => getBookSearchResultData(searchKeyword, pageParam),
  {
    onSuccess: () => setPage((prev) => prev + 1),
    onError: (error) => console.error(error),
    getNextPageParam: (lastPage) => {
      if (lastPage.is_end) return;

      return page;
    },
    enabled: !!searchKeyword && !bookSearchData.current,
  },
);
  • 수정 후 코드
// useRef 제거

// 데이터 유무만 check하면 되므로 boolean type으로 수정
const isBookSearchData = !!queryClient.getQueryData([
  'book',
  'search',
  'result',
  'list',
  searchKeyword,
]);

const {
  data: bookSearchResultLists,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage,
  status,
} = useInfiniteQuery(
    ...
    enabled: !!searchKeyword && !isBookSearchData,
  },
);

📌 무한 스크롤 리스트에서 어느 시점 부터 데이터를 render 하지 못하는 문제

  • 도서 검색 페이지에서 useInfinityQuery를 통해 스크롤 하면 리스트 데이터를 fetch를 해옴
  • 어느 시점 부터 데이터는 fetch 해오지만 render를 못하는 문제 발생

👉 수정 전 소스코드

{readingGroupLists.pages.map(
  ({ groups, currentPage }, index) =>
  groups.length > 0 &&
  groups[index] && (
    <RecruitList key={currentPage} listData={groups} />
  ),
)}

👉 데이터가 render 되지 않았던 이유

  • 조건문에 groups[index]를 설정한 것이 문제가 되었다.
  • 데이터 유무를 check 후 데이터를 전달하기 위한 의도는 좋았으나 useInfinityQuery를 통해 fetch한 데이터 형식은 조금 다르다는 부분을 간과하였다.
  • groups[index]를 예시를 통해 구체적으로 설명하려고 한다.

👉 groups[index]

  • 예를들어 현재 총 30개의 리스트 아이템이 있고 스크롤 내릴 때 마다 5개의 아이템을 render 한다고 가정(총 그룹 수: 6개, 30 / 5)
  • groups[index] : n번째 그룹데이터의 n번 index의 데이터가 존재하면 true
  • 첫번째 그룹 : 첫번째 그룹 => 1번 groups[1(그룹번호)] => 데이터가 있어서 render
  • 두번째 그룹 : 두번째 그룹 => 2번 groups[2] => 데이터가 있어서 render
  • 다섯번째 그룹 : 다섯번째 그룹 => 5번 groups[5]
    • 위의 예시를 기준으로 1개의 그룹에는 5개의 아이템만 있기 때문에 특정 그룹 index의 최대값은 4
    • 하지만 5번째 index를 조회함으로 인해 false가 반환되고 그로인해 다섯 번째 그룹에 해당하는 리스트가 render 안됨.
    • 그 뒤의 그룹도 마찬가지 이유로 render가 안됬다.
  • 처음에는 index가 각 그룹의 아이템 번호라고 생각했다.
  • 하지만 index는 그룹 번호이기 때문에 이런 문제가 발생

👉 수정 후 소스코드

  • index, groups[index] 제거 후 정상적으로 render 되었다!
{readingGroupLists.pages.map(
  ({ groups, currentPage }) =>
  groups.length > 0 && (
    <RecruitList key={currentPage} listData={groups} />
  ),
)}

📌 결론

특정 기능을 구현하는 것도 중요하지만 사용자 입장에서 유지/보수 하는 것이 더 중요하다는 것을 수정 작업을 하며 알아가는 것 같다. 틀린 부분이 있거나 부족한 부분이 있으면 피드백 부탁드립니다!

1개의 댓글

comment-user-thumbnail
2023년 7월 31일

개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.

답글 달기