본문 바로가기
airbnb clone coding

[React] 19. React Query: Fetch / TanStack Query / Axios / DevTools

by busybee-busylife 2024. 11. 13.
반응형

Django DB에서 데이터를 프론트엔드로 가져오기

 

fetch

JavaScript에서 제공하는 내장함수 

네트워크 요청을 통해 서버로부터 데이터를 가져오거나 서버에 데이터를 전송하는데 사용 

주로 API로부터 JSON 데이터나 HTML 리소스를 가져올 때 많이 사용 


django-cors-headers

: fetch할 수 있는 사람을 지정 

 

1) 터미널에서 설치: poetry add django-cors-headers

2) config > settings.py 의 THIRD_PARTY_APPS 에 'corsheaders' 추가 

3) config > settings.py 의  MIDDLEWARE 에 'corsheaders.middleware.CorsMiddleware' 추가 

4) config > settings.py 에 CORS_ALLOWED_ORIGINS = ["http://localhost:3000"] 추가 

5) Home.tsx와 Room.tsx를 수정

 

: 예전 방식. 더 좋은 방법이 있다 

 

 


TanStack Query(구 React Query) 

: 서버에서 데이터를 가져오고 캐싱, 동기화, 업데이트 상태를 관리하기 위한 도구(data fetching을 효율적으로 할 수 있도록) 

 - API로부터 fetch한 모든 데이터를 캐싱한다(현재 페이지를 떠났다가 다시 돌아와도 이전에 fetch한 데이터를 기억) 

 - REST API만 사용하는 경우 TanStack Query, SWR이 적합

 - GraphQL을 사용하면 Apollo Client나 Relay가 더 나은 선택일 수 있다

 

1) 터미널에서 설치: npm i @tanstack/react-query

2) index.tsx에서 import 

3) src > api.ts 파일 생성(fetching 코드를 하나의 파일로 모으기)

4) Home.tsx 파일에서 useQuery를 사용하여 코드 수정 

 

  • useQuery 훅 사용
    • useQuery 훅을 사용하여 getRooms 함수를 통해 비동기적으로 방 데이터를 가져옴
    • queryKey: "rooms"라는 키를 지정하여 React Query가 캐시에 저장
    • queryFn: getRooms 함수를 호출하여 서버에서 데이터를 가져옴
    • isLoading: 데이터가 로딩 중일 때 true / 완료되면 false
    • data: getRooms 함수에서 반환된 방 데이터가 저장
    • fetch한 데이터는 브라우저의 메모리에 남아있어서(캐시 만들어 보존) 다시 돌아오면 로딩 없이 바로 뜬다...!
  • Home 컴포넌트 구조
    • Grid: 방 목록을 격자 형태로 배치. 반응형 설정을 통해 화면 크기에 따라 컬럼의 개수를 조정
    • isLoading 조건: isLoading이 true일 때 RoomSkeleton을 보여주어 데이터가 로딩 중임을 나타냄
    • 데이터 렌더링: data 배열의 각 room 객체를 Room 컴포넌트로 렌더링
      • imageURL, name, rating, city, country, price 등 Room 컴포넌트에 필요한 데이터를 전달
      • 옵셔널 체이닝(?.)을 사용하여 첫 번째 사진이 존재하지 않을 경우에도 오류가 발생하지 않도록 함
  • 렌더링 결과:
    • 로딩 중일 때는 RoomSkeleton으로 스켈레톤 UI가 나타나고, 로딩이 완료되면 Room 컴포넌트 목록이 화면에 표시

 

 


Axios

: fetch를 하는 일종의 adapter 

 

1) 터미널에서 설치: npm i axios 

2) api.ts 파일 수정: 훨씬 간결해졌다! 


Room Detail

: 이미지를 클릭하면 세부정보를 표시하는 url로 이동 

 

1) Room.tsx 에서 세부정보 페이지 링크 만들기: pk 설정  

2) src > components > routes 에서 RoomDetail.tsx 파일 생성 

3) src > router.tsx 에서 new childeren 추가 

4) src > api.ts 에서 fetch 함수 만들기 


Devtools and Query Keys

React DevTools

: React 컴포넌트 구조와 상태관리, 렌더링을 시각적으로 확인하여 디버그에 도움(Chrome, Firefox 확장 프로그램) 

 

1) 터미널에서 설치: npm i @tanstack/react-query-devtools

2) src > components > Root.tsx 에서 import 

3) src > components > routes > Roomdetail.tsx 코드 수정 


Photos Grid

: RoomDetail 페이지 만들기 

 

1) src > types.d.ts 파일 생성하여 데이터 타입 지정 & RoomDetail.tsx에서 <IRoomDetail> 추가 

2) src > components > routes > RoomDetail.tsx 파일 


Reviews 

src > api.ts에서 getRoomReviews 함수 추가 

src > components > routes > RoomDetail.tsx 에서도 useQuery로 추가 

 

Avatar Component

 - 사용자가 업로드한 프로필 이미지를 src 속성에 전달하여 표시 

 - 사용자 업로드 이미지가 없거나, 이미지에 문제가 있으면 사용자 이름으로 아바타를 만들 수 있다 

 - 뱃지 추가(색깔 등) 등 다양한 기능 

import { useQuery } from "@tanstack/react-query";
import { useParams } from "react-router-dom";
import { getRoom, getRoomReviews } from "../../api";
import { IRoomDetail, IReview } from "../../types";
import { Box, Heading, Image, VStack, Text, HStack, Button, Skeleton, SkeletonText, Grid, GridItem, Avatar, AvatarBadge } from "@chakra-ui/react";
import { FaStar } from "react-icons/fa"

export default function RoomDetail() {
    const { roomPk } = useParams();
    // const { isLoading, data} = useQuery([`room:${roomPk}`], getRoom)
    // // const { isLoading, data } = useQuery({queryKey: [`room:${roomPk}`], queryFn: () => getRoom,});
    // const { isLoading, data } = useQuery({queryKey: [`rooms`, roomPk], queryFn: () => getRoom(roomPk)});
    const { isLoading, data } = useQuery<IRoomDetail>({queryKey: ['rooms', roomPk], queryFn: getRoom  // getRoom 함수를 직접 전달
    });

    const {isLoading:isReviewsLoading, data:reviewsData} = useQuery<IReview[]>({queryKey: ['rooms', roomPk, 'reviews'], queryFn: getRoomReviews});


    return <Box mt={10} px={{base:10, lg:40}} >
        <Skeleton height={"39.89px"} width={"25%"} isLoaded={!isLoading}>
            <Heading>{data?.name}</Heading> 
        </Skeleton>

        <Grid mt={8} rounded="xl" overflow={"hidden"} gap={"2"} height={"60vh"} templateRows={"1fr 1fr"} templateColumns={"repeat(4, 1fr)"}>
            {[0,1,2,3,4].map((index) => (<GridItem colSpan={index===0? 2: 1} rowSpan={index===0? 2: 1} overflow={"hidden"} key={index}>
                <Skeleton isLoaded={!isLoading} h="100%" w="100%">
                    <Image objectFit={"cover"} w="100%" h="100%" src={data?.photos[index].file} />
                </Skeleton>
            </GridItem>))}  
        </Grid>   
        
        <HStack width={"100%"} justifyContent={"space-between"} mt={10}>
            <VStack alignItems={"flex-start"}>

                <Skeleton isLoaded={!isLoading} height={"30px"}>
                    <Heading fontSize={"2xl"}>House hosted by {data?.owner.username}</Heading>
                </Skeleton>

                <Skeleton isLoaded={!isLoading} height={"30px"}>
                    <HStack justifyContent={"flex-start"} w="100%">
                        <Text>{data?.toilets} toilet{data?.toilets===1? "":"s"}</Text>
                        <Text>/</Text>
                        <Text>{data?.rooms} room{data?.toilets===1? "":"s"}</Text>
                    </HStack>
                </Skeleton>

            </VStack>
            <Avatar name={data?.owner.username} size={"lg"} src={data?.owner.avatar}>  {/* 이미지가 없는 경우 이름을 사용 */}
                <AvatarBadge boxSize="1.25em" bg='red.500' />
            </Avatar>  
        </HStack>

        <Box mt={10}>
            <Heading fontSize={"2xl"}>
                <HStack>
                    <FaStar /> <Text>{data?.rating} / </Text>
                    <Text>{reviewsData?.length} review{reviewsData?.length===1? "":"s"}</Text>
                </HStack>
            </Heading>
        </Box>

    </Box>;
}
반응형