묻고 답해요
140만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결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 서비스 만들기
react-query의 useSuspense.. 사용 시 클라이언트에서 suspense가 동작을 하지 않습니다.
안녕하세요. 강사님예제를 보고 하던 중 suspense 가 동작하지 않아 질문드립니다.처음 예시로 알려주신 react-query의 isPending 을 사용한 로딩처리는 잘 동작하지만 마지막에 알려주신 useSuspense(useSuspenseInfiniteQuery, useSuspenseQuery)들을 사용하는 경우 동작하지 않네요..*팔로우 중 을 선택해도 suspense에 설정한 로딩 컴포넌트가 나오지 않고 딜레이된 시간(5초) 후 데이터가 보여집니다. 로딩 컴포넌트도 회전하지 않고 멈춰있습니다.어떤부분을 봐야할까요?ㅠㅠ반대로 이런 증상을 경험하니 이전 데이터가 먼저 보여진 후 5초 뒤에 최신 데이터로 보여지므로 사용자가 잘 못된 데이터를 표시 할 수 있다는걸 배울 수 있었습니다.😎소스코드const HomePage = async () => { return ( <HomeContextProvider> <HomeTopTab /> <WriteForm /> <Suspense fallback={<Loader />}> <TabDividerSuspense /> </Suspense> </HomeContextProvider> ); };const TabDividerSuspense = async () => { const queryClient = new QueryClient(); await queryClient.prefetchInfiniteQuery({ queryKey: ["tweet", "recommends"], queryFn: getPostRecommends, initialPageParam: 0, }); const dehydratedState = dehydrate(queryClient); return ( <HydrationBoundary state={dehydratedState}> <TabDivider /> </HydrationBoundary> ); };const TabDivider = () => { const { tab } = useContext(HomeContext); return tab === "recommended" ? <TweetList /> : <FollowingList />; };const TweetList = () => { const { ref, inView } = useInView(); const { data, fetchNextPage, hasNextPage, isFetching, isPending } = useSuspenseInfiniteQuery< Post[], object, InfiniteData<Post[]>, [string, string], number >({ queryKey: ["tweet", "recommends"], queryFn: getPostRecommends, initialPageParam: 0, getNextPageParam: (lastPage) => lastPage.at(-1)?.postId, }); useEffect(() => { if (inView) { !isFetching && hasNextPage && fetchNextPage(); } }, [fetchNextPage, hasNextPage, inView, isFetching]); const tweets = useMemo(() => { if (data) { return data.pages.flat(); } }, [data]); return ( <> {tweets?.map((tweet) => ( <Tweet post={tweet} key={tweet.postId} /> ))} {isPending && <Loader />} <div ref={ref} /> </> ); };추가 질문빌드 후 네트워크 탭에서 home을 확인해보면 post 글 들이 모두 html로 변환되어 내려 오고 있습니다!(dev에서는 템플릿?으로 표현되더라고요)저는 html이 아닌 데이터 형태로 내려와 useQuery로 해당 키로 접근해서 그냥 데이터를 가져올 줄 알았는데..그게 아닌가보네요.혹시 좀 더 자세히 설명 좀 부탁드려도 될까요?ㅠ그리고 home의 미리보기 탭에서는 post글들이 아닌 로딩 컴포넌트가 보입니다. (위의 사진에 응답 탭에서는 post글 들이 존재하고요)로딩 컴포넌트가 보이는 이유는 하이드레이션이 처리되기 전이라 그런게 맞나요? 지금 자료를 다시 찾으려니 못 찾고 있는데.. suspense를 사용할 경우 완성된 화면이 아닌 로딩화면을 먼저 내려주므로 seo에는 나쁠 수 있다라는 글을 본적이 있던 것 같은데..맞을까요?*SEO 관련해서 추가로 궁금한건 강의가 따로 있다고 영상에서 말씀하셔서 거기까지 보고 필요할 경우 질문 한번 더 드리겠습니다.👍
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
AddPostScreen 부분에서 useEffect 의존성 배열 관련해서 질문있습니다!
강의를 보다가 해당 useEffect 의존성 배열 부분에 빈 배열을 넣어야 한다고 생각해서 넣었었는데 이러면 등록 버튼 눌렀을 때 에러가 뜹니다.그러다가 어쩔 때는 등록이 되기도 하네요 왜 이럴까요? 혹시 오타나 제가 잘못 작성한 부분이 있는걸까요?등록 눌러도 응답이 없길래 onError이용해서 찍어보니위 사진과 같이 계속 400 에러가 뜨고 있었어요 그래서 의존성 부분에 빈배열 빼고 강의에서 작성해주신 것처럼 아무것도 안넣었더니 정상적으로 작동했습니다!저와 비슷한 생각으로 의존성 관련 질문 주신분 있어서 답변 보니 빈배열 넣어야 한다고 하셨는데 전 왜 에러가 뜰까요? 스스로 해결해 보려다가 아직 부족한게 많아서 글 작성해봅니다! 코드도 같이 첨부합니당https://github.com/HYEJUNGYANG/Matzip
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
getProfileQuery.data 반환값들의 타입을 인식하지 못하는 것 같아요! 도와주시면 감사하겠습니다
강사님은 const {} = getProfileQuery.data || {};에서왼쪽 {} 안에 email, nickname 등 변수명 입력할 때마다 Profile.nickname 이런식으로 타입 바로바로 인식하는 것 같은데 전 인식을 못하는 것 같아요. 제가 코드를 잘못 작성한 부분이 있을까요?빨간줄 그어져 있어서 확인해보면 이렇게 뜨네요getProfileQuery.data를 콘솔에 찍으면위에 사진처럼 잘 나옵니다. 도와주시면 감사하겠습니다!!
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
Ios 시뮬레이터 아이콘
안녕하세요 강사님 강의 정말 잘 듣고있습니다. 안드로이드에선 잘되는데 IOS에선 아이콘이 잘뜨지 않아서요처음 부터 다시 해보기도하고 캐시도 삭제하고 실행해도 제대로 아이콘이 나오질 않네요x-code에서 다시 빌드할때 에러는아니고 워닝이 690개로 너무 많이 뜨는데 그거랑도 상관이 있을까요? 이유를 몰라서 해보다가 질문남깁니다.
-
미해결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() { } }); 다른 찜하기 질문에서키를가지고 호출하지 않해서라고 하신걸 봤었는데,위와 같은 경우에는 클라이언트 서버에서 쿼리키를 가지고 호출했는데 데이터가 잘 안내려오는 것을 확인했습니다.찜을 눌렀을때찜을 누르고 해당글 상세로 이동했을때이러한 경우에는 찜하기를 누르고 추가적인 작업이 필요한지 궁금합니다. 예를들면 찜하기를 누르고 해당 쿼리키의 데이터를 호출해야한다는지... 혹은 제가 잘못호출한것이라면 알려주시면 감사하겠습니다.
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
안녕하세요. 추가 기능 관련 문의드립니다.
안녕하세요. Kyo 강사님최근 강의를 듣고 이것 저것 기능 추가하고 있습니다.#1 엑세스 토큰이 만료 된후에 재인증을 받아야 하는데요.1시간 지나면 인증이 풀리고 로그 아웃하고 다시 받아야 하네요..찾아보니 인터셉터라는 기능을 써서 401이냐 419 코드에 따라 처리 해야 하는 것 같은데요.401 일때는 리프래쉬 토큰이 있으면 자동으로 재인증 받고 처리419 코드 일 경우 메인 스크린으로 리다이렉트 되어야 할 것 같은데요.잘 안되네요.. 핵심 코드만 보고 적용 하기에는 아직 무리가 있습니다. #2. 구글 메세지 push 기능도 추가하고 있는데요.. FCM서버에서 메세지 전송 / 앱에서 받기 가르침 부탁드립니다. 혹시 참고 강의가 있을까요? 감사합니다.
-
미해결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문을 그냥 통과해버립니다.혹시 제가 놓치는 부분이 있는걸까요?
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
ios 시뮬레이터에서 비밀번호(or 비밀번호 확인) 재 포커스 후 입력시 이전값이 초기화 됩니다
안드로이드에서는 초기화 문제 없는데 ios에서만 그러네요..ㅠ일단 오류 확인에 있어 필요하다는 코드 위주로 올렸는데 더 필요한 코드가 있다면 알려주세요! SignupScreen.tsximport {SafeAreaView, StyleSheet, Text, TextInput, View} from 'react-native'; import React, {useRef} from 'react'; import InputField from '../../components/InputField'; import useForm from '../../hooks/useForm'; import CustomButton from '../../components/CustomButton'; import {validateSignup} from '../../utils'; function SignupScreen() { const passwordRef = useRef<TextInput | null>(null); const passwordConfirmRef = useRef<TextInput | null>(null); const signup = useForm({ initialValue: {email: '', password: '', passwordConfirm: ''}, validate: validateSignup, }); const handleSubmit = () => { console.log(signup.values); }; return ( <SafeAreaView style={styles.container}> <View style={styles.inputContainer}> <InputField autoFocus placeholder="이메일" error={signup.errors.email} touched={signup.touched.email} inputMode="email" returnKeyType="next" // return키가 아닌 다른 키 옵션을 주고 싶을때 blurOnSubmit={false} // next와 같은 키를 눌러도 키가 닫히지 않음 (false) onSubmitEditing={() => passwordRef.current?.focus()} // next키를 눌렀을 때 다음 input으로 이동 {...signup.getTextInputProps('email')} /> <InputField key="password" ref={passwordRef} placeholder="비밀번호" textContentType="oneTimeCode" // ios에서 강력한 암호 뜨게 하는걸 방지 error={signup.errors.password} touched={signup.touched.password} secureTextEntry returnKeyType="next" blurOnSubmit={false} onSubmitEditing={() => passwordConfirmRef.current?.focus()} {...signup.getTextInputProps('password')} /> <InputField key="passwordConfirm" ref={passwordConfirmRef} placeholder="비밀번호 확인" textContentType="oneTimeCode" error={signup.errors.passwordConfirm} touched={signup.touched.passwordConfirm} secureTextEntry onSubmitEditing={handleSubmit} {...signup.getTextInputProps('passwordConfirm')} /> </View> <CustomButton label="회원가입" /> </SafeAreaView> ); } export default SignupScreen; const styles = StyleSheet.create({ container: { flex: 1, margin: 30, }, inputContainer: { gap: 20, marginBottom: 30, }, }); useForm.ts// 리액트 hook form 같은 리액트 라이브러리를 사용해도 괜찮지만 // 복잡하고 많은 input을 다루지 않기 때문에 직접 구현함 import {useEffect, useState} from 'react'; interface UseFormProps<T> { initialValue: T; validate: (values: T) => Record<keyof T, string>; } function useForm<T>({initialValue, validate}: UseFormProps<T>) { const [values, setValues] = useState(initialValue); const [touched, setTouched] = useState<Record<string, boolean>>({}); const [errors, setErrors] = useState<Record<string, string>>({}); const handleChangeText = (name: keyof T, text: string) => { setValues({ ...values, [name]: text, }); }; const handleBlur = (name: keyof T) => { setTouched({ ...touched, [name]: true, }); }; const getTextInputProps = (name: keyof T) => { const value = values[name]; const onChangeText = (text: string) => handleChangeText(name, text); const onBlur = () => handleBlur(name); return {value, onChangeText, onBlur}; }; useEffect(() => { const newErrors = validate(values); setErrors(newErrors); }, [validate, values]); useEffect(() => { console.log('values값 뭐뭐 들어왔는지 체크용: ', values); }, [values]); return {values, errors, touched, getTextInputProps}; } export default useForm; validate.tstype UserInformation = { email: string; password: string; }; function validateUser(values: UserInformation) { const errors = { email: '', password: '', }; // email이 올바른 이메일인지 검사하는 정규표현식 // 이 테스트를 통과하지 못할경우 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) { errors.email = '올바른 이메일 형식이 아닙니다.'; } if (!(values.password.length >= 8 && values.password.length < 20)) { errors.password = '비밀번호는 8~20자 사이로 입력해주세요.'; } return errors; } function validateLogin(values: UserInformation) { return validateUser(values); } function validateSignup(values: UserInformation & {passwordConfirm: string}) { const errors = validateUser(values); const signupErrors = {...errors, passwordConfirm: ''}; if (values.password !== values.passwordConfirm) { signupErrors.passwordConfirm = '비밀번호가 일치하지 않습니다.'; } return signupErrors; } export {validateLogin, validateSignup}; InputField.tsximport { Dimensions, Pressable, StyleSheet, Text, TextInput, TextInputProps, View, } from 'react-native'; import React, {ForwardedRef, forwardRef, useRef} from 'react'; import {colors} from '../constants'; import {mergeRefs} from '../utils'; interface InputFieldProps extends TextInputProps { disabled?: boolean; error?: string; touched?: boolean; } // 디바이스의 높이 가져옴 const deviceHeight = Dimensions.get('screen').height; const InputField = forwardRef( ( {disabled = false, error, touched, ...props}: InputFieldProps, ref?: ForwardedRef<TextInput>, ) => { const inputRef = useRef<TextInput | null>(null); // input부분이 아닌 error msg(View컴포넌트) 부분을 클릭해도 input에 포커스를 해주기 위함 (Pressable 컴포넌트로 먼저 감싸주고나서!) const handlePressInput = () => { inputRef.current?.focus(); }; return ( <Pressable onPress={handlePressInput}> <View style={[ styles.container, disabled && styles.disabled, touched && Boolean(error) && styles.inputError, ]}> <TextInput ref={ref ? mergeRefs(inputRef, ref) : inputRef} // false일 때는 편집 가능 editable={!disabled} placeholderTextColor={colors.GRAY_500} style={[styles.input, disabled && styles.disabled]} {...props} autoCapitalize="none" // 자동 대문자 방지 spellCheck={false} autoCorrect={false} /> {/* error 메세지가 있을 때만 해당 컴포넌트 표시하기 위해 string 타입을 Boolean으로 변경 */} {touched && Boolean(error) && ( <Text style={styles.error}>{error}</Text> )} </View> </Pressable> ); }, ); export default InputField; const styles = StyleSheet.create({ container: { borderWidth: 1, borderColor: colors.GRAY_200, padding: deviceHeight > 700 ? 15 : 10, }, input: { fontSize: 16, color: colors.BLACK, padding: 0, }, disabled: { backgroundColor: colors.GRAY_200, color: colors.GRAY_700, }, inputError: { borderWidth: 1, borderColor: colors.RED_300, }, error: { color: colors.RED_500, fontSize: 12, paddingTop: 5, }, }); common.tsimport {ForwardedRef} from 'react'; // input component를 만들어서 사용할 때 해당 컴포넌트에서 사용하는 ref와 외부에서 주입하는 ref를 둘 다 사용 가능하게 함 function mergeRefs<T>(...refs: ForwardedRef<T>[]) { return (node: T) => { refs.forEach(ref => { if (typeof ref === 'function') { ref(node); } else if (ref) { ref.current = node; } }); }; } export {mergeRefs}; 찾아보기도 하고 챗gpt에도 물어보고 해봤지만 도저히 모르겠어서 질문 남겨봅니다! 일단 회원가입 스크린 관련해서만 올리긴 했는데 로그인 화면에서도 마찬가지로 비밀번호 부분만 그런 현상이 발생하네요..ㅠ혹시나 해서 비밀번호 input 속성으로 secureTextEntry 전달하는거 없애보니까 이런 문제가 없긴한데 그렇다고 이걸 전달 안할 수도 없고..어찌해야 할까요?? 아니면 이건 코드가 아닌 ios 자체 문제일까요??
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
ANDROID STUDIO 버전
현재 ANDROID STUDIO가 Jellyfish 버전으로 다운로드 되는데 상관없을까요?
-
미해결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> </> ); }
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
Using the first of multiple matching destinations 에러 해결방법!
--- xcodebuild: WARNING: Using the first of multiple matching destinations: { platform:iOS Simulator, id:D6FC302F-1EC1-4258-8FD4-4AA0049970DB, OS:17.5, name:iPhone 15 } { platform:iOS Simulator, id:D6FC302F-1EC1-4258-8FD4-4AA0049970DB, OS:17.5, name:iPhone 15 } ** BUILD FAILED ** 이러한 에러가 발생하며, 빌드가 되지 않습니다. 해결 방법을 알고 싶습니다!!! 검색해도 정보가 많지 않습니다 ㅠ
-
해결됨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;
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
로그아웃할때 /server/uploads/index.html' 파일을 찾을수없다고 에러가 나오는데 어떻게해야할까요?
로그아웃 버튼을 클릭하면 Error: ENOENT: no such file or directory, stat '/Users/user/Documents/code/MatzipApp/server/uploads/index.html'이런 에러가 나오고있어서 uploads폴더에 Index.html파일을 추가하니까 에러는 안나오는데 index.html에 아무것도 안작성해도되나요?
-
미해결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> ); }
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
"실행 오류" yarn start 실행후
r - reload the appd - open developer menui - run on iOSa - run on Androidinfo Opening the app on Android...info JS server already running.info Launching emulator...info Installing the app...> Task :gradle-plugin:compileKotlin UP-TO-DATE> Task :gradle-plugin:compileJava NO-SOURCE> Task :gradle-plugin:pluginDescriptors UP-TO-DATE> Task :gradle-plugin:processResources UP-TO-DATE> Task :gradle-plugin:classes UP-TO-DATE> Task :gradle-plugin:jar UP-TO-DATE> Task :gradle-plugin:inspectClassesForKotlinIC UP-TO-DATE> Task :app:buildCodegenCLI SKIPPED> Task :app:generateCodegenSchemaFromJavaScript SKIPPED> Task :app:generateCodegenArtifactsFromSchema SKIPPED> Task :app:generatePackageList> Task :app:preBuild> Task :app:preDebugBuild> Task :app:compileDebugAidl NO-SOURCE> Task :app:compileDebugRenderscript NO-SOURCE> Task :app:generateDebugBuildConfig UP-TO-DATE> Task :app:javaPreCompileDebug UP-TO-DATE> Task :app:checkDebugAarMetadata UP-TO-DATE> Task :app:generateDebugResValues UP-TO-DATE> Task :app:mapDebugSourceSetPaths UP-TO-DATE> Task :app:generateDebugResources UP-TO-DATE> Task :app:mergeDebugResources UP-TO-DATE> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE> Task :app:extractDeepLinksDebug UP-TO-DATE> Task :app:processDebugMainManifest UP-TO-DATE> Task :app:processDebugManifest UP-TO-DATE> Task :app:processDebugManifestForPackage UP-TO-DATE> Task :app:processDebugResources UP-TO-DATE> Task :app:compileDebugJavaWithJavac UP-TO-DATE> Task :app:mergeDebugShaders UP-TO-DATE> Task :app:compileDebugShaders NO-SOURCE> Task :app:generateDebugAssets UP-TO-DATE> Task :app:mergeDebugAssets UP-TO-DATE> Task :app:compressDebugAssets UP-TO-DATE> Task :app:processDebugJavaRes NO-SOURCE> Task :app:mergeDebugJavaResource UP-TO-DATE> Task :app:checkDebugDuplicateClasses UP-TO-DATE> Task :app:desugarDebugFileDependencies UP-TO-DATE> Task :app:mergeExtDexDebug UP-TO-DATE> Task :app:mergeLibDexDebug UP-TO-DATE> Task :app:dexBuilderDebug UP-TO-DATE> Task :app:mergeProjectDexDebug UP-TO-DATE> Task :app:mergeDebugJniLibFolders UP-TO-DATE> Task :app:mergeDebugNativeLibs UP-TO-DATE> Task :app:stripDebugDebugSymbols UP-TO-DATE> Task :app:validateSigningDebug UP-TO-DATE> Task :app:writeDebugAppMetadata UP-TO-DATE> Task :app:writeDebugSigningConfigVersions UP-TO-DATE> Task :app:packageDebug UP-TO-DATE> Task :app:createDebugApkListingFileRedirect UP-TO-DATE> Task :app:installDebug FAILED38 actionable tasks: 2 executed, 36 up-to-dateinfo 💡 Tip: Make sure that you have set up your development environment correctly, by running react-native doctor. To read more about doctor command visit: https://github.com/react-native-community/cli/blob/main/packages/cli-doctor/README.md#doctorFAILURE: Build failed with an exception.* What went wrong:Execution failed for task ':app:installDebug'.> com.android.builder.testing.api.DeviceException: No connected devices!* Try:> Run with --stacktrace option to get the stack trace.> Run with --info or --debug option to get more log output.> Run with --scan to get full insights.* Get more help at https://help.gradle.orgBUILD FAILED in 5sinfo Run CLI with --verbose flag for more details.|후 실행이 안됩니다..어떻게 해야되나요?
-
해결됨Next + React Query로 SNS 서비스 만들기
spring boot를 사용한 social login
Next.js와 Spring boot를 사용해서 소셜 로그인을 추가하려고 합니다.소셜 로그인의 경우 코드와 토큰을 프론트에서 관리하지 않는 것이 좋다고 하여 백엔드에서 모든 로직을 처리하고 있습니다. 현재 프론트에서 window.location.href = "http://localhost:8080/oauth2/authorization/google";위와 같은 경로로 백엔드로 소셜 로그인 요청을 보내고 백엔드에서는 소셜 로그인을 진행한 뒤에 유저 정보를 생성하고 JWT를 생성한 뒤 응답의 헤더에 쿠키 형태로 JWT를 넣어 response.sendRedirect("http://localhost:3000/auth/social");로 리다이렉트하고 있습니다. 프론트에서는 /auth/social 페이지에서 쿠키에 있는 JWT를 꺼내 유저 정보를 가져오는 API를 다시 호출해 유저 정보를 가져오고 있습니다.이 후 auth.js 라이브러리를 통한 session 관리를 하고자 하는데 session을 업데이트 하려면 어떻게 해야 하나요..?(session.user가 있는지 없는지를 통한 로그인 체크를 위해 사용한다고 이해했습니다.) 서버 컴포넌트에서 import { auth } from "@/auth"; 를 통해 가져온 session에 데이터를 넣어보았지만 데이터가 제대로 들어가지 않는 것 같아 조언을 구해봅니다..