묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Next + React Query로 SNS 서비스 만들기
혹시 벡엔드 서버를 따로안두고 프론트와 벡엔드 둘다 하나의 서버에 둔다면 배포방법이 달라지나요?
지금 제로초님은 벡엔드와 프론트서버를 구분해두셨고 백엔드서버는 ec2에 올리지않아서 백엔드와 관련된 것들은 작동하지 않는 상태인데, 만약에 fetch할때 백엔드서버주소가 아닌 프론트쪽 경로로 해서 하면 본 강좌에서 한것과 같이 ec2로 배포를 해도 작동을 할까요? 아니면 이경우 배포방법이 달라지나요?
-
미해결Next + React Query로 SNS 서비스 만들기
게시물 팔로우 중 탭에서 게시물 업로드 오류 발생
게시물 추천 탭에서는 게시하기 버튼 클릭하면 게시물 업로드가 잘 작동됩니다. 그러나 팔로우 중 탭에서 게시하기 버튼을 클릭하면 에러가 발생합니다.게시물 게시하기 코드는 아래와 같습니다. const mutation = useMutation({ mutationFn: async (e: React.FormEvent) =>{ e.preventDefault(); const formData = new FormData(); formData.append("content", content); imgPreview.forEach((img) => { img && formData.append("images", img.file); }); return fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/posts`, { method: "post", credentials: "include", body: formData, }); }, onSuccess: async (response) => { const newPost = await response.json(); setContent(""); setImgPreview([]); if (queryClient.getQueryData(["posts", "recommends"])) { queryClient.setQueryData( ["posts", "recommends"], (prev: { pages: Post[][] }) => { const shallow = { ...prev, pages: [...prev.pages] }; shallow.pages[0] = [...shallow.pages[0]]; shallow.pages[0].unshift(newPost); return shallow; } ); } if(queryClient.getQueryData(["posts", "followings"])) { console.log("get", queryClient.getQueryData(["posts", "followings"])); queryClient.setQueryData( ["posts", "followings"], (prev: { pages: Post[][] }) => { const shallow = { ...prev, pages: [...prev.pages] }; shallow.pages[0] = [...shallow.pages[0]]; shallow.pages[0].unshift(newPost); return shallow; } ); } }, onError: (error) => { console.error(error); alert("업로드 중 에러가 발생했습니다"); }, });최근 새소식을 보고 useSuspenseQuery 가 문제가 있다는 것을 보고 Suspense 없애고 기존에 사용한 TabDecider 컴포넌트로 변경했습니다.// src\app\(afterLogin)\home\page.tsx export default async function Home() { const session = await auth(); return ( <main className={styles.main}> <TabContextProvider> <Tab /> <PostForm me={session} /> <TabDecider /> </TabContextProvider> </main> ); } 네트워크 탭에서 posts 요청은 성공했다고 나와있습니다.새로고침하면 업로드가 제대로 되지만 게시하기 버튼 클릭하면 onError에서 발생하는 '업로드 중 에러 발생' 알림창이나옵니다.react-query devtools에서 mutation 에러를 확인하니인피니트 스크롤링하는 pages에 관한 오류가 나왔습니다.FollowingPosts 컴포넌트와 PostForm에서 queryClient.getQueryData(["posts","followings"] 에서 받는 데이터를 출력해보니 아래와 같은 데이터 구조를 가지고 있습니다.FollowingPosts 컴포넌트 코드는 다음과 같습니다.export default function FollowingPosts() { const { isPending, data } = useQuery<PostType[]>({ queryKey: ["posts", "followings"], queryFn: getFollowingPosts, staleTime: 60 * 1000, gcTime: 300 * 1000, }); if (isPending) return <Loading />; console.log("data2", data); return data?.map((post) => <Post key={post.postId} post={post} />); }FollowingPosts 컴포넌트에서는 useInifiniteQuery가 아닌 useQuery를 통해 데이터를 받아와서 데이터 안에 있는 pages가 없어서 이런 오류가 나오는걸까요??우선 useInfiniteQuery로 변경해 게시물을 등록하면잘 작동되는 것은 확인했습니다.
-
미해결Next + React Query로 SNS 서비스 만들기
next.js 멀티플 런타임 관련해서 질문 올립니다..
안녕하세요! 강의와 직접적으로 관련된 질문은 아니지만 도저히 해결책을 도저히 찾기가 어려워서 질문 글 올립니다..하나의 Next.js 프로젝트에서 백엔드 api를 구성할 때, node.js와 파이썬 서버리스 함수를 함께 사용할 수 있나요?백엔드로 파이썬 서버리스 함수를 단독으로 사용하는 것은 가능한 것 같은데,동일한 프로젝트에서 node.js 서버리스 함수와 함께 사용할 수 있는지가 궁금합니다..
-
해결됨Next + React Query로 SNS 서비스 만들기
Post 컴포넌트가 표시되지 않고 User가 undefined로 받아와져요
PostRecommends에서 전달한 post를 Post 컴포넌트에서 받아 표시하면 User에서만 오류가 발생합니다.User에 대한 정보를 다 받아오는데 Post 컴포넌트에서는 아무것도 표시되지 않습니다.프로필 탭을 눌러 User.id 페이지로 이동하면 오류가 발생합니다.Cannot read properties of undefined (reading 'User')User에 옵셔널 체이닝을 붙여도 동일한 오류가 발생합니다.Post 코드 첨부합니다. import { Post as IPost } from "@/model/Post"; dayjs.locale("ko"); dayjs.extend(relativeTime); type Props = { noImage?: boolean; post: IPost; }; export default function Post({ noImage, post }: Props) { const target: IPost = post; if (Math.random() > 0.5 && !noImage) { target?.Images.push( { imageId: 1, link: faker.image.urlLoremFlickr() }, { imageId: 2, link: faker.image.urlLoremFlickr() }, { imageId: 3, link: faker.image.urlLoremFlickr() }, { imageId: 4, link: faker.image.urlLoremFlickr() } ); } return ( <PostArticle post={target}> <div className={style.postWrapper}> <div className={style.postUserSection}> <Link href={`/${target.User?.id}`} className={style.postUserImage}> <img src={target.User.image} alt={target.User.nickname} /> <div className={style.postShade} /> </Link> </div> <div className={style.postBody}> <div className={style.postMeta}> <Link href={`/${target.User.id}`}> <span className={style.postUserName}>{target.User.nickname}</span> <span className={style.postUserId}>@{target.User.id}</span> · </Link> <span className={style.postDate}> {dayjs(target.createdAt).fromNow(true)} </span> </div> <div>{target.content}</div> <div> <PostImages post={target} /> </div> <ActionButtons /> </div> </div> </PostArticle> ); }
-
해결됨Next + React Query로 SNS 서비스 만들기
react-query onMutate vs onSuccess / mutate vs mutateasync 가장 적절한 쓰임이 궁금합니다.
안녕하세요. 첫번째 질문은 onMutate vs onSuccess 인데 사실 낙관적 업데이트를 해주냐 안해주냐에 따라서 기호에 따라 다른 경우는 알겠습니다. 제가 궁금한거는 onSuccess invalidatequeries를 무지성으로 사용해도 query-key외 따로 전달 되는 params값이 중요하진 않은 것 같아서 쉽게 적용 했었던 기억이 있는 것 같습니다/ 예를 들면 onSuccess를 해주는 mutation에 invalidatequeries로 invalidatequeries(['key', {...}])를 굳이 안하고 invalidatequeries(['key']) 요거만 해줘도 새로 캐싱된 API를 새로 조회 하는 것 같더라구요. 근데 onMutate를 쓰려는 경우에 getQueryData에 query-key 정보와 그에 매칭하는 파라메터를 정확하게 넘겨주지 않으면 undefined 같은데 애초 설계 할때 getQueryData뒤에 보내는 파라메터를 잘 가져 올 수 있게 해야 할지 혹시 다른 방법이 있는지 궁금합니다. query에서 find해서 찾기는 보장이 안되는 것 같구요? 이건 사용자가 셀렉트 박스로 막 선택해서 조회 다시 이전것 조회 요런식으로 하다보니 마지막으로 선택된 파람 정보가 명확하진 않더라구요. 현재는 혹시 모르니 전부 searchParams로 개편은했지만.... 실제 프로젝트에서는 어드민성(?) 서비스를 제공 해서 ux는 딱히...?????? 엄청 중요한 않기도 했고 지식도 부족해서 onMutate 대신에 onSuccess를 썼지만 보통은 좀 어떻게 잘 써야하는지 궁금합니다. 2번째 질문은 이건 진짜 뭘 더 써야하는지 모르겠습니다.쓰임에 따라 다르게 써야한다면 어떤 경우인지 취향에 차이라면 취향것 쓰면 되는건지 궁금합니다
-
미해결Next + React Query로 SNS 서비스 만들기
혹시 Search페이지에서 매개변수 누가 넘겨주는건가요?
수업을 다 듣고 저 스스로 코드를 짜던중 갑자기 놓친부분이 있는 것 같아 여쭤봅니다.혹시<search/page.tsx>export default function Search({ searchParams }: Props) {여기서 이 search/page.tsx의 매개변수는 누가 넘겨주는 걸까요?
-
미해결Next + React Query로 SNS 서비스 만들기
미리보기했을때 저는 아예안뜨는데 뭐가 문제일까요?
저는 미리보기하면 PostRecommend.tsx부분이 아예 안뜨는데 뭐가 잘못된지 모르겠어서 질문드립니다.(단, 화면에 정상적으로 post들은 문제없이 뜹니다.)<PostRecommend.tsx>의 코드는 아래입니다."use client" import Post from "../../_component/Post"; import { useQuery } from "@tanstack/react-query" import { getPostRecommends } from "../_lib/getPostRecommends" import { Post as IPost } from "@/app/model/post"; export default function PostRecommend(){ const {data} = useQuery<IPost[]>({queryKey:['posts','recommends'],queryFn:getPostRecommends}); return data?.map((post)=> <Post key={post.postId} post={post}/> ) } 또한 Post.tsx에서 넘겨받은 post를 console.log해봤는데 undefined가 나와 이것과 연관되어있지않나 싶어서 Post.tsx의 코드도 올리겠습니다.import style from './post.module.css'; import Link from "next/link"; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import 'dayjs/locale/ko'; import ActionButtons from "@/app/(afterLogin)/_component/ActionButtons"; import PostArticle from "@/app/(afterLogin)/_component/PostArticle"; import {faker} from '@faker-js/faker'; import PostImages from "@/app/(afterLogin)/_component/PostImages"; import { Post as IPost } from '@/app/model/post'; dayjs.locale('ko'); dayjs.extend(relativeTime) type Props = { noImage?: boolean, post:IPost, } export default function Post({ noImage,post }: Props) { const target = post; console.log("+++++++@@@@@@"+target); return ( <PostArticle post={target}> <div className={style.postWrapper}> <div className={style.postUserSection}> <Link href={`/${target.User.id}`} className={style.postUserImage}> <img src={target.User.image} alt={target.User.nickname}/> <div className={style.postShade}/> </Link> </div> <div className={style.postBody}> <div className={style.postMeta}> <Link href={`/${target.User.id}`}> <span className={style.postUserName}>{target.User.nickname}</span> <span className={style.postUserId}>@{target.User.id}</span> · </Link> <span className={style.postDate}>{dayjs(target.createdAt).fromNow(true)}</span> </div> <div>{target.content}</div> {!noImage && <div> <PostImages post={target} /> </div>} <ActionButtons/> </div> </div> </PostArticle> ) }
-
해결됨Next + React Query로 SNS 서비스 만들기
게시물 업로드 POST 메서드 Internal Sever Error
게시물 업로드할 때 status: 500 Internal Server Error가나옵니다. 게시물 업로드 함수는 아래와 같습니다. const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(); formData.append("content", content); imgPreview.forEach((img) => { img && formData.append("images", img.file); }); await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/posts`, { method: "post", credentials: "include", body: formData, }); };작성한 폼데이터를 전송한 응답을 출력해보니 status:500,Internal Server Error가 나옵니다. 네트워크 탭에서 확인해보니 formData는 제대로 전송되는 듯해요. 서버 로그로 확인한 결과입니다. 서버에서 문제가 발생한걸까요 ?
-
미해결Next + React Query로 SNS 서비스 만들기
해당 예제 코드는 ch4에 없는 것 같아서 질문 올립니다.
Suspense로 Streaming하여 최적화하기(feat. loading.tsx, error.tsx)해당 강의 예제 코드를 보려고 github에서 이리저리 굴러봐도 강의 예제코드와 동일한 코드가 보이지 않아서 질문 올리게 되었습니다.혹시 suspense hook / reactQuery로 suspense 사용해보기에 관한 맛만 보여주시고 깃에서는 확인 불가능한걸까요?
-
미해결Next + React Query로 SNS 서비스 만들기
로그아웃할때 어떻게 next-auth는 이것이 api/logout으로의 post요청을 보내는것인지 아는건가요?
http.post('/api/logout', () => { console.log('로그아웃'); return new HttpResponse(null, { headers: { 'Set-Cookie': 'connect.sid=;HttpOnly;Path=/;Max-Age=0' } }) }),위 코드가 제로초님이 로그아웃을 위한 handler를 짜놓으신 건데 정작 로그아웃을할때는 const onLogout = () => { signOut({ redirect: false }) .then(() => { router.replace('/'); }); };위와 같이 그저 signOut 함수만 사용하고있으며 로그인때와 같이 따로 providers에서 fetch 경로를 설정해준것도아닌데 next-auth에서는 어떻게 signOut의 경로가 /api/logout인지 아는건가요?
-
미해결Next + React Query로 SNS 서비스 만들기
isPending과 isLoading의 쓰임새에 대하여
isPending은 데이터를 불러오고 있을 때, true가 되고isLoading은 쿼리가 처음으로 실행될 때 true가 되는 것으로 알고 있습니다.제가 이해하기로는 두 속성의 개념이 상당히 많이 겹칠 수 있을 것 같은데, 왜 이렇게 개별로 있는 것인지 궁금합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
handlers.ts에서 회원가입쪽 handler를 짤때의 질문입니다.
http.post('/api/users', async ({ request }) => { console.log('회원가입'); // return HttpResponse.text(JSON.stringify('user_exists'), { // status: 403, // }) return HttpResponse.text(JSON.stringify('ok'), { headers: { 'Set-Cookie': 'connect.sid=msw-cookie; HttpOnly;Path=/;Max-Age=0' } }) }),현재 위 코드가 제로초님의 회원가입 코드인데로그아웃을 할때 세션을 만료하기위해서 Max-Age=0을 넣는것은 이해가 되지만왜 굳이 회원가입을 할때도 Max-Age=0을 붙이신건가요?회원가입시 쿠키가 왜 필요한지와 필요하다고하더라도 왜 굳이 바로 Max-Age=0을 추가해서 바로 만료시켜버리는지가 궁금합니다!
-
미해결Next + React Query로 SNS 서비스 만들기
link태그의 prefetching 질문
안녕하세요 선생님상세페이지에서 홈으로 이동할때 로딩화면에 관련해서Link태그의 prefetching 질문있습니다.아래와 같이 suspense를 적용했을때app/(afterLoging)/home/page.tsximport style from './home.module.scss' import Tab from "@/app/(afterLogin)/home/_component/Tab"; import TabProvider from "@/app/(afterLogin)/home/_component/TabProvider"; import PostForm from "@/app/(afterLogin)/home/_component/PostForm"; import TabDeciderSuspense from '@/app/(afterLogin)/home//_component/TabDeciderSuspense'; import { Suspense } from 'react'; import Loading from './loading'; import { auth } from '@/auth'; export default async function Home() { const session = await auth(); return ( <main className={style.main}> <TabProvider> <Tab /> <PostForm me={session} /> {/* suspense는 서버컴포넌트여야만 한다. */} {/* suspense는 부모컴포넌트여야지 자식(아래)있는 컴포넌트 감지할 수 있다. */} <Suspense fallback={<Loading />}> <TabDeciderSuspense /> </Suspense> </TabProvider> </main> ); } next.js 문서를 보면link태그가 있는 경우, 화면에 들어왔을때static한 부분은 prefetch하고, 데이터 호출이 필요한 경우는 loading.tsx까지 호출해준다고 되어있더라구요.그래서 제가 기대한 것은 상세페이지에서, 홈의 Link태그가 화면에 들어오기 대문에, 홈으로 이동했을때 첫번째 이미지가 아닌, 두번째 이미지처럼 로딩이 되어야할 것 같은데 첫번째 이미지 처럼 되더라구요. (이동한것도 30초 이내였습니다)혹시 제가 잘못이해한건지 알려주시면 감사합니다.유저 상세페이지에서 홈으로 이동할때suspense적용후 새로고침하거나 팔로우중 클릭시
-
미해결Next + React Query로 SNS 서비스 만들기
찜하기하고 해당글 상세페이지 이동시 찜 정보제대로 안내려오는 현상
안녕하세요 선생님홈에서 찜했다, 안했다 잘 작동하고상세페이지로 이동하면 찜하기 데이터가 제대로 내려오지 않는 부분을 확인했습니다.호출은 아래와 같이 하고있습니다./src/app/(afterLogin)/[username]/status/[id]/page.tsximport BackButton from "@/app/(afterLogin)/_component/BackButton"; import style from './singlePost.module.scss'; import Post from "@/app/(afterLogin)/_component/Post"; import CommentForm from "@/app/(afterLogin)/[username]/status/[id]/_component/CommentForm"; import SinglePost from '@/app/(afterLogin)/[username]/status/[id]/_component/SinglePost'; import Comments from '@/app/(afterLogin)/[username]/status/[id]/_component/Comments'; import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query'; import { getSinglePost } from '@/app/(afterLogin)/[username]/status/[id]/_lib/getSinglePost'; import { getComments } from '@/app/(afterLogin)/[username]/status/[id]/_lib/getComments'; type Props = { params: { id: string} } export default async function Pasge({ params }: Props) { console.log('----------------------------- single post params', params); const { id } = params; const queryClient = new QueryClient(); await queryClient.prefetchQuery({ queryKey: ['posts', id], queryFn: getSinglePost }); await queryClient.prefetchQuery({ queryKey: ['posts', id, 'comments'], queryFn: getComments }); const dehydratedState = dehydrate(queryClient); return ( <div className={style.main}> <HydrationBoundary state={dehydratedState}> <div className={style.header}> <BackButton/> <h3 className={style.headerTitle}>게시하기</h3> </div> <SinglePost id={id} /> <CommentForm id={id} /> <div> <Comments id={id} /> </div> </HydrationBoundary> </div> ) }/src/app/(afterLogin)/[username]/status/[id]/_component/SinglePost.tsx'use client'; import { Post as IPost } from '@/models/Post' import { useQuery } from '@tanstack/react-query' import { getSinglePost } from '@/app/(afterLogin)/[username]/status/[id]/_lib/getSinglePost'; import Post from '@/app/(afterLogin)/_component/Post'; export default function SinglePost({id, noImage}: {id: string, noImage?: boolean}) { const { data: post, error } = useQuery<IPost, Object, IPost, [_1: string, _2: string]>({ queryKey: ['posts', id], queryFn: getSinglePost, staleTime: 60 * 1000, gcTime: 300 * 100, }); console.log(post, '--------------------------single post'); if (error) { return ( <div style={{ height: 100, alignItems: 'center', fontSize: 31, fontWeight: 'bold', display: 'flex', justifyContent: 'center' }}>게시글을 찾을 수 없습니다.</div> ) } if (!post) { return null; } return <Post post={post} key={post.postId} noImage={noImage} /> }찜하기 코드export default function ActionButtons({ white, post }: Props) { const queryClient = useQueryClient(); const { data: session } = useSession(); const commented = !!post.Comments?.find(d => d.userId === session?.user?.email); const reposted = !!post.Reposts?.find(d => d.userId === session?.user?.email); const liked = !!post.Hearts?.find(d => d.userId === session?.user?.email); const { postId } = post; const heart = useMutation({ mutationFn: () => { return fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/posts/${postId}/heart`, { method: 'post', credentials: 'include', }) }, onMutate() { const queryCache = queryClient.getQueryCache(); const queryKeys = queryCache.getAll().map(cache => cache.queryKey); console.log('queryKey', queryKeys); queryKeys.forEach((queryKey) => { if (queryKey[0] === 'posts') { const value: Post | InfiniteData<Post[]> | undefined = queryClient.getQueryData(queryKey); if (value && 'pages' in value) { const obj = value.pages.flat().find(d => d.postId === postId); if (obj) { // 존재는 하는지? const pageIndex = value.pages.findIndex(page => page.includes(obj)); const index = value.pages[pageIndex].findIndex(d => d.postId === postId); const shallow = produce(value, draft => { draft.pages[pageIndex][index].Hearts = [{ userId: session?.user?.email as string }]; draft.pages[pageIndex][index]._count.Hearts += 1; }) queryClient.setQueryData(queryKey, shallow); } } else if (value) { // 싱글 포스트인 경우 if (value.postId === postId) { const shallow = { ...value, Hearts: [...value.Hearts, { userId: session?.user?.email as string }], _count: { ...value._count, Hearts: value._count.Hearts + 1, } } queryClient.setQueryData(queryKey, shallow); } } } }) }, onError() { }, onSettled() { } }); 다른 찜하기 질문에서키를가지고 호출하지 않해서라고 하신걸 봤었는데,위와 같은 경우에는 클라이언트 서버에서 쿼리키를 가지고 호출했는데 데이터가 잘 안내려오는 것을 확인했습니다.찜을 눌렀을때찜을 누르고 해당글 상세로 이동했을때이러한 경우에는 찜하기를 누르고 추가적인 작업이 필요한지 궁금합니다. 예를들면 찜하기를 누르고 해당 쿼리키의 데이터를 호출해야한다는지... 혹은 제가 잘못호출한것이라면 알려주시면 감사하겠습니다.
-
미해결Next + React Query로 SNS 서비스 만들기
프로필 부분 getUser.ts Error가 반환되지 않는 이유를 모르겠습니다.
import { QueryFunction } from "@tanstack/react-query"; import { User } from "@/model/User"; const getUser: QueryFunction<User, [string, string]> = async ({ queryKey }) => { const [_1, username] = queryKey; const res = await fetch(`http://localhost:9090/api/users/${username}`, { next: { tags: ["users", username], }, cache: "no-store", }); console.log("res.ok : ", res.ok); if (!res.ok) { throw new Error("해당 유저 정보를 불러오지 못 했습니다."); } return res.json(); }; export default getUser; mocks>handler.ts에 없는 username을 url에 입력해도 res.ok로 떠서 if문을 그냥 통과해버립니다.혹시 제가 놓치는 부분이 있는걸까요?
-
미해결Next + React Query로 SNS 서비스 만들기
prefetchQuery 관련 질문
prefetchQuery 서버컴포넌트에서 데이터를 한번 불러오면, 정상적으로 불러왔는지 확인할 수 있나요??서버 컴포넌트에서 prefetchQuery한 다음에 클라이언트컴포넌트에서 useQuery로 불러오게 되면(queryKey 동일) 이미 데이터가 저장 되어 있는거로 알고 있는데,console.log를 찍어보게 되면, undefined가 뜬 다음에 데이터가 호출 됩니다.prefetchQuery가 정상적으로 동작 안하는게 아닌가 싶습니다. export default function TestClient() { const { data } = useQuery({ queryKey: ['typeData'], queryFn: getTypeData, }); console.log(typeData); ...export default async function TestServer() { const queryClient = new QueryClient(); await queryClient.prefetchQuery({ queryKey: ['typeData'], queryFn: getTypeData }); const dehydratedState = dehydrate(queryClient); return ( <> <HydrationBoundary state={dehydratedState}> <TestClient /> </HydrationBoundary> </> ); }
-
해결됨Next + React Query로 SNS 서비스 만들기
인터셉팅 라우터가 signup에는 적용이 안되는 문제
login은 인터셉팅 라우터가 잘 되는데,signup은 (.)i로 인터셉팅이 안되고 버튼 클릭을 하면 그냥 i/flow/signup/page.tsx로만 보여지는 문제가 있습니다. 왜 signup은 인터셉팅 라우터가 작동이 안되는 것일까요?? 경로는 이렇게 잘 설정되어있고 안에 파일 내용은 아래와 같습니다. 아래는 @modal/(.)i/flow/signup/page.tsx아래는 i/flow/signup/page.tsx홈 버튼 링크는 아래와 같이 되어있습니다.혹시 제가 빠뜨린 무엇이 있을까요?
-
미해결Next + React Query로 SNS 서비스 만들기
http://localhost:3000/api/auth/session 500에러
안녕하세요 선생님 로컬에서 locallhost:3000/api/auth를 호출하면 500에러가 발생합니다.* 참고: 1. useSession()은 모두 클라이언트 컴포넌트에만 적용했습니다. 2. 원인을 찾아보다가 예전 선생님 답변해주신 서버 컴포넌트에서 세션props로 받아스라고 하신거 참고해서 그렇게 수정해도 동일하게 발생합니다.3. 비슷한 질문에 쿠키랑 다 없앤뒤 잘됐다는것도 보고 다제거해봤지만 동일하게 발생했습니다.4. .next, node_module 다 제거후 새로 깔거나, 빌드를 새로 하거나 테스트도 해보았습니다.5. 브라우저 껐다키고, 컴퓨터 재부팅도 해보았습니다.6. 선생님의 깃코드를 가져다 쓰기도 해보았습니다.빌드 한후에 npm run start로 했을땐 정상적으로 나오는데 로컬에서만 발생합니다. /src/auth.tsimport NextAuth from "next-auth" import Credentials from "next-auth/providers/credentials" export const { handlers, signIn, signOut, auth } = NextAuth({ pages: { signIn: '/login', newUser: '/signup', }, providers: [ Credentials({ credentials: { id: {}, password: {}, }, authorize: async (credentials) => { try { const authResponse = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/login`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(credentials), }) console.log(authResponse.ok, '-----------------------------authResponse.ok'); if (!authResponse.ok) { return null; } let user = await authResponse.json(); console.log(user, '--------------------------------'); return { ...user, email: user.id, name: user.nickname, image: user.image, } } catch (err) { console.error('로그인 에러', err); } }, }), ], }).envAUTH_SECRET=WKFOJhbw7gZOYXumT66CwwKtDZ9YsalV8qMRx134Uc8= AUTH_TRUST_HOST=http://localhost:3000.env.localNEXT_PUBLIC_API_MOCKING=enabled NEXT_PUBLIC_MODE=local NEXT_PUBLIC_BASE_URL=http://localhost:9090 NEXTAUTH_URL=http://localhost:3000 /src/app/layout.tsximport type { Metadata, Viewport } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import { MSWComponent } from './_component/MSWComponent'; import AuthSession from './_component/AuthSession'; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: { template: '%s | MBTI', default: 'MBTI가 어떻게 되세요?', }, description: "MBTI로 찾는 내 연인", }; export const viewport: Viewport = { width: 'device-width', initialScale: 1, maximumScale: 1, userScalable: false, // Also supported by less commonly used // interactiveWidget: 'resizes-visual', } export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body className={`${inter.className} antialiased`}> <MSWComponent /> <AuthSession> {children} </AuthSession> </body> </html> ); } /src/app/_component'use client'; import { SessionProvider } from 'next-auth/react'; import { ReactNode } from 'react'; export default function AuthSession({children}: {children: ReactNode}) { return ( <SessionProvider>{children}</SessionProvider> ) } /src/app/api/auth/[...nextauth]/route.tsimport { handlers } from "@/auth" // Referring to the auth.ts we just created export const { GET, POST } = handlers; 폴더구조📦src ┣ 📂app ┃ ┣ 📂(afterLogin) ┃ ┃ ┣ 📂(home) ┃ ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┃ ┣ 📜mbtiCarousel.module.css ┃ ┃ ┃ ┃ ┣ 📜MbtiCarousel.tsx ┃ ┃ ┃ ┃ ┗ 📜UserCardList.tsx ┃ ┃ ┃ ┗ 📂_lib ┃ ┃ ┃ ┃ ┗ 📜getUserAll.ts ┃ ┃ ┣ 📂@modal ┃ ┃ ┃ ┣ 📂(.)promise ┃ ┃ ┃ ┃ ┗ 📂form ┃ ┃ ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┃ ┣ 📂[userId] ┃ ┃ ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┃ ┃ ┣ 📜UserDetailContent.tsx ┃ ┃ ┃ ┃ ┃ ┣ 📜UserDetailPromise.tsx ┃ ┃ ┃ ┃ ┃ ┣ 📜UserDetailTop.tsx ┃ ┃ ┃ ┃ ┃ ┣ 📜UserInfo.tsx ┃ ┃ ┃ ┃ ┃ ┗ 📜UsrCarousel.tsx ┃ ┃ ┃ ┃ ┣ 📂_lib ┃ ┃ ┃ ┃ ┃ ┣ 📜getAUser.ts ┃ ┃ ┃ ┃ ┃ ┗ 📜getUserPromise.ts ┃ ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┃ ┗ 📜default.tsx ┃ ┃ ┣ 📂like ┃ ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┃ ┣ 📜LikeCard.tsx ┃ ┃ ┃ ┃ ┣ 📜LikeTabProvider.tsx ┃ ┃ ┃ ┃ ┣ 📜Tab.tsx ┃ ┃ ┃ ┃ ┣ 📜TabDecider.tsx ┃ ┃ ┃ ┃ ┣ 📜UserILike.tsx ┃ ┃ ┃ ┃ ┗ 📜UserLikeMe.tsx ┃ ┃ ┃ ┣ 📂_lib ┃ ┃ ┃ ┃ ┣ 📜getUserILike.ts ┃ ┃ ┃ ┃ ┗ 📜getUserLikeMe.ts ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┣ 📂messages ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┣ 📂profile ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┣ 📂promise ┃ ┃ ┃ ┣ 📂form ┃ ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┃ ┣ 📜PromiseCard.tsx ┃ ┃ ┃ ┃ ┣ 📜PromiseCardDropdown.tsx ┃ ┃ ┃ ┃ ┣ 📜PromiseCardLink.tsx ┃ ┃ ┃ ┃ ┣ 📜PromiseFormButton.tsx ┃ ┃ ┃ ┃ ┣ 📜promiseSection.module.css ┃ ┃ ┃ ┃ ┗ 📜PromiseSection.tsx ┃ ┃ ┃ ┣ 📂_lib ┃ ┃ ┃ ┃ ┗ 📜getPromiseAll.ts ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┣ 📂recommend ┃ ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┃ ┗ 📜RecommendSection.tsx ┃ ┃ ┃ ┣ 📂_lib ┃ ┃ ┃ ┃ ┗ 📜getUserRecommends.ts ┃ ┃ ┃ ┣ 📜page.tsx ┃ ┃ ┃ ┗ 📜recommend.module.css ┃ ┃ ┣ 📂search ┃ ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┃ ┣ 📜SearchCard.tsx ┃ ┃ ┃ ┃ ┣ 📜SearchForm.tsx ┃ ┃ ┃ ┃ ┗ 📜SearchResult.tsx ┃ ┃ ┃ ┣ 📂_lib ┃ ┃ ┃ ┃ ┗ 📜getSearchResult.ts ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┣ 📂setting ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┣ 📂[userId] ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┣ 📜Back.tsx ┃ ┃ ┃ ┣ 📜ImageWithPlaceholder.tsx ┃ ┃ ┃ ┣ 📜LogoutButton.tsx ┃ ┃ ┃ ┣ 📜MainTitle.tsx ┃ ┃ ┃ ┣ 📜MbtiRecommendSection.tsx ┃ ┃ ┃ ┣ 📜Modal.tsx ┃ ┃ ┃ ┣ 📜RQProvider.tsx ┃ ┃ ┃ ┣ 📜SearchForm.tsx ┃ ┃ ┃ ┣ 📜userCard.module.css ┃ ┃ ┃ ┣ 📜UserCard.tsx ┃ ┃ ┃ ┣ 📜UserCardArticle.tsx ┃ ┃ ┃ ┗ 📜UserRandomRecommendSection.tsx ┃ ┃ ┣ 📂_lib ┃ ┃ ┃ ┣ 📜getBase64.ts ┃ ┃ ┃ ┗ 📜getUserRandomRecommends.ts ┃ ┃ ┣ 📜layout.module.css ┃ ┃ ┣ 📜layout.tsx ┃ ┃ ┗ 📜page.tsx ┃ ┣ 📂(beforeLogin) ┃ ┃ ┣ 📂login ┃ ┃ ┃ ┣ 📜login.module.css ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┃ ┣ 📂signup ┃ ┃ ┃ ┣ 📜page.tsx ┃ ┃ ┃ ┗ 📜signup.module.css ┃ ┃ ┣ 📂userSetting ┃ ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┃ ┣ 📜BirthdaySelect.tsx ┃ ┃ ┃ ┃ ┣ 📜DrinkSelect.tsx ┃ ┃ ┃ ┃ ┣ 📜GenderSelect.tsx ┃ ┃ ┃ ┃ ┣ 📜ImageSelect.tsx ┃ ┃ ┃ ┃ ┣ 📜JobSelect.tsx ┃ ┃ ┃ ┃ ┣ 📜MbtiSelect.tsx ┃ ┃ ┃ ┃ ┣ 📜NicknameSelect.tsx ┃ ┃ ┃ ┃ ┣ 📜RegionSelect.tsx ┃ ┃ ┃ ┃ ┣ 📜ReligionSelect.tsx ┃ ┃ ┃ ┃ ┣ 📜SchoolSelect.tsx ┃ ┃ ┃ ┃ ┣ 📜SetUser.tsx ┃ ┃ ┃ ┃ ┣ 📜SetUser2.tsx ┃ ┃ ┃ ┃ ┣ 📜SetUserComplete.tsx ┃ ┃ ┃ ┃ ┣ 📜SetUserProvider.tsx ┃ ┃ ┃ ┃ ┣ 📜SetUserProvider2.tsx ┃ ┃ ┃ ┃ ┣ 📜SetUserTop.tsx ┃ ┃ ┃ ┃ ┣ 📜SmokeSelect.tsx ┃ ┃ ┃ ┃ ┗ 📜TallSelect.tsx ┃ ┃ ┃ ┣ 📜page.tsx ┃ ┃ ┃ ┗ 📜userSetting.module.css ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┣ 📜title.module.css ┃ ┃ ┃ ┗ 📜Title.tsx ┃ ┃ ┗ 📂_lib ┃ ┃ ┃ ┗ 📜login.ts ┃ ┣ 📂api ┃ ┃ ┗ 📂auth ┃ ┃ ┃ ┗ 📂[...nextauth] ┃ ┃ ┃ ┃ ┗ 📜route.ts ┃ ┣ 📂_component ┃ ┃ ┣ 📜AuthSession.tsx ┃ ┃ ┣ 📜BottomNav.tsx ┃ ┃ ┣ 📜LeftNav.tsx ┃ ┃ ┣ 📜MSWComponent.tsx ┃ ┃ ┣ 📜nav.module.css ┃ ┃ ┗ 📜TopNav.tsx ┃ ┣ 📜favicon.ico ┃ ┣ 📜globals.css ┃ ┗ 📜layout.tsx ┣ 📂components ┃ ┗ 📂ui ┃ ┃ ┣ 📜avatar.tsx ┃ ┃ ┣ 📜badge.tsx ┃ ┃ ┣ 📜button.tsx ┃ ┃ ┣ 📜card.tsx ┃ ┃ ┣ 📜carousel.tsx ┃ ┃ ┣ 📜dropdown-menu.tsx ┃ ┃ ┣ 📜form.tsx ┃ ┃ ┣ 📜input.tsx ┃ ┃ ┣ 📜label.tsx ┃ ┃ ┣ 📜progress.tsx ┃ ┃ ┣ 📜skeleton.tsx ┃ ┃ ┣ 📜textarea.tsx ┃ ┃ ┗ 📜tooltip.tsx ┣ 📂lib ┃ ┗ 📜utils.ts ┣ 📂mocks ┃ ┣ 📜browser.ts ┃ ┣ 📜handlers.ts ┃ ┗ 📜http.ts ┣ 📂model ┃ ┣ 📜Post.ts ┃ ┣ 📜postImage.ts ┃ ┣ 📜User.ts ┃ ┗ 📜UserImage.ts ┣ 📜auth.ts ┗ 📜middleware.ts 로컬에서 잘되다가 어느순간부터 됐다 안됐다 하다가 이제는 안되고 있습니다. 어느 부분을 좀 더 찾아보고 해야할지 조언주시면 정말 감사하겠습니다.
-
해결됨Next + React Query로 SNS 서비스 만들기
prefetchQuery 적용 후 Warning: Text content did not match. 오류가 발생합니다.
안녕하세요. 강의 잘 보고 있습니다.!!👍👍react-query를 적용 중 오류가 표시되어 오류와 궁금한 부분이 있어서 문의 드립니다.Text content did not match. 오류가 발생하는데 어느 부분이 문제인지 잘 모르겠습니다 ㅠㅠprefetchQuery 를 사용하여 서버에서 post(트윗)데이터를 프리패치 후 TweetList컴포넌트에서 useQuery를 사용하게 만들었습니다. Post의 데이터는 msw에서 알려주신 faker를 통해서 요청마다 content를 동적으로 생성하게 했구요. 콘솔로 출력한 데이터와 reactQuery개발 도구의 데이터는 동일한데 화면에 표기된 데이터는 전혀 다른 데이터로 표시되네요..그리고 방식이 prefetchQuery 를 사용한 데이터는 staleTime과 상관없이 Fresh상태로 되어 useQuery 호출 시 데이터가 아직 fresh한 상태이므로 서버를 호출하지 않고 캐쉬에서 가져와 보여주는 방식이 맞나요?오류로그app-index.js:33 Warning: Text content did not match. Server: "Tener adulatio decens conitor. Thesis apostolus ago at decerno. Iste uberrime commodo. Verus amitto quas cometes delicate. Cognomen alii curto ciminatio. Solitudo complectus tristis. Teneo sum vindico. Adhuc decimus triumphus ipsa arguo umquam addo adulatio cresco. Sponte degenero non trado cauda beneficium laboriosam approbo." Client: "Tremo collum sint terror templum summisse viduo usque unus benevolentia. Eligendi substantia concido amissio uredo turpis aperte. Admoneo titulus audeo conicio fuga." at div at div at div at article at TweetWrapper (webpack-internal:///(app-pages-browser)/./src/app/(afterLogin)/home/_components/TweetWrapper.tsx:11:11) at Tweet (webpack-internal:///(app-pages-browser)/./src/app/(afterLogin)/home/_components/Tweet.tsx:31:11) at TweetList (webpack-internal:///(app-pages-browser)/./src/app/(afterLogin)/home/_components/TweetList.tsx:16:93) at HydrationBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@tanstack+react-query@5.39.0_react@18.2.0/node_modules/@tanstack/react-query/build/modern/HydrationBoundary.js:14:11) at HomeContextProvider (webpack-internal:///(app-pages-browser)/./src/app/(afterLogin)/home/_components/Provider.tsx:17:11) at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:242:11) at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:74:9) at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:82:11) at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/not-found-boundary.js:84:11) at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:340:11) at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/error-boundary.js:162:11) at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:152:9) at ScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:227:11) at RenderFromTemplateContext (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/render-from-template-context.js:16:44) at OuterLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:359:11) at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:242:11) at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:74:9) at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:82:11) at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/not-found-boundary.js:84:11) at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:340:11) at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/error-boundary.js:162:11) at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.1.4_@babel+core@7.24.4_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:152:9) at ScrollAndFocu소스코드(Tweet이 Post입니다! 나중에 바꿔야겠네여)[page.tsx] /homeimport React from "react"; import HomeTopTab from "./_components/HomeTopTab"; import WriteForm from "./_components/WriteForm"; import TweetList from "./_components/TweetList"; import HomeContextProvider from "./_components/Provider"; import { QueryClient, dehydrate, HydrationBoundary, } from "@tanstack/react-query"; import getPostRecommends from "./_lib/getPostRecommends"; const HomePage = async () => { const queryClient = new QueryClient(); await queryClient.prefetchQuery({ queryKey: ["tweet", "recommends"], queryFn: getPostRecommends, }); const dehydratedState = dehydrate(queryClient); return ( <HomeContextProvider> <HomeTopTab /> <WriteForm /> <HydrationBoundary state={dehydratedState}> <TweetList /> </HydrationBoundary> </HomeContextProvider> ); }; export default HomePage; TweetList.tsx"use client"; import React from "react"; import Tweet from "./Tweet"; import getPostRecommends from "../_lib/getPostRecommends"; import { Post } from "./TweetWrapper"; import { QueryClient, useQuery } from "@tanstack/react-query"; const TweetList = () => { const { data: tweets } = useQuery<Post[]>({ queryKey: ["tweet", "recommends"], queryFn: getPostRecommends, enabled: false, }); console.log("🚀 _ TweetList _ tweets:", tweets); return tweets?.map((tweet) => <Tweet post={tweet} key={tweet.postId} />); }; export default TweetList;
-
미해결Next + React Query로 SNS 서비스 만들기
찜하기 관련 질문드립니다.
안녕하세요 상세페이지에서 찜하기 요청을 하게되면 users/favorite/${peopleId}로 post 요청을 보내게되고 그럼 users/favorite api에 찜한 사람들의 목록이 추가됩니다.찜하기를 눌렀을경우 아이콘의 컬러를 변경시켜야해서 getQueryData로 찜한 사람들의 목록 을 가져오려고 하였는데 const { data: likePeopleList } = useQuery<GetPeoples>({ queryKey: ["get", "likepeoples"], queryFn: getLikePeoples, staleTime: 60 * 1000, // fresh -> stale, 5분이라는 기준 gcTime: 300 * 1000, });위에 코드가 없으면 likeQuery를 가져오지 못하고있는것같습니다. getQueryData로 가져오는 방법이 잘못된걸까요? 다른 좋은 방법있다면 여쭤보고싶습니다.밑에는 전체 코드입니다. type Props = { peopleId: string; }; export default function PeoplePosts({ peopleId }: Props) { const { data } = useQuery< GetPeoplePost, Object, GetPeoplePost, [_1: string, _2: string, _3: string] >({ queryKey: ["get", "peoplesDetail", peopleId], queryFn: getPeopleDetail, }); const { data: likePeopleList } = useQuery<GetPeoples>({ queryKey: ["get", "likepeoples"], queryFn: getLikePeoples, staleTime: 60 * 1000, // fresh -> stale, 5분이라는 기준 gcTime: 300 * 1000, }); const { content, nickname, userFileUrl, favoriteCount, viewCount, softSkill, techStack, links, position, alarmStatus, year, } = data?.data ?? {}; const queryClient = useQueryClient(); const likeQuery = queryClient.getQueryData<GetPeoples>([ "get", "likepeoples", ]); console.log("likeQuery", likeQuery); const liked = !!likeQuery?.data.find( (item) => item.userId === Number(peopleId), ); console.log("liked", liked); const like = useMutation({ mutationFn: (peopleId: string) => { return fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/users/favorite/${peopleId}`, { method: "post", }, ); }, onMutate(peopleId: string) { const oldData = queryClient.getQueryData<GetPeoples>([ "get", "likepeoples", ]); if (oldData) { const newData = { ...oldData, data: oldData.data.map((item) => ({ ...item, userId: Number(peopleId), })), }; queryClient.setQueryData(["get", "likepeoples"], newData); } }, }); const unLike = useMutation({ mutationFn: (peopleId: string) => { return fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/users/favorite/${peopleId}`, { method: "delete", }, ); }, onMutate(peopleId: string) { const oldData = queryClient.getQueryData<GetPeoples>([ "get", "likepeoples", ]); if (oldData) { const deleteData = { ...oldData, data: oldData.data.filter((item) => item.userId !== Number(peopleId)), }; queryClient.setQueryData(["get", "likepeoples"], deleteData); } }, }); const onLike: MouseEventHandler<HTMLButtonElement> = (e) => { e.preventDefault(); if (liked) { unLike.mutate(peopleId); } else { like.mutate(peopleId); } }; return ( <div className="flex flex-col"> <div className="flex flex-col gap-4 border-b pb-10"> <div className="flex items-center gap-[10px]"> <Image src={`${userFileUrl}`} alt="유저프로필" width={30} height={30} /> <h1 className="text-[32px] font-bold">{nickname}</h1> <div> <BlueTextBox textSize="12px" textToShow={`${position}`} /> </div> </div> <div className="flex flex-col gap-2"> {softSkill ?.split(",") .map((skill, i) => <HashTag text={skill} key={i} />)} </div> <div className="flex gap-3"> <HeartEyeIconBox count={favoriteCount as number} icon={heartIcon} /> <HeartEyeIconBox count={viewCount as number} icon={eyeIcon} /> </div> </div> <div className="mt-[42px] flex flex-col gap-[50px]"> <div className="people-post-grid"> <h1 className="text-[22px] font-bold">경력</h1> <h3>{year}</h3> </div> <div className="people-post-grid"> <h1 className="text-[22px] font-bold">사용언어</h1> <div className="flex gap-2"> {techStack ?.split(",") .map((stack, i) => ( <TechStack techStack={stack} showText key={`stack${i}`} /> ))} </div> </div> <div className="flex flex-col gap-3"> <h1 className="text-[22px] font-bold">자기소개</h1> <p>{content}</p> </div> <div className="flex flex-col gap-3"> <h1 className="text-[22px] font-bold">Link</h1> <Link href={links as string}>{links}</Link> </div> </div> <div className="mt-[60px] flex gap-[13px] self-center"> <button className={`h-[58px] w-[142px] rounded-md bg-neutral-orange-500 font-bold ${alarmStatus ? "text-neutral-white-0" : "text-neutral-black-800"}`} > {alarmStatus ? "제안하기" : "제안불가"} </button> <button onClick={onLike} className="flex h-[58px] w-[58px] flex-col items-center justify-center rounded-md border" > {liked ? ( <Image src={fillHeartIcon} alt="하트아이콘" width={20} height={20} /> ) : ( <Image src={heartIcon} alt="하트아이콘" width={20} height={20} /> )} <h5 className="text-[12px]">{favoriteCount}</h5> </button> </div> </div> ); }