💻 Frontend/React

React Query에서 데이터 길이 확인하기

hjinn0813 2024. 11. 3. 09:45
728x90

지난 시간에는 로컬에 있던 프로배구팀 데이터를 가상환경의 DB 계정으로 이관하는 작업을 끝냈다. 이후에 React로 API 불러와서 화면에 보여주는 작업을 하면서, 선수단 전체 목록 페이지에서 특정 선수의 이름을 클릭하면 목록에서 ID값이 추출되며 디테일 페이지로 이동하게 했다. 디테일 페이지에서는 그 선수에 대한 정보만 보여지고, ID값을 통해 이전/이후 버튼을 클릭하면 해당 번호를 가진 선수의 정보가 바로 보여질 수 있도록 했다. 예를 들어 2번 선수를 클릭했을 경우, 이전/이후 버튼을 통해 1번과 3번 선수의 정보를 바로 확인할 수 있는 방식이다.

선수단은 인원에 한계가 있기 때문에, 첫번째 팀 데이터로 작업할 때는 이전/이후 버튼의 동작을 위해 dataLength 변수에 정의를 내리면서 숫자를 사용했었다. 그런데 만약에 dataLength가 얼마인지 모르는 경우라면 어떻게 작성해야 하는지 궁금해져서, 두번째 팀 테이터로 작업하면서는 코드를 변경해봤다.

※ 로컬에서 데이터 이관하기 - https://hjinn0813.tistory.com/147

 

MySQL 권한 에러, DB 이관하기

로컬에서 가상환경으로 데이터를 옮겨서 MySQL 권한 에러를 해결한 이야기

hjinn0813.tistory.com


컴포넌트 초기 설정

import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import '../../style/Spiders.scss';

const getSpidersById = async (id) => {
  const URL = `http://127.0.0.1:8001/spiders/${id}`;
  const resp = await axios.get(URL);
  return resp.data;
};

// data.length 확인을 위해 전체 목록 불러오기
const getAllSpiders = async () => {
  const URL = 'http://127.0.0.1:8001/spiders';
  const resp = await axios.get(URL);
  return resp.data;
};
  • 일단 기능들을 제대로 사용하기 위해 import를 하면서 작성을 시작한다.
  • getSpidersById()는 ID값을 통해 특정 선수의 정보를 추출해서 보여주기 위한 함수이다.
  • getAllSpiders()는 이전/이후 버튼의 작동을 위해 전체 목록을 불러와서 데이터 길이를 확인하려고 작성한 함수이다.

컴포넌트 작성

export default function SpidersDetail() {
  const navigate = useNavigate();
  const { id } = useParams();

  /* 모든 선수 목록 쿼리 */
  const { data: allPlayers, error: allPlayersError } = useQuery({
    queryKey: ['allSpiders'],
    queryFn: getAllSpiders,
    enabled: true,
  });

// 이하 생략
}
  • 이전/이후 버튼을 클릭하면 페이지가 이동되어야 하니까 useNavigate()를 정의했고,
    URL에서 ID값을 추출해서 특정 선수의 정보를 가져오려고 useParams()를 정의했다.
  • 모든 선수 목록을 서버에서 가져오는 useQuery는 data, error 변수의 이름을 변경시켰다.
    아래에서 특정 선수의 정보만 불러오는 useQuery와 변수의 이름이 같으면 에러가 발생할 수 있기 때문이다.
  • 여기에서 queryFn에 함수는 괄호 없이 작성해서 바로 실행되지 않도록 해야 한다. 처음에 특정 선수를 클릭했을 때에만 queryFn에서 해당 함수를 호출하여 전체 목록을 불러오면, 이전/이후 버튼을 클릭할 경우에는 현재 확인하고 있는 선수의 번호로 이전/다음 번호를 계산하면 되기 때문이다.
    만약에 queryFn에서 함수를 호출하면서 괄호가 있다면 해당 함수가 바로 실행되니까, 처음 특정 선수를 클릭했을 때에 한번만 가져오면 되는 전체 목록을 매번 가져오게 되고, 결국 이전/이후 버튼을 클릭할 때마다 전체 목록을 불러오게 되어 코드가 비효율적으로 작동하게 된다.
const { data, error } = useQuery({
    queryKey: ['spiders', id],
    queryFn: () => getSpidersById(id),
    enabled: true,
    refetchOnWindowFocus: false,
  });

  // 선수 목록이 로딩 중이거나 에러가 발생한 경우 처리
  if (allPlayersError)
    return <div>Error loading players: {allPlayersError.message}</div>;
  if (!allPlayers) return <div>Loading...</div>;

  /* 전체 목록을 통해 data.length 확인 */
  const dataLength = allPlayers.length;
  const currentId = parseInt(id);
  const previousId = currentId > 1 ? currentId - 1 : null;
  const nextId = currentId < dataLength ? currentId + 1 : null;
  • 여기서의 useQuery는 ID값을 통해 특정 선수의 정보를 불러오는 함수이다.
  • 선수 전체 목록에 대한 로딩/에러 처리를 if문으로 작성했다. 전체 목록을 서버에서 불러오다가 에러가 발생하면 에러 메시지를 화면에 보여주고, 목록을 불러오는 중이라서 데이터가 없으면 '로딩중..'이라는 메시지를 화면에 보여준다.
  • 전체 목록으로 데이터 길이를 확인할 수 있게 변수에 담고, 주소창에서 가져온 ID값은 기본적으로 문자열로 되어있으니까 정수로 변환하여 currentId 변수에 담았다. previousId, nextId는 삼항연산자로 조건에 맞는 값을 계산해 보여주도록 했다.

return문 작성

return (
    <div className="spiders-wrap">
      <div className="spiders-detail">
        {data ? (
          <>
            <h2>{data.name}</h2>
            <table>
              <tbody>
                <tr>
                  <th>등번호</th>
                  <td>{data.backNo}</td>
                </tr>
                <tr>
                  <th>신장</th>
                  <td>{data.height}cm</td>
                </tr>
                <tr>
                  <th>포지션</th>
                  <td>{data.position}</td>
                </tr>
                {data.info && (
                  <tr>
                    <th>특이사항</th>
                    <td>{data.info}</td>
                  </tr>
                )}
              </tbody>
            </table>
          </>
        ) : error ? (
          <div>Error: {error.message}</div>
        ) : (
          <div>Loading..</div> // 초기 로딩 상태
        )}
      </div>
      <div className="btn-area">
        {previousId && (
          <div
            className="spiders-prev"
            onClick={() => navigate(`/spiders/${previousId}`)}
          >이전 선수</div>
        )}
        <div className="spiders-all" onClick={() => navigate(`/spiders`)}>
          전체 목록
        </div>
        {nextId && (
          <div
            className="spiders-next"
            onClick={() => navigate(`/spiders/${nextId}`)}
          >다음 선수</div>
        )}
      </div>
    </div>
  );
  • 코드가 너무 길어서 간단하게 줄여봤다.
    선수의 정보가 실제로 보여지는 부분은 전체를 삼항연산자로 작성했다. 데이터가 있으면 선수의 정보를 하나씩 보여주고, 에러 발생하면 에러 메시지 띄우고, 둘 다 아니면 'Loading..'이라고 표시하여 초기 로딩 상태를 나타낸다.
  • 특이사항은 && 연산자를 사용하여 data.info에 값이 있을 때에만 화면에 표시되도록 했다. 이전/다음 선수 버튼도 마찬가지 원리로 작동한다. (null, undefined일 경우에는 화면에 표시되지 않음)

이번 팀의 디테일 페이지의 코드를 작성하면서, 전체 목록 쿼리에서 이해되지 않는 부분이 있어서 이 글을 작성하게 되었다.

하나씩 차근차근 뜯어보니까 이제 이해가 되었고, 효율적으로 작동하는 코드를 작성하는게 중요하다고 느꼈다.

프론트엔드도 쉽지 않은데 백엔드까지 하려니 힘들지만, 그만큼 내가 성장하고 있는거니까 힘을 내보자!

728x90