-
Notifications
You must be signed in to change notification settings - Fork 0
코드리뷰 용 PR #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: empty
Are you sure you want to change the base?
코드리뷰 용 PR #2
Changes from all commits
f4171e3
bc94f96
4dba641
7b236b1
92ccc44
bde244a
60b961f
34842b0
1a4ca2f
1b9767b
dfd8310
2642ecd
d128515
7f4964b
8f56101
29826e4
646e07d
499be93
273003f
04f854e
b8b741d
275e832
a602313
9badc68
691b826
a00e1ac
85de9e4
e83a81d
1783a75
8f80ffb
ca5bfac
1055837
7baba10
80533ee
cc83351
a43400f
56d5d66
224a147
8993136
19a436d
01fc2d8
3b44247
1028f71
a8f3e1c
b0bebc2
29753d0
0783d34
d6e76c7
9877220
9f2f8c5
5cad9fd
a06d3ac
b1b5a35
1f38612
cf65e20
8a014af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Logs | ||
| logs | ||
| *.log | ||
| npm-debug.log* | ||
|
|
||
| # Runtime data | ||
| pids | ||
| *.pid | ||
| *.seed | ||
|
|
||
| # Directory for instrumented libs generated by jscoverage/JSCover | ||
| lib-cov | ||
|
|
||
| # Coverage directory used by tools like istanbul | ||
| coverage | ||
|
|
||
| # nyc test coverage | ||
| .nyc_output | ||
|
|
||
| # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
| .grunt | ||
|
|
||
| # node-waf configuration | ||
| .lock-wscript | ||
|
|
||
| # Compiled binary addons (http://nodejs.org/api/addons.html) | ||
| build/Release | ||
|
|
||
| # Dependency directories | ||
| node_modules | ||
| jspm_packages | ||
|
|
||
| # Optional npm cache directory | ||
| .npm | ||
|
|
||
| # Optional REPL history | ||
| .node_repl_history | ||
| .next | ||
|
|
||
| # Optional add | ||
| .DS_Store | ||
|
|
||
| .env.local | ||
|
|
||
| .vscode |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). | ||
|
|
||
| ## Getting Started | ||
|
|
||
| First, run the development server: | ||
|
|
||
| ```bash | ||
| npm run dev | ||
| # or | ||
| yarn dev | ||
| # or | ||
| pnpm dev | ||
| # or | ||
| bun dev | ||
| ``` | ||
|
|
||
| Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. | ||
|
|
||
| You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. | ||
|
|
||
| This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. | ||
|
|
||
| ## Learn More | ||
|
|
||
| To learn more about Next.js, take a look at the following resources: | ||
|
|
||
| - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. | ||
| - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. | ||
|
|
||
| You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! | ||
|
|
||
| ## Deploy on Vercel | ||
|
|
||
| The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | ||
|
|
||
| Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,25 @@ | ||||||||
| "use client"; | ||||||||
|
|
||||||||
| import Loading from "@/common/Loading"; | ||||||||
| import FollowList from "@/component/FollowList"; | ||||||||
| import UserInfo from "@/component/UserInfo"; | ||||||||
| import useGetFollowInfo from "@/libs/hook/useGetFollowInfo"; | ||||||||
| import useGetUserInfo from "@/libs/hook/useGetUserInfo"; | ||||||||
| import React from "react"; | ||||||||
|
|
||||||||
| const page = () => { | ||||||||
| const { isLoading: userInfoLoading, data: userInfoData } = useGetUserInfo(); | ||||||||
| const { isLoading: followInfoLoading, data: followInfoData } = | ||||||||
| useGetFollowInfo(); | ||||||||
|
|
||||||||
| return userInfoLoading || followInfoLoading ? ( | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isLoading이라는 것을 판단하는 변수를 만들어서 써주면 더 직관적일것 같아요!
Suggested change
|
||||||||
| <Loading /> | ||||||||
| ) : ( | ||||||||
| <React.Fragment> | ||||||||
| {userInfoData && <UserInfo userInfoData={userInfoData} />} | ||||||||
| {followInfoData && <FollowList followInfoData={followInfoData} />} | ||||||||
| </React.Fragment> | ||||||||
| ); | ||||||||
| }; | ||||||||
|
|
||||||||
| export default page; | ||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,26 @@ | ||||||||||||||
| import CommonLayout from "@/common/CommonLayout"; | ||||||||||||||
| import type { Metadata } from "next"; | ||||||||||||||
| import { Inter } from "next/font/google"; | ||||||||||||||
| import React from "react"; | ||||||||||||||
| import "../style/global.css"; | ||||||||||||||
|
|
||||||||||||||
| const inter = Inter({ subsets: ["latin"] }); | ||||||||||||||
|
|
||||||||||||||
| export const metadata: Metadata = { | ||||||||||||||
| title: "Follow Detector", | ||||||||||||||
| description: "Following by Follow Detector App", | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| export default function RootLayout({ | ||||||||||||||
| children, | ||||||||||||||
| }: Readonly<{ | ||||||||||||||
| children: React.ReactNode; | ||||||||||||||
| }>) { | ||||||||||||||
| return ( | ||||||||||||||
| <html lang="ko"> | ||||||||||||||
| <CommonLayout> | ||||||||||||||
| <body className={inter.className}>{children}</body> | ||||||||||||||
| </CommonLayout> | ||||||||||||||
|
Comment on lines
+21
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CommonLayout이 body 태그로 이루어져 있는데, 하위에 body 태그가 또 있어서 중복 코드 같습니다!
Suggested change
|
||||||||||||||
| </html> | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Home 부분 (url : / )은 유저 액션이 들어가는 부분이
유저 액션이 들어가는 위 두 부분 외에는 단순 ui이기 때문에 next.js의 작동 원리를 생각해 봤을 때, 서버 컴포넌트를 사용하면 더 빠르게 유저가 초기 화면을 볼 수 있다는 장점이 있다고 생각합니다!
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞네요! next.js의 작동 원리에 대해 더 고민해보도록 하겠습니다 ! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| "use client"; | ||
|
|
||
| import Link from "next/link"; | ||
| import { ChangeEvent, useState } from "react"; | ||
| import * as styles from "../style/Home/Home.css"; | ||
|
|
||
| export interface UserTypes { | ||
| login?: string; | ||
| bio?: string; | ||
| avatar_url?: string; | ||
|
Comment on lines
+8
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요기 type들을 다 nullable로 정의한 이유가 있나유?!
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 타입 수정하는 과정에서 제대로 반영이 안된 것 같아요! 리팩토링하면서 함께 수정할 예정입니다 ! |
||
| } | ||
|
|
||
| export default function Home() { | ||
| const [token, setToken] = useState(""); | ||
|
|
||
| const handleChangeInput = (e: ChangeEvent<HTMLInputElement>) => { | ||
| setToken(e.target.value); | ||
| }; | ||
|
|
||
| const handleClickBtn = () => { | ||
| sessionStorage.setItem("token", token); | ||
| }; | ||
|
|
||
| return ( | ||
| <main className={styles.HomeWrapper}> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요기는 vanilla extract의 css 정의 네이밍이 PascalCase로 되어 있네유 |
||
| <section className={styles.HomeContents}> | ||
| <div className={styles.TokenLinkBox}> | ||
| <Link | ||
| href={"https://github.com/settings/tokens"} | ||
| className={styles.TokenLinkBtn} | ||
| > | ||
| Github Token 만들러 가기 | ||
| </Link> | ||
| <p className={styles.TokenInfoText}> | ||
| ﹒ 토큰 발급 시 권한 user(Update ALL user data)를 체크해주세요! | ||
| </p> | ||
| </div> | ||
|
|
||
| <article className={styles.TokenInputBox}> | ||
| <input | ||
| type="text" | ||
| placeholder="Github Token을 입력해주세요" | ||
| onChange={handleChangeInput} | ||
| className={styles.TokenInput} | ||
| /> | ||
|
|
||
| {/* 추후 수정 방향 */} | ||
| {/* <Link | ||
| href={{ pathname: "/follow-list", query: { token: token } }} | ||
| as={"/follow-list"} | ||
| > */} | ||
| <Link href={"/follow-list"}> | ||
| <button | ||
| type="button" | ||
| onClick={handleClickBtn} | ||
| className={styles.TokenInputNextBtn} | ||
| disabled={token.length === 0} | ||
| > | ||
| 나의 맞팔 확인하기 | ||
| </button> | ||
| </Link> | ||
| </article> | ||
| </section> | ||
| </main> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| "use client"; | ||
|
|
||
| import { Inter } from "next/font/google"; | ||
| import React from "react"; | ||
| import { QueryClient, QueryClientProvider } from "react-query"; | ||
| import Header from "./Header"; | ||
| const inter = Inter({ subsets: ["latin"] }); | ||
|
|
||
| const queryClient = new QueryClient(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nextjs에서 root layout 계층에 선언하는 queryClient는, 성능적인 측면과 참조 동일성 유지(라이프사이클에서 한 번만 초기화 될 수 있도록)를 위해 참고한 자료 링크도 함께 남겨요~
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오!! 참고해볼게요 감사합니다 ! |
||
|
|
||
| const CommonLayout = ({ children }: { children: React.ReactNode }) => { | ||
| return ( | ||
| <body className={inter.className}> | ||
| <QueryClientProvider client={queryClient}> | ||
| <Header /> | ||
| {children} | ||
| </QueryClientProvider> | ||
| </body> | ||
| ); | ||
| }; | ||
|
|
||
| export default CommonLayout; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import img_github_logo_white from "@/public/image/img_github_logo_white.png"; | ||
| import Image from "next/image"; | ||
| import * as styles from "../style/Common/Header.css"; | ||
|
|
||
| const Header = () => { | ||
| return ( | ||
| <header className={styles.HeaderWrapper}> | ||
| <Image | ||
| src={img_github_logo_white} | ||
| alt="깃허브-로고" | ||
| width={30} | ||
| height={30} | ||
| /> | ||
| <h1 className={styles.HeaderTitle}>깃허브 팔로우 탐지기</h1> | ||
| </header> | ||
| ); | ||
| }; | ||
|
|
||
| export default Header; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import useDeleteFollower from "@/libs/hook/useDeleteFollower"; | ||
| import usePutFollower from "@/libs/hook/usePutFollower"; | ||
| import { ListLayoutTypes } from "@/type/user"; | ||
| import Image from "next/image"; | ||
| import * as styles from "../style/Common/ListLayout.css"; | ||
|
|
||
| const ListLayout = ({ list, isUserInfo, isFollowingBtn }: ListLayoutTypes) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 흐음 요건 개인적인 의견인데... |
||
| const putMuation = usePutFollower(); | ||
| const deleteMutation = useDeleteFollower(); | ||
|
|
||
| const handleClickFollowBtn = ({ | ||
| isFollowingBtn, | ||
| login, | ||
| }: { | ||
| isFollowingBtn?: boolean; | ||
| login: string; | ||
| }) => { | ||
| isFollowingBtn ? putMuation.mutate(login) : deleteMutation.mutate(login); | ||
| }; | ||
|
|
||
| return list.map(({ login, avatar_url }) => { | ||
| return ( | ||
| <ul key={login} className={styles.contentsWrapper}> | ||
| {avatar_url && ( | ||
| <Image | ||
| width={130} | ||
| height={130} | ||
| src={avatar_url} | ||
| alt={"유저-이미지"} | ||
| priority={true} | ||
| style={{ marginTop: "1rem" }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기만 스타일링에 style 속성을 사용한 이유가 있을까용 ?!
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헙 이 부분도 추후에 수정하려 했는데, 놓치고 넘어갔네요.. ㅜ_ㅜ 리팩토링하면서 같이 수정할게요 ! |
||
| /> | ||
| )} | ||
| <div className={styles.followWrapper}> | ||
| <p className={styles.loginInfo}>{login}</p> | ||
|
|
||
| {!isUserInfo && ( | ||
| <button | ||
| type="button" | ||
| className={isFollowingBtn ? styles.followBtn : styles.unfollowBtn} | ||
| onClick={() => | ||
| login && handleClickFollowBtn({ isFollowingBtn, login }) | ||
| } | ||
| > | ||
| {isFollowingBtn ? "팔로우" : "언팔로우"} | ||
| </button> | ||
| )} | ||
| </div> | ||
| </ul> | ||
| ); | ||
| }); | ||
| }; | ||
|
|
||
| export default ListLayout; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| "use client"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요기 혹시 클라이언트 컴포넌트로 선언하지 않으면 오류 나나요 ?! 아니라면 서버 컴포넌트로 사용해도 좋을것 같습니당! |
||
|
|
||
| import Lottie from "lottie-react"; | ||
| import animationData from "../public/lottie/lottie.json"; | ||
| import * as styles from "../style/Common/Loading.css"; | ||
|
|
||
| const Loading = () => { | ||
| return ( | ||
| <section className={styles.LoadingPageItemContainer}> | ||
| <article className={styles.LottieWrapper}> | ||
| <Lottie animationData={animationData} /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lottie 너무 잘쓴당😎 |
||
| </article> | ||
| </section> | ||
| ); | ||
| }; | ||
|
|
||
| export default Loading; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||||||||||||||||||||||||||||||||
| import ListLayout from "@/common/ListLayout"; | ||||||||||||||||||||||||||||||||||||||||
| import { FollowInfoDataTypes } from "@/type/user"; | ||||||||||||||||||||||||||||||||||||||||
| import * as styles from "../style/FollowList/FollowList.css"; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const FollowList = ({ | ||||||||||||||||||||||||||||||||||||||||
| followInfoData, | ||||||||||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||||||||||
| followInfoData: FollowInfoDataTypes; | ||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||
| const { followingData, followersData } = followInfoData; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const matchedList = followersData.filter((follower) => { | ||||||||||||||||||||||||||||||||||||||||
| return followingData.some( | ||||||||||||||||||||||||||||||||||||||||
| (following) => following.login === follower.login | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const unfollowingList = followersData.filter((follower) => { | ||||||||||||||||||||||||||||||||||||||||
| return !matchedList.includes(follower); | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이전 멘토 리뷰에서 남겨주셨듯이 useMemo로 메모이제이션 해주면 좋을것 같아요! |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const user = [matchedList, unfollowingList]; | ||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. user를 위와 같은 array 형식으로 사용할거라면,
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||
| <section className={styles.followListWrapper}> | ||||||||||||||||||||||||||||||||||||||||
| {user.map((list, idx) => { | ||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||
| <article key={idx} className={styles.listWrapper}> | ||||||||||||||||||||||||||||||||||||||||
| <div className={styles.titleWrapper}> | ||||||||||||||||||||||||||||||||||||||||
| <p className={styles.title}> | ||||||||||||||||||||||||||||||||||||||||
| {list === matchedList | ||||||||||||||||||||||||||||||||||||||||
| ? "맞팔 중인 사용자" | ||||||||||||||||||||||||||||||||||||||||
| : "내가 팔로우 안 한 사용자"} | ||||||||||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||||||||||
| <p className={styles.title}>{`${list.length} 명`}</p> | ||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| <ListLayout list={list} isUserInfo={false} isFollowingBtn={list !== matchedList} /> | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. list === matchedList 로직을 여러 곳에서 사용하고 있는것 같아요!
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
| </article> | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||
| </section> | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| export default FollowList; | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import ListLayout from "@/common/ListLayout"; | ||
| import { UserProfileInfoTypes } from "@/type/user"; | ||
| import * as styles from "../style/User/User.css"; | ||
|
|
||
| const UserInfo = ({ userInfoData }: { userInfoData: UserProfileInfoTypes }) => { | ||
| return ( | ||
| <section className={styles.userInfoWrapper}> | ||
| <ListLayout list={[userInfoData]} isUserInfo={true} /> | ||
| </section> | ||
| ); | ||
| }; | ||
|
|
||
| export default UserInfo; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { client } from "."; | ||
|
|
||
| const deleteFollower = async (login: string) => { | ||
| const { data } = await client().delete(`/user/following/${login}`); | ||
| return data; | ||
| }; | ||
|
|
||
| export default deleteFollower; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 여기서 두 useQuery문을 병렬적으로 실행하기 위해 useQueires문을 사용했는데요,
데이터 return 되는 타입이 복잡해진다는 단점이 있지만 loading, data 값을 한 번에 관리할 수 있고(tanstack query v5의 combine 문법 사용) 지금처럼 한 뷰에 보여져야 하는 데이터들을 병렬적으로 호출할 수 있다는 장점이 있는것 같습니다!
useQuries 공식 문서 링크 보고 한 번 비교해봐도 좋을것 가타요~
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안 그래도 연서님 코드 리뷰하면서 useQueries에 대해 더 깊게 알아보고 또 직접 코드에 적용해봐야겠다고 생각했습니다!
useQuery를 사용하면서 쿼리 수가 증가함에 따라 loading과 data 값의 수 역시 함께 증가해서 관리가 복잡하다고 느꼈는데요..!
이런 상황에 사용하는게 useQueries인 것 같네요!! 참고하겠습니당 :)