묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
리액트 네이티브 스타일 라이브러리 사용에 대한 궁금증입니다.
일반적인 웹 개발의 경우, module css, tailwind css, styled-components, emotion, style-x, module css 등 정말 다양한 대체안들이 많은것으로 알고 있습니다.리액트 네이티브도, tailwind css나, styled-components, emotion을 사용할 수 있는 것으로 알고 있는데, 현업이나 다양한 프로젝트에서도, react-native에서 제공해주는 style-sheet를 사용하는 편인가요? 보통 리액트에서, 스타일을 적용할 떄 일반적인 컴포넌트 코드와, 스타일 코드를 따로 분리해서 적용시켜주는 방식을 많이 쓰곤하는데, 리액트 네이티브는 이렇게 한 파일에 사용하는 방법밖에 없는지 이 부분도 궁금합니다!
-
미해결Next + React Query로 SNS 서비스 만들기
router.replace() 질문
팔로우 버튼을 누르면 로그인 화면으로 가도록 기능을 넣어봤습니다.FollowRecommend 컴포넌트에서 router.replace('/i/flow/login'); 를 하면 notfound 페이지가 뜹니다. (당연히 router.replace('/home')을 해도 마찬가지 입니다.<- /i/flow/login으로 가기때문)그러나 이 상태에서 새로고침하면 로그인 화면 잘 뜹니다.이렇게 root 경로로는 잘 넘어가서 일단은 메인화면으로 해놨는데, 왜 이런 현상이 발생하는 걸까요..? 감사합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
is not valid JSON 에러 ..!
import { http, HttpResponse } from "msw"; const testData = [ { result: "success", userId: 2, nickname: "닉네임", favoriteCount: 223, viewCount: 336, position: "Backend", userFileUrl: "/Users/user/Desktop/pictures/userPicture.jpg", year: "경력없음", techStack: "react, java, ---", softSkill: "소통, 적극성, ---", links: "http://블로그주소", alarmStatus: true, content: "안녕하세요 구인 중입니다", }, ]; export const handlers = [ http.post("/api/post", () => { return HttpResponse.text(JSON.stringify("ok")); }), http.get("/api/get", ({ request }) => { console.log("request", request); return HttpResponse.json(testData); }), ]; export default handlers;"use client"; import { redirect } from "next/navigation"; export default function Home() { const handleClick = () => { fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/get`, { method: "get", }) .then((res) => console.log("res", res.json())) // JSON 형식으로 변환된 데이터를 가져옴 .then((data) => console.log("res?", data)); // JSON 데이터를 로그에 출력 }; return ( <> <div> <button onClick={handleClick}>test btn</button> <p>계정을 생성하세요</p> <form> <div> <> <label htmlFor="id">아이디</label> <input type="text" id="id" name="id" required /> </> <> <label htmlFor="name">이름</label> <input type="text" id="name" name="name" required /> </> <button type="submit">가입하기</button> </div> </form> </div> </> ); } browser.ts, handlers.ts, http.ts, MSWComponent.tsx, server.ts 는 제로초님과 다 동일하게 설정하였고 get부분에서 위와같이 하였는데 Unexpected token '<', "<!DOCTYPE "... is not valid JSON 라는 에러가 계속 발생합니다 ㅠㅠ 어떤부분이 문제일까요.. !console.log("res", res.json())) 이부분에서 res만 콘솔로 확인하면 이렇게 나옵니다..!
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
영상 재생은 되는데 영상이 안보입니다
코드 버그는 아닌데 인프런 문제인지 갑자기 선생님 강의 영상은 안보이고 재생만 됩니다.. MAC OS (Chrome,Safari 둘다 영상이 안보여요)
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
useEffect 의존성 질문
장소 등록 후 MapHomeScreen으로 돌아가는 코드입니다.useEffect의 의존성 배열이 없이 사용하셨는데어떤 이유가 있으신가요?!빈 배열이나, 의존성을 안 넣으신 이유가 궁금합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
cookies().toString() 질문
getUserServer headers 내부 Cookie에 cookies().toString()을 통해 넣어주셨는데 쿠키 안에 있는 모든 값들을 통으로 toString 변환하여 넣어준 이유가 어떤 이유일까요?
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
@react-navigation/drawer 접근 에러
강의에서 알려주신대로 babel.config.js 에 plugins: ['react-native-reanimated/plugin'], 추가.npx react-native start --reset-cache했지만 에러가 발생합니다.에러는 node_modules에 @react-navigation/drawer 를 못찾는다고 하는데 왼쪽 디렉토리 보시면 해당 폴더는 있습니다.방법이 있을까요?
-
미해결Next + React Query로 SNS 서비스 만들기
답글 api 관련 질문
[username]/status/[id] 페이지에 접근했을 때해당 게시글의 답글을 리액트 쿼리의 인피니트 스크롤을 통해 가져오는데제로초님이 직접 배포하신 z.nordbird.com도 그렇고 제가 강의를 통해 만들고 있는 페이지에 인피니트 스크롤을 적용해봐도리액트 쿼리를 통해 가져오는 데이터가 계속 중복이 되는데(cursor 값이 증가하면서 다음 답글을 가지고 오지 못하고 어떤 cursor를 기준으로 이보다 작은 값을 가지는 답글만 fetching 됨)코드상의 오류는 아니고 백엔드 설계가 그렇게 되어 있는 거죠? 답글 무한 스크롤 결과(pageParam이 96을 넘어가지 못함)답글 인피니트 스크롤 부분 코드는 배포된 페이지 깃에 올라온 내용과 동일합니다.
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
회원가입 버튼 클릭 시 아무런 반응이 없습니다..
강사님..제가 어딘가 빠트린 부분이 있는 것 같은데.. 에러도 안나서 도저히 못찾고 있습니다..ㅠㅠ백엔드와 postgresql 서버 모두 실행중입니다..이런 경우 오류를 확인할 수 있는 꿀팁 같은게 있을까요?!혹시 몰라서 깃헙 공유드립니다..https://github.com/KMSKang/react
-
미해결Next + React Query로 SNS 서비스 만들기
provider 가 안되요...
안녕하세요 provider 따라해보는중 다른데는 잘되는데 안되는곳이 있어 문의드립니다. "use client" import {createContext, ReactNode, useState} from "react"; const currentYear = new Date().getFullYear(); const currentMonth = new Date().getMonth() + 1; export const FilterContext = createContext({ year: currentYear, setYear: (value: number) => {}, month: currentMonth.toString().padStart(2, "0"), setMonth: (value: string) => {}, }); type Props = { children: ReactNode }; export default function FilterProvider({ children }: Props) { const [ year, setYear ] = useState(currentYear); const [ month, setMonth ] = useState(currentMonth.toString().padStart(2, "0")); return ( <FilterContext.Provider value={{ year, setYear, month, setMonth }}> {children} </FilterContext.Provider> ); } const { year, setYear, month, setMonth } = useContext(FilterContext); const [selectBox2, setSelectBox2] = useState(false); const [selectBox3, setSelectBox3] = useState(false); {Array.from({ length: year - 2019 }, (_, index) => year - index).map((item) => ( <li key={item} onClick={() => { setSelectBox2(false); setYear(item); console.log(item); }}>{item}</li> ))} {Array.from({ length: 12 }, (_, index) => (index + 1).toString().padStart(2, "0")).map((item) => ( <li key={item} onClick={() => { setSelectBox3(false); console.log("month",item); setMonth(item); }}>{item}</li> ))}yser, month 가 업데이트가 될질 않아서요. 콘솔에는 찍히는데 값이 변경이 안되는 이유를 모르겠습니다.
-
미해결Next + React Query로 SNS 서비스 만들기
useMutation의 onError에 관하여 질문있습니다.
안녕하세요. 제로초님 항상 좋은 강의 감사드립니다. 해당 강의에서 팔로우 api에 의도적으로 에러를 일으켜 보았는데, onError에서 캐치하지 못하는 것을 확인했습니다. const follow = useMutation({ mutationFn: (userId: string) => { // console.log('follow', userId); return fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/users/${userId}/follow`, { credentials: 'include', method: 'post', } ); }, ... onError(error, userId: string) { ... } 그래서 밑에와 같이 바꾸어보니 onError에서 캐치하여 롤백이 정상적으로 되었습니다. mutationFn: async (userId: string) => { const response = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/users/${userId}/follow`, { credentials: 'include', method: 'post', } ); if (!response.ok) { throw new Error('팔로우 실패'); } return response; },혹시 본 강의 코드대로 하면 onError에서 원래 캐치해야하는데 안 되는 것인지 아니면 수강생들에게 숙제로 낸 것인지 궁금합니다.
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
marker.d.ts 에러 문제
import {LatLng, MapMarkerProps} from 'react-native-maps'; // marker props의 타입을 변경 declare module 'react-native-maps' { export interface MyMapMarkerProps extends MapMarkerProps { coordinate?: LatLng; } } Interface 'MyMapMarkerProps' incorrectly extends interface 'MapMarkerProps'.Type 'MyMapMarkerProps' is not assignable to type '{ anchor?: Point | undefined; calloutAnchor?: Point | undefined; calloutOffset?: Point | undefined; centerOffset?: Point | undefined; coordinate: LatLng; ... 22 more ...; zIndex?: number | undefined; }'.Types of property 'coordinate' are incompatible.Type 'LatLng | undefined' is not assignable to type 'LatLng'.Type 'undefined' is not assignable to type 'LatLng'.ts(2430)이런 에러가 발생합니다. 강의와 동일하게 했는데 그렇습니다! 아래 질문주신분 답변에 응답이 없어서 다시 질문드리는 점 양해부탁드립니다!
-
해결됨Next + React Query로 SNS 서비스 만들기
무한 스크롤 prefetch SSR 로그인 쿠키 관련 질문
안녕하세요 제로초님,api/users/{id} 에서 Followers 데이터는제가 상대방을 팔로잉 했고 로그인 정보 쿠키가 잘 전달되면상대방의 팔로워 수와 관계없이 제가 팔로우 했다면 저의 userId만 배열에 담겨서 돌아오기 때문에 api/posts/followings 혹은 api/posts/recommends의Comments 데이터도 이와 동일할 것이라고 생각했습니다. 추천 포스트는 prefetch를 진행하고(Suspense를 적용하지 않음)팔로잉 포스트는 SSR을 할 때 서버에서 로그인 여부를 판단하는 쿠키를 함께 보내야 하기 때문에prefetch에서는 next의 cookies를 사용해서 쿠키를 전달했습니다.하지만 Comments 배열에 로그인 유저가 아닌 다른 유저의 id도 함께 날라옵니다. 추천 포스트가 아닌 팔로잉 포스트는 useSuspenseInfiniteQuery를 사용했는데로그인 유저의 id만 잘 전달받았습니다. 이게 Suspense 내부에 있어서 그런가 싶어서Suspense를 걷어내고 prefetch만 사용했을 때도동일하게 로그인 유저가 아닌 다른 유저의 id도 확인할 수 있었습니다.왜 로그인 쿠키를 보내는데도 다른 유저의 정보도 날라오는지 궁금합니다.어디가 잘못된 것일까요?useSuspenseInfiniteQuery를 사용한 팔로잉 포스트는 추천 포스트와 어떤점이 달라서 정상 작동한 것일까요?추가) 이 글 작성하고 추천 포스트에도 useSuspenseInfiniteQuery를 적용했는데 팔로잉 포스트처럼 안 나오고 이전과 동일하게 다른 유저의 id도 함께 날라오는데 왜 그럴까요? 추천 포스트 결과현재 로그인 한 유저의 id는 'jihwan3'Comments에 'jihwan2'도 함께 날라오는중 팔로잉 포스트 결과현재 로그인 한 유저의 id는 'jihwan3'Comments에 'jihwan3'만 날라오는 중Hearts도 jihwan3가 좋아요를 안 눌러서 비어있는 모습page.tsximport { Suspense } from "react"; import { auth } from "@/auth"; import { Container } from "./_component/styled"; import HomeTab from "./_component/HomeTab"; import HomeTabProvider from "./_component/HomeTabProvider"; import PostForm from "./_component/PostForm"; import SuspenseDecider from "./_component/SuspenseDecider"; import Loading from "../_component/LoadingUI"; export default async function Home() { const session = await auth(); return ( <Container> <HomeTabProvider> <HomeTab></HomeTab> <PostForm me={session} /> <Suspense fallback={<Loading />}> {/* @ts-expect-error Server Component */} <SuspenseDecider /> </Suspense> </HomeTabProvider> </Container> ); } SuspenseDecider.tsximport { QueryClient, HydrationBoundary, dehydrate } from "@tanstack/react-query"; import getRecommendPosts from "../_lib/getRecommendPosts"; import getRecommendPostsServer from "../_lib/getRecommendPostsServer"; import PostDisplay from "./PostDisplay"; export default async function SuspenseDecider() { const queryClient = new QueryClient(); await queryClient.prefetchInfiniteQuery({ queryKey: ["posts", "recommends"], queryFn: getRecommendPostsServer, initialPageParam: 0, getNextPageParam: (lastPage, pages) => lastPage.at(-1)?.postId, pages: 1, }); return ( <HydrationBoundary state={dehydrate(queryClient)}> <PostDisplay /> </HydrationBoundary> ); }getRecommendPostsServer.tsimport { QueryFunction } from "@tanstack/query-core"; import { Post as IPost } from "@/model/Post"; import { cookies } from "next/headers"; type Prop = { pageParam: number; }; const getRecommendPostsServer: QueryFunction<IPost[], [_1: string, _2: string], number> = async ({ pageParam }: Prop) => { const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/posts/recommends?cursor=${pageParam}`, { method: "get", headers: { Cookie: cookies().toString(), }, }); if (response.ok) return response.json(); else throw new Error(); }; export default getRecommendPostsServer;postDisplay.tsx"use client"; import { use } from "react"; import { TabContext } from "./HomeTabProvider"; import RecommendPosts from "./RecommendPosts"; import FollowingPosts from "@/app/(afterLogin)/home/_component/FollowingPosts"; export default function PostDisplay() { const { selectedMenu } = use(TabContext); if (selectedMenu === "recommend") { return <RecommendPosts />; } return <FollowingPosts />; } RecommendPosts.tsx"use client"; import { useEffect, Fragment } from "react"; import { InfiniteData, useInfiniteQuery } from "@tanstack/react-query"; import { useInView } from "react-intersection-observer"; import getRecommendPosts from "../_lib/getRecommendPosts"; import Post from "../../_component/Post"; import { Post as IPost } from "@/model/Post"; export default function RecommendPosts() { const { isFetching, fetchNextPage, hasNextPage, data } = useInfiniteQuery<IPost[], Object, InfiniteData<IPost[]>, [_1: string, _2: string], number>({ queryKey: ["posts", "recommends"], queryFn: getRecommendPosts, initialPageParam: 0, getNextPageParam: (lastPage) => lastPage.at(-1)?.postId, }); const { ref, inView } = useInView({ /* Optional options */ threshold: 0, delay: 100, }); useEffect(() => { if (inView) !isFetching && hasNextPage && fetchNextPage(); }, [inView, isFetching, hasNextPage, fetchNextPage]); if (!data) return null; // 이거 에러는...? type error인데.. -> InfiniteData로 해결 return ( <> {data.pages.map((ele: IPost[], idx: number) => ( <Fragment key={idx}> {ele.map((post: IPost) => ( <Post key={post.postId} post={post}></Post> ))} </Fragment> ))} <div ref={ref} style={{ height: "50px" }}></div> </> ); } getRecommendPost.tsimport { QueryFunction } from "@tanstack/query-core"; import { Post as IPost } from "@/model/Post"; type Prop = { pageParam: number; }; const getRecommendPosts: QueryFunction<IPost[], [_1: string, _2: string], number> = async ({ pageParam }: Prop) => { const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/posts/recommends?cursor=${pageParam}`, { method: "get", credentials: "include", }); if (response.ok) return response.json(); else throw new Error(); }; export default getRecommendPosts; FollowingPosts.tsx"use client"; import { Fragment, useEffect } from "react"; import { useSuspenseInfiniteQuery, InfiniteData } from "@tanstack/react-query"; import { useInView } from "react-intersection-observer"; import getFollowingPosts from "../_lib/getFollowingPosts"; import Post from "../../_component/Post"; import { Post as IPost } from "@/model/Post"; export default function FollowingPosts() { const { isFetching, fetchNextPage, hasNextPage, data } = useSuspenseInfiniteQuery<IPost[], Object, InfiniteData<IPost[]>, [_1: string, _2: string], number>({ queryKey: ["posts", "followings"], queryFn: getFollowingPosts, initialPageParam: 0, getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => lastPage.at(-1)?.postId, }); const { ref, inView } = useInView({ threshold: 0, delay: 100, }); useEffect(() => { if (inView) !isFetching && hasNextPage && fetchNextPage(); }, [inView, fetchNextPage, hasNextPage, isFetching]); if (!data) return null; return ( <> {data.pages.map((ele: IPost[], idx: number) => ( <Fragment key={idx}> {ele.map((ele) => ( <Post key={ele.postId} post={ele}></Post> ))} </Fragment> ))} <div ref={ref} style={{ height: "100px" }}></div> </> ); }
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
react-native-vector-icons 에러 관련 질문
import React, {useRef, useState} from 'react'; import {Alert, Pressable, StyleSheet, View} from 'react-native'; import MapView, { Callout, LatLng, LongPressEvent, Marker, PROVIDER_GOOGLE, } from 'react-native-maps'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import Ionicons from 'react-native-vector-icons/Ionicons'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import {StackNavigationProp} from '@react-navigation/stack'; import {alerts, colors, mapNavigations} from '@/constants'; import {CompositeNavigationProp, useNavigation} from '@react-navigation/native'; import {DrawerNavigationProp} from '@react-navigation/drawer'; import {MainDrawerParamList} from '@/navigations/drawer/MainDrawerNavigator'; import {MapStackParamList} from '@/navigations/stack/MapStackNavigator'; import useUserLocation from '@/hooks/useUserLocation'; import usePermission from '@/hooks/usePermission'; import mapStyle from '@/style/mapStyle'; import CustomMarker from '@/components/CustomMarker'; type Navigation = CompositeNavigationProp< StackNavigationProp<MapStackParamList>, DrawerNavigationProp<MainDrawerParamList> >; const MapHomeScreen = () => { const inset = useSafeAreaInsets(); const navigation = useNavigation<Navigation>(); const mapRef = useRef<MapView | null>(null); const {userLocation, isUserLocationError} = useUserLocation(); const [selectLocation, setSelectLocation] = useState<LatLng | null>(); usePermission('LOCATION'); const handleLongPressMapView = ({nativeEvent}: LongPressEvent) => { setSelectLocation(nativeEvent.coordinate); }; const handlePressAddPost = () => { // if (!selectLocation) { return Alert.alert( alerts.NOT_SELECTED_LOCATION.TITLE, alerts.NOT_SELECTED_LOCATION.DESCRIPTION, ); } navigation.navigate(mapNavigations.ADD_POST, { location: selectLocation, }); // 다시 뒤로 돌아왔을때는 위치를 초기화 setSelectLocation(null); }; const handlePressUserLocation = () => { if (isUserLocationError) { // 에러 메시지 표시 return; } mapRef.current?.animateToRegion({ latitude: userLocation.latitude, longitude: userLocation.longitude, latitudeDelta: 0.0922, longitudeDelta: 0.0421, }); }; // 1. 나의 위치 구하고. (geolocation) // 2. 지도를 그곳으로 이동. return ( <> <MapView ref={mapRef} style={styles.container} provider={PROVIDER_GOOGLE} showsUserLocation followsUserLocation showsMyLocationButton={false} customMapStyle={mapStyle} onLongPress={handleLongPressMapView}> <CustomMarker color="RED" score={3} coordinate={{ latitude: 37.52016541, longitude: 127.127520372, }} /> <CustomMarker color="BLUE" coordinate={{ latitude: 37.550165411, longitude: 127.127520372, }} /> {selectLocation && ( <Callout> <Marker coordinate={selectLocation} /> </Callout> )} </MapView> <Pressable style={[styles.drawerButton, {top: inset.top || 20}]} onPress={() => navigation.openDrawer()}> <Ionicons name="menu" color={colors.WHITE} size={25} /> </Pressable> <View style={styles.buttonList}> <Pressable style={styles.mapButton} onPress={handlePressAddPost}> <MaterialIcons name="add" color={colors.WHITE} size={25} /> </Pressable> <Pressable style={styles.mapButton} onPress={handlePressUserLocation}> <MaterialIcons name="my-location" color={colors.WHITE} size={25} /> </Pressable> </View> </> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, drawerButton: { position: 'absolute', left: 0, top: 20, paddingVertical: 10, paddingHorizontal: 12, backgroundColor: colors.PINK_700, borderTopRightRadius: 50, borderBottomRightRadius: 40, shadowColor: colors.BLACK, shadowOffset: {width: 1, height: 1}, shadowOpacity: 0.5, // 안드는 elevation elevation: 4, }, buttonList: { position: 'absolute', bottom: 30, right: 15, }, mapButton: { backgroundColor: colors.PINK_700, marginVertical: 5, height: 48, width: 48, alignItems: 'center', justifyContent: 'center', borderRadius: 30, shadowColor: colors.BLACK, shadowOffset: {width: 1, height: 2}, shadowOpacity: 0.5, elevation: 2, }, }); export default MapHomeScreen; 제대로 import도 한 것 같고, 실제로 안드로이드에서는 아이콘 이모지가 매우 정상적으로 보이는데 ios에서만 아이콘이 다르게 보이거나, 아예 안뜹니다. drawer 이모지도 동일하게 동작합니다. 혹시 어떻게 해결하는지에 대해 알고싶습니다! cache clean도 진행해보았습니다!!!
-
해결됨Next + React Query로 SNS 서비스 만들기
로그인을 서버액션으로 구현해봤는데 궁금한게 있습니다.
로그인을 클라이언트 컴포넌트에서 서버액션을 통해 구현해봤는데, 궁금한게 생겨서 질문 드립니다.로그인 모달의 코드입니다. "use client"; import style from "@/app/(beforelogin)/_component/login.module.css"; import { ChangeEventHandler, FormEventHandler, useState } from "react"; import { redirect, useRouter } from "next/navigation"; import { signIn } from "next-auth/react"; import { useFormState, useFormStatus } from "react-dom"; import onSubmit from "../_lib/signin"; import BackButton from "./BackButton"; function showMessage(messasge: string | null | undefined) { if (messasge === "no_id") { return "아이디를 입력하세요."; } if (messasge === "no_password") { return "비밀번호를 입력하세요."; } return ""; } export default function LoginModal() { const [state, formAction] = useFormState(onSubmit, { message: null }); const { pending } = useFormStatus(); return ( <div className={style.modalBackground}> <div className={style.modal}> <div className={style.modalHeader}> <BackButton /> <div>로그인하세요.</div> </div> <form action={formAction}> <div className={style.modalBody}> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="id"> 아이디 </label> <input id="id" name="id" className={style.input} type="text" placeholder="" /> </div> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="password"> 비밀번호 </label> <input id="password" name="password" className={style.input} type="password" placeholder="" /> </div> </div> <div className={style.message}>{showMessage(state?.message)}</div> <div className={style.modalFooter}> <button className={style.actionButton} disabled={pending}> 로그인하기 </button> </div> </form> </div> </div> ); } 아래는 signin.ts 의 코드입니다."use server"; import { redirect } from "next/navigation"; import { signIn } from "@/auth"; const onSubmit = async (prevState: any, formData: FormData) => { if (!formData.get("id") || !(formData.get("id") as string)?.trim()) { return { message: "no_id" }; } if ( !formData.get("password") || !(formData.get("password") as string)?.trim() ) { return { message: "no_password" }; } let shouldRedirect = false; try { const response = await signIn("credentials", { username: formData.get("id"), password: formData.get("password"), redirect: false, }); console.log(response.status, "1"); console.log(response, "2"); shouldRedirect = true; } catch (err) { console.error(err); return { message: null }; } if (shouldRedirect) { redirect("/home"); // try/catch문 안에서 X } }; export default onSubmit; 이렇게 했을 때에, 콘솔이 이렇게 찍힙니다. 1. response가 http://localhost:3000/i/flow/login이렇게 날라오고, status는 그에 따라 undefined 입니다.응답이 이렇게 오면 response에 따른 status를 모르는데 에러처리를 어떻게 해야하나요 ?? 아래는 회원가입을 똑같이 서버액션으로 구현했을때에, signup.ts의 코드입니다."use server"; import { redirect } from "next/navigation"; import { signIn } from "@/auth"; const onSubmit = async (prevState: any, formData: FormData) => { if (!formData.get("id") || !(formData.get("id") as string)?.trim()) { return { message: "no_id" }; } if (!formData.get("name") || !(formData.get("name") as string)?.trim()) { return { message: "no_name" }; } if ( !formData.get("password") || !(formData.get("password") as string)?.trim() ) { return { message: "no_password" }; } if (!formData.get("image")) { return { message: "no_image" }; } let shouldRedirect = false; try { const response = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/users`, { method: "post", body: formData, credentials: "include", } ); console.log(response.status, "1"); if (response.status === 403) { return { message: "user_exists" }; } console.log(await response.json(), "2"); console.log(response, "3"); shouldRedirect = true; /*await signIn("credentials", { username: formData.get("id"), password: formData.get("password"), redirect: false, });*/ } catch (err) { console.error(err); return { message: null }; } if (shouldRedirect) { redirect("/home"); // try/catch문 안에서 X } }; export default onSubmit; 이 때에, console.log 의 결과입니다.response 응답 객체에서 await response.json()을 취했는데, OK로 나오는 이유가 궁금합니다. 로그인 구현에서 redirect를 false로 하면 client측에서 라우팅하는 것이고, true로 하면 서버쪽에서 리다이렉트 하는 것이라고 알고 있습니다.그런데 서버 액션으로 api 호출을 하는 것인데,redirect를 false로 하고 아래 redirect를 해주어도 redirect가 되는 것인지 궁금합니다. 그리고 redirect를 true로 하면이렇게 에러가 떠버립니다.서버액션으로 리다이렉트 시켜주는 것이라 생각해서 true옵션을 주었는데 에러가 왜 뜨는지 궁금합니다.바쁘실텐데 많은 질문 죄송합니다 ㅜㅜ
-
해결됨Next + React Query로 SNS 서비스 만들기
서버 컴포넌트에서 server action 사용 질문이 있습니다.
서버 컴포넌트에서 server actions 사용 중 네트워크 탭에서 응답이 안담기는 문제가 있어서 질문 드립니다.강의에 나와있는대로 코드를 따라했는데 뭐가 문제인지 잘 모르겠습니다.아래는 signupModal의 코드입니다.import style from "./signup.module.css"; import onSubmit from "../_lib/signup"; import BackButton from "@/app/(beforelogin)/_component/BackButton"; import { useFormState, useFormStatus } from "react-dom"; import { redirect } from "next/navigation"; function showMessage(messasge: string | null | undefined) { if (messasge === "no_id") { return "아이디를 입력하세요."; } if (messasge === "no_name") { return "닉네임을 입력하세요."; } if (messasge === "no_password") { return "비밀번호를 입력하세요."; } if (messasge === "no_image") { return "이미지를 업로드하세요."; } if (messasge === "user_exists") { return "이미 사용 중인 아이디입니다."; } return ""; } export default function SignupModal() { //const [state, formAction] = useFormState(onSubmit, { message: null }); //const { pending } = useFormStatus(); const formAction = async (formData: any) => { "use server"; let shouldRedirect = false; try { const response = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/users`, { method: "post", body: formData, credentials: "include", } ); console.log(response.status); console.log(await response.json(), "abc"); if (response.status === 403) { console.log("???"); return { message: "user_exists" }; } shouldRedirect = true; } catch (err) { console.log(err); } if (shouldRedirect) { redirect("/home"); // try/catch문 안에서 X } }; return ( <> <div className={style.modalBackground}> <div className={style.modal}> <div className={style.modalHeader}> <BackButton /> <div>계정을 생성하세요.</div> </div> <form action={formAction}> <div className={style.modalBody}> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="id"> 아이디 </label> <input id="id" name="id" className={style.input} type="text" placeholder="" required /> </div> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="name"> 닉네임 </label> <input id="name" name="name" className={style.input} type="text" placeholder="" required /> </div> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="password"> 비밀번호 </label> <input id="password" name="password" className={style.input} type="password" placeholder="" required /> </div> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="image"> 프로필 </label> <input id="image" name="image" required className={style.input} type="file" accept="image/*" /> </div> </div> <div className={style.modalFooter}> <button type="submit" className={style.actionButton}> 가입하기 </button> </div> </form> </div> </div> </> ); } 아래는 핸들러 세팅입니다.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", // }, // }); }),여기서 회원가입을 누를시에 console.log() 처리한 부분은 잘 찍히고 서버쪽에서 찍은 회원가입 콘솔도 잘 찍히는 모습입니다.네트워크탭에서 페이로드는 잘 담겼는데, 응답이 없습니다.회원가입 누를 때 콘솔이 찍히는 것으로 보아서 포트도 9090으로 제대로 열려있고, 403응답이 오는 것으로 보아 핸들러 쪽은 제대로 작동하는 것 같습니다.그리고 서버액션 쪽 콘솔 "???"가 찍히는 것으로 보아서 status도 403으로 잘 오는 것 같은데 리턴 메시지가 제대로 안되는 것인지 응답이 왜 없는 것인지 궁금합니다.
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
AOS 플랫폼 문제 [해결]
안드로이드 실행시마다, GooglePlay Services keeps stopping이라는 현상이 매초 간격으로 발생하고, 제대로 지도 또한 실행하지 않는 것 같습니다. 혹시 이런 문제를 해결할려면, 어떻게 해야할까요?ios는 정상적으로 실행됩니다!AOS 같은 경우 Matzip isn't responding 하면서, 로그인 회원가입도 잘 동작하지 않습니다. 아래 방법으로 진행해봐도, 제대로 동작하지 않습니다!https://stackoverflow.com/questions/50313967/google-play-services-are-updating-error-on-release-not-emulator-google-play/50327544#50327544몇시간 찾아봤는데...특정 기기에서는 동작하지 않는 것 같습니다. 기기를 바꾸니 동작하네요!! 정확한 이유가 궁금하긴 합니다...ㅠㅠ
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
문법 문의드립니다!
안녕하세요 강사님! 알찬 수업 잘듣고 있습니다~다름이아닌 아직 typescript문법이 약해서 공부를 동행하며 수업을 진행중인데요 막히는부분이 하나씩 발생하여 문의드려요! 예를들어 하기 문법이 이해가안가면 어느 문서를 참고하는게 좋을까요? ㅠㅠ{...login.getTextInputProps('email')}
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
로그인 통신 질문
import axios from 'axios'; const axiosInstance = axios.create({ // 안드는 localhost:3000이 안먹힐 수 있기에 10.0.2.2로 테스트 baseURL: 'http://10.0.2.2:3030', withCredentials: true, }); export default axiosInstance; 이럴때 안드로이드는 제대로 동작하지만, ios는 다시import axios from 'axios'; const axiosInstance = axios.create({ // 안드는 localhost:3000이 안먹힐 수 있기에 10.0.2.2로 테스트 baseURL: 'http://localhost:3030', withCredentials: true, }); export default axiosInstance; 이렇게 바꿔주어야 동작합니다. 이렇게 매번 바꿔서 체크하는게 맞을까요?
-
해결됨맛집 지도앱 만들기 (React Native + NestJS)
EncryptedStorage import 오류가 발생합니다.
환경:Macbook Air M2 SonomaVS-CodeRN v0.74.1Metro v0.80.8 문제:아래 명령어로 라이브러리 설치 및 pod install 과정은 모두 완료한 상태입니다. $ yarn add react-native-encrypted-storage$ npx pod-install ios/frontend/utils/encryptStorage.ts에서 EncryptStorage 라이브러리를 불러올 때 다음과 같은 경로로 불러와집니다. 자동완성 기준으로 불러왔으며, 라이브러리 삭제 후 재설치해도 현상은 같습니다. import EncryptedStorage from 'react-native-encrypted-storage/lib/typescript/EncryptedStorage'; 이 경로로 importing된 라이브러리를 파일 내에서 사용하면 정상적으로 메서드가 자동완성 됩니다. (따라서 코딩할 때에는 문제가 되는지 몰랐습니다)이 코드를 바탕으로 앱 실행 시 react-native-encrypted-storage모듈을 불러올 수 없다는 오류가 발생합니다.error: Error: Unable to resolve module react-native-encrypted-storage/lib/typescript/EncryptedStorage from /Users/popo/Desktop/ClipProjects/frontend/src/utils/encryptStorage.ts: react-native-encrypted-storage/lib/typescript/EncryptedStorage could not be found within the project or in these directories: node_modules 1 | import EncryptedStorage from 'react-native-encrypted-storage/lib/typescript/EncryptedStorage'; | ^ 2 | 3 | const setEncryptedStorage = async <T>(key: string, value: T) => { 4 | await EncryptedStorage.setItem(key, JSON.stringify(value)); 해당 모듈 외 문제는 없는 상황 같습니다. 서버 연결 및 응답(로그인, 회원가입에 대해)은 잘 됩니다. 시도해본 것:import EncryptedStorage from 'react-native-encrypted-storage/lib/typescript/EncryptedStorage';를import EncryptedStorage from 'react-native-encrypted-storage'; 로 바꿔 실행해봤는데, 컴파일 오류는 발생하지 않고 로그인 및 회원가입 시점에 eject가 발생합니다. 질문: 왜 이런 문제가 발생하는 것이며, 어떻게 해결할 수 있을까요?