airbnb clone coding

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

busybee-busylife 2024. 11. 13. 19:06
728x90

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>;
}
728x90