묻고 답해요
172만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
궁금했던 질문입니다. 바로 콤마위치 입니다.
저는 개발자 생활 초기에는 화면에서 받은 파라미터들을 받아 파라미터들을 이용하여 쿼리문을 작성하여 화면에 노출시키는 로직등을 많이 개발해 왔었어요. 그러다 보니 화면도 중요하지만 쿼리작성이 무엇보다 많이 중요했었거든요. 쿼리작성이 중요하다 보니, PL님 혹은 팀장님들 에게서 많이 교육 받은것 중 하나가 콤마를 앞으로 쓰는 겁니다. 그게 버릇이 되어 왔고 이쁘다고 생각했었어요하지만 요즘 프론트엔드 개발자들 대부분은 콤마를 뒤에다가 선언을 하더라고요.콤마를 앞이든 뒤이든 돌아가면 상관없긴 하지만, 왜 이렇게 콤마를 뒤에 두는 개발자들이 많이 생겼는지 궁금합니다. const [todos, dispatch] = useImmerReducer(todoReducer, [ { id : 0, text : 'HTML&CSS 공부하기', done : false }, { id : 1, text : '자바스크립트 공부하기', done : false } ]);
-
해결됨한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
포스트 작성 중 이탈 방지 기능 (취소 버튼 클릭 시 오류)
안녕하세요 정환님, 이렇게 바로 또 질문을 드릴 줄은 몰랐는데.. 이번 포스트 작성 중 이탈 방지 기능 개발을 하다가 "확인 버튼"을 누르게 되면 정상적으로 모든 모달이 종료가 되지만, "취소 버튼"을 누르게 되면 AlertModal이 종료되지 않고 계속 리렌더링이 되는 현상이 발생합니다ㅠㅠ 해당 부분에 대해 저번처럼 메일로 프로젝트 파일을 압축해서 보내드려도 되는지 여쭤봅니다!
-
해결됨한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
"포스트 생성에 실패했습니다" 실패 문구와 함께 에러가 발생합니다ㅠㅠ
안녕하세요 정환님! 정환님의 강의를 들으면서 코드를 따라 작성해보다가 포스트와 이미지를 최종적으로 업로드 하는 과정에서 아래의 캡쳐 사진과 같이 포스트 생성에 실패했다는 에러 메시지가 뜨게됩니다. 네트워크 탭에 보면, 포스트 부분은 잘 전달이 되는거같은데 이미지 생성에 관한 결과가 보이지 않는 것으로 보아 이미지 생성 관련 코드에서 문제가 생긴것같은데 무슨 문제일까요??..ㅠ 아래는 해당 코드들입니다. 이미지 생성 관련 코드를 가져왔는데 혹여나 다른 부분의 코드가 필요하면 추가 업로드 드리겠습니다. api/post.tsimport type { PostEntity } from './../types'; import supabase from '@/lib/supabase'; export async function createPost(content: string) { const { data, error } = await supabase .from('post') .insert({ content }) .select() .single() if (error) throw error return data } export async function createPostWithImages({ content, images, userId }: { content: string images: File[] userId: string }) { // 1. 새로운 포스트 생성 (맨 위에서 생성한 포스트 비동기 함수 가져오기) const post = await createPost(content) if (images.length === 0) return post // 2. 이미지 업로드 try { const imageUrls = await Promise.all( images.map((image) => { const fileExtension = image.name.split(".").pop() || "webp" const fileName = `${Date.now()}-${crypto.randomUUID()}.${fileExtension}` const filePath = `${userId}/${post.id}/${fileName}` return uploadImage({ file: image, filePath }) }) ) // 3. 포스트 테이블 업데이트 const updatedPost = await updatePost({ id: post.id, image_urls: imageUrls }) return updatedPost } catch(error) { await deletePost(post.id) throw error } } export async function updatePost(post: Partial<PostEntity> & { id: number }) { const { data, error } = await supabase .from("post") .update(post) .eq("id", post.id) .select() .single() if (error) throw error return data } export async function deletePost(id: number) { const { data, error } = await supabase .from("post") .delete() .eq("id", id) .select() .single() if (error) throw error return data } hooks/mutations/post/use-create-post.tsimport type { UseMutationCallback } from './../../../types'; import { createPost, createPostWithImages } from '@/api/post'; import { useMutation } from '@tanstack/react-query'; export function useCreatePost(callbacks?:UseMutationCallback) { return useMutation({ mutationFn: createPostWithImages, onSuccess: () => { if(callbacks?.onSuccess) callbacks.onSuccess() }, onError: (error) => { if (callbacks?.onError) callbacks.onError(error) } }) } components/modal/post-editor-modal.tsximport { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog' import { useCreatePost } from '@/hooks/mutations/post/use-create-post' import { usePostEditorModal } from '@/store/post-editor-modal' import { useSession } from '@/store/session' import { ImageIcon, XIcon } from 'lucide-react' import { useRef, useState, useEffect, ChangeEvent } from 'react' import { toast } from "sonner" import { Carousel, CarouselContent, CarouselItem } from '../ui/carousel' type Image = { file: File, previewUrl: string } export default function PostEditorModal() { const session = useSession() const { isOpen, close } = usePostEditorModal() const { mutate: createPost, isPending: isCreatePostPending } = useCreatePost({ onSuccess: () => { close() }, onError: (error) => { toast.error("포스트 생성에 실패했습니다", { position: "top-center" }) } }) const [content, setContent] = useState("") const [images, setImages] = useState<Image[]>([]) const textareaRef = useRef<HTMLTextAreaElement>(null) const fileInputRef = useRef<HTMLInputElement>(null) const handleCloseModal = () => { close() } const handleCreatePostClick = () => { if (content.trim() === "") return createPost({ content, images: images.map((image) => image.file), userId: session!.user.id }) } const handleSelectImages = (e:ChangeEvent<HTMLInputElement>) => { if (e.target.files) { const files = Array.from(e.target.files) files.forEach((file) => { setImages((prev) => [ ...prev, { file, previewUrl: URL.createObjectURL(file) }, ]) }) } e.target.value = "" } const handleDeleteImage = (image:Image) => { setImages((prevImages) => prevImages.filter((item) => item.previewUrl !== image.previewUrl)) } useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = "auto" textareaRef.current.style.height = textareaRef.current.scrollHeight + "px" } }, [content]) useEffect(() => { if (!isOpen) return textareaRef.current?.focus() setContent("") setImages([]) }, [isOpen]) return ( <Dialog open={isOpen} onOpenChange={handleCloseModal}> <DialogContent className="max-h-[90vh]"> <DialogTitle>포스트 작성</DialogTitle> <textarea disabled={isCreatePostPending} ref={textareaRef} value={content} onChange={(e) => setContent(e.target.value)} className="max-h-125 min-h-25 focus:outline-none" placeholder="무슨 일이 있었나요?" /> <input onChange={handleSelectImages} ref={fileInputRef} type="file" accept="image/*" multiple className="hidden" /> {images.length > 0 && <Carousel> <CarouselContent> {images.map((image) => <CarouselItem className="basis-2/5" key={image.previewUrl}> <div className="relative"> <img src={image.previewUrl} className="h-full w-full rounded-sm object-cover" /> <div onClick={() => handleDeleteImage(image)} className="absolute top-0 right-0 m-1 cursor-pointer rounded-full bg-black/30 p-1" > <XIcon className="h-4 w-4 text-white"/> </div> </div> </CarouselItem>)} </CarouselContent> </Carousel>} <Button onClick={() => { fileInputRef.current?.click() }} variant={"outline"} className="cursor=pointer" > <ImageIcon/> 이미지 추가</Button> <Button disabled={isCreatePostPending} onClick={handleCreatePostClick} className="cursor=pointer"> 저장 </Button> </DialogContent> </Dialog> ) }
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
React 버전업 관련 문의
안녕하세요 선생님, 궁금한 점이 있어서 문의드립니다.현재 강의를 보면 react 버전이 18 인데,혹시 19 로 버전업하실 계획 있으실까요?
-
해결됨한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
강의자료는 어떻게 제작하시나요?
강사님,강의 너무 잘 듣고 있습니다.그런데 강의자료가 Next.js 도 그렇고 깔끔하게 잘 만드시던데 무엇으로 어떻게 제작하시는지 간략히 설명 좀 해주시면 감사하겠습니다.
-
미해결한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
비밀번호 재설정 1회용 이메일 링크
안녕하세요!비밀번호 재설정 이메일의 링크를 클릭한 후 비밀번호를 변경했는데, 같은 링크로 다시 접속해도 계속 비밀번호가 변경되는 것을 확인했습니다.재설정 링크를 1회 사용 후 만료되게 하려면 어떻게 해야하나요?
-
해결됨한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
강의 수강 후 포트폴리오 준비 방향에 대해 조언 부탁드립니다.
안녕하세요 정환님.저는 정환님의 강의를 모두 구매해서 수강하고 있는 수강생입니다.현재는 바이브 코딩 강의를 제외한 강의들을 모두 수강했고, 지금은 복습을 진행하고 있습니다.복습과 함께 프론트엔드 신입 지원용 개인 프로젝트를 준비하고 있는데, 정환님께 배운 내용을 최대한 활용해 보고 싶어서 질문드립니다.개인 프로젝트 기술 스택은 Next.js + TanStack Query + Supabase 기반으로 생각하고 있습니다. 1. 제가 이전에 영어학원에서 근무한 경험이 있어서, 그 경험을 바탕으로 영어와 관련된 서비스를 만들어보려고 합니다.예를 들어 영어 원서 학습 서비스나 영어 단어장 서비스 등을 생각하고 있는데, 이런 주제가 프론트엔드 신입 포트폴리오용 개인 프로젝트로 적절할지 궁금합니다. 2. 혹시 프론트엔드 신입 포트폴리오용으로 추천해 주실 만한 프로젝트 주제가 있을까요? 3. 현재 React, TypeScript, Next.js, TanStack Query를 학습하고 복습 중입니다.신입 프론트엔드 개발자로 취업하기 위해 어느 정도 수준까지 준비하는 것이 좋을지, 그리고 추가로 공부하면 좋을 부분이 있다면 조언 부탁드립니다. 4. 정환님의 바이브 코딩 강의를 수강한 이후에는 AI를 활용한 프로젝트도 하나 진행해 보고 싶습니다.프론트엔드 신입 포트폴리오 관점에서 추천해 주실 만한 AI 프로젝트 주제가 있을까요? 5.포트폴리오를 준비하는 입장에서 아래 항목들의 우선순위를 어떻게 가져가는 것이 좋을지도 궁금합니다.현재 준비 기간이 넉넉하지 않아 세 가지를 모두 진행하지 못할 수도 있다는 생각이 들어서, 어떤 순서로 준비하는 것이 가장 효과적일지 조언 부탁드립니다. 1) 개인 프로젝트 2) AI 프로젝트 3) 팀 프로젝트 질문이 많았는데 시간 내어 읽어주셔서 감사합니다.정환님 강의를 통해 정말 많은 도움을 받고 있습니다. 항상 좋은 강의 만들어주셔서 감사드립니다.
-
해결됨아바타 커뮤니티앱 만들기 (React Native Expo)
eas 빌드가 엄청 오래걸려서..
❗질문 작성시 꼭 참고해주세요에러 메세지에서 단서를 찾을 수 있는 경우가 많습니다. 에러 메세지를 읽고 한번 검색해보시는것을 추천드립니다.질문글을 작성하실때는, 현재 문제(또는 에러)와 코드나 github을 첨부해주세요.개발중인 OS, ReactNative, Node 버전 등의 개발환경을 알려주셔야합니다.에러메세지는 일부분이 아닌 전체 상황을 올려주세요. 일부만 보여주시면 답변이 어렵습니다.에러 잘라서 올리시면 안됩니다!(에러 일부만 자르거나 일부만 복사하지말아주세요) eas 빌드가 엄청 오래걸려서 검색해보니 몇시간 걸릴수도 있다고 해서..eas build --platform android --local로컬 빌드를 하라고 하던데 로컬 빌드 할려면 설정이 필요한가요?
-
해결됨한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
회원가입 구현 (구현 후 최종 화면 출력 X)
해당 강의 수강중에 정환님께서 진행해주신 부분 그대로 똑같이 따라했는데 화면에서는 아예 아무것도 보이지않는.. 출력이 안됩니다..ㅠㅠ 무슨 문제일까요??예상은 api를 저장하는 auth.ts 부분부터 뭔가 잘못된거같은데 이유를 모르겠습니다.(그리고, 예외사항으로 import 부분의 빨간밑줄오류나 코드에서 오타가 아닌 빨간밑줄오류는 왜 생기는걸까요??ㅠ) 현재 진행중인 코드를 한번에 알아보기 쉽게 Github에 바로 올렸습니다.링크 : https://github.com/Skyllerrr안에 Repository의 첫 번째 프로젝트(onebite-SNS)입니다.
-
미해결맛집 지도앱 만들기 (React Native & NestJS)
디바이스 별 다른 크기의 화면을 고려하는 반응형UI
❗질문 작성시 꼭 참고해주세요에러 메세지에서 단서를 찾을 수 있는 경우가 많습니다. 에러 메세지를 읽고 한번 검색해보시는것을 추천드립니다.질문글을 작성하실때는, 현재 문제(또는 에러)와 코드나 github을 첨부해주세요.개발중인 OS, ReactNative, Node 버전 등의 개발환경을 알려주셔야합니다.에러메세지는 일부분이 아닌 전체 상황을 올려주세요. 일부만 보여주시면 답변이 어렵습니다. (에러 일부만 자르거나 일부만 복사하지말아주세요) 개발을 진행하면서, 사용자마다 서로 다른 핸드폰을 가지고 있고, 디바이스 별 화면 크기가 다릅니다. 이에 대한 반응형 UI대응은 어떻게 대응하는 것이 좋을까요?
-
미해결한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
(6.11) 회원가입시 프로필 정보 자동 생성하기 Q. 호출 순서 문의
안녕하세요 6.11 회원가입시 프로필 정보 자동 생성하기 강의중18:17 분 쯤에아래처럼 useProfileData(session?.user.id) -> useEffect 훅을 선언하는데처음에는 session?.user.id이 값이 없는데 useProfileData 훅을 먼저 부르는게 맞나요?저는 useEffect -> useProfileData 이 순서가 맞다고 생각해서요 원본export default function SessionProvider({ children }: { children: ReactNode }) { const session = useSession(); const setSession = useSetSession(); const isSessionLoaded = useIsSessionLoaded(); const { data: profile, isLoading: isProfileLoading, isPending, } = useProfileData(session?.user.id); useEffect(() => { supabase.auth.onAuthStateChange((event, session) => { setSession(session); }); }, []); if (!isSessionLoaded) return <GlobalLoader />; if (isProfileLoading) return <GlobalLoader />; return children; } 내 생각 export default function SessionProvider({ children }: { children: ReactNode }) { const session = useSession(); const setSession = useSetSession(); const isSessionLoaded = useIsSessionLoaded(); useEffect(() => { supabase.auth.onAuthStateChange((event, session) => { setSession(session); }); }, []); const { data: profile, isLoading: isProfileLoading, isPending, } = useProfileData(session?.user.id); if (!isSessionLoaded) return <GlobalLoader />; if (isProfileLoading) return <GlobalLoader />; return children; }
-
미해결한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
명시적 타입 선언(콜론 타입 선언)과 as 타입 단언 차이
안녕하세요 6.8 Zustand를 이용해 세션 관리하기4:52초에 타입 선언 부분에 타입 문의가 있어서요!아래처럼 차이가 뭔지 잘 모르겟네요 강사님은 as 타입선언으로 자주 쓰시던데 ai 물어보니 initailState: State 이 형식이 명시적 타입 선언 이라 더 엄격해서 추천한다고 하던데 as로 타입 단언 하는 이유가 있으신가요? const initailState: State = { isLoaded: false, session: null, }; const initailState = { isLoaded: false, session: null, } as State;
-
미해결한입 리액트 실전 라이브러리 키트 - Zustand, Tanstack Query, TailwindCSS
쿠폰 문의 드립니다.
안녕하세요 강사님 강의들 잘 보고 있습니다.2026년에 구입했던 "한입 리액트 실전 라이브러리 키트 - Zustand, Tanstack Query, TailwindCSS" 강의 다시 보게 되면서 "한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편" 강의도 구매해서 보고 싶어 졌는데 예전에 받았던 할인 쿠폰이 만료 처리 되어 사용할 수가 없네요. 너무 늦은 시점에 요청 드려 염치가 없지만 혹시 쿠폰 재발급이 가능한지 문의 드립니다. 감사합니다.수고하세요.
-
해결됨한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
useMutation 적용 후 새로운 글 등록시 content가 안보여요
useMutation 적용후,새로운 아이템을 추가하면,아이템은 추가 되지만 안에 컨텐츠 내용은 안나오고, 삭제도 안되네요?어디를 놓친걸까요??
-
미해결한입 리액트 실전 라이브러리 키트 - Zustand, Tanstack Query, TailwindCSS
(5.12) 캐시 정규화 하기 1 질문
안녕하세요5.12 캐시 정규화하기 부분에 궁금한 부분이 있어 질문 남겨봅니다!이 캐시 정규화는 리스트 페이지와 상세 페이지의 데이터가 같을 경우에만 사용 가능한 패턴인가요? 만약 상세 페이지에서 백엔드가 내려주는 데이터가 리스트 페이지와 비슷하지만 몇 개의 필드가 다르게 내려온다면 사용할 수 없는 패턴인가요?
-
미해결한입 리액트 실전 라이브러리 키트 - Zustand, Tanstack Query, TailwindCSS
섹션3 Shadcn/ui 생성 시
( 해당 섹션 수강하던 2026-04-03 기준 )npx shadcn@latest init 명령어 입력하면 2가지 선택지를 제공해줘요.Select a component library 라고 떠요선택지에 Radix, Base 있는데 Radix가 흔히 아는 Shadcn/ui 라고 합니다.Which preset would you like to use?Nova (아이콘 Lucide, 폰트 Geist)Vega (아이콘 Lucide, 폰트 Inter)Maia (아이콘 Hugeicons, 폰트 Figtree)Lyra (아이콘 Phosphor, 폰트 JetBrains Mono)Mira (아이콘 Hugeicons, 폰트 Inter)Luma (아이콘 Lucide, 폰트 Inter)강의에선 7:16 참고 하시면 base color 고르라는 선택지가 나왔고, 강사님께선 Neutral 고르셨습니다. 저는 저게 안나와서 gemini 에게 물어봤고 이와 같은 답변을 얻어서 진행했습니다. 혹시나 참고가 될까싶어 작성합니다.
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
오타?
2 퀴즈 강의에서 설명된 Create React App(CRA)과 Bit의 비교 중, Bit의 특징으로 언급된 것은 무엇인가요? 이거 오타 아닌가요? Bit -> Vite?
-
미해결한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
6.8 zustand 세션 질문입니다.
session.tsimport type { Session } from "@supabase/supabase-js"; import { create } from "zustand"; import { combine, devtools } from "zustand/middleware"; type State = { isLoaded: boolean; session: Session | null; }; const initialState = { isLoaded: false, session: null, } as State; const useSessionStore = create( devtools( combine(initialState, (set) => ({ actions: { setSession: (session: Session | null) => { set({ session, isLoaded: true }); }, }, })), { name: "sessionStore", }, ), ); export const useSession = () => { const session = useSessionStore((store) => store.session); return session; }; export const useIsSessionLoaded = () => { const isSessionLoaded = useSessionStore((store) => store.isLoaded); return isSessionLoaded; }; export const useSetSession = () => { const setSession = useSessionStore((store) => store.actions.setSession); return setSession; }; App.tsximport { Button } from "@/components/ui/button"; import RootRoute from "./root-route"; import supabase from "@/lib/supabase"; import { useEffect } from "react"; import { useSetSession } from "@/store/session"; export default function App() { const setSession = useSetSession(); useEffect(() => { //console.log("111"); supabase.auth.onAuthStateChange((event, session) => { setSession(session); }); }, []); return <RootRoute />; } 코드는 강의와 동일하게 잘따라간 것 같은데개발자 도구 에서 session이랑 isLoaded가 안보입니다.새로고침을 해도, 탭을 껏다 켜도, 리액트를 껏다 켜도,제미나이한테 물어봐도 이게 해결이 안됩니다..... +추가App.tsximport { Button } from "@/components/ui/button"; import RootRoute from "./root-route"; import supabase from "@/lib/supabase"; import { useEffect } from "react"; import { useIsSessionLoaded, useSession, useSetSession } from "@/store/session"; export default function App() { const setSession = useSetSession(); const session = useSession(); const isLoaded = useIsSessionLoaded(); useEffect(() => { //console.log("111"); supabase.auth.onAuthStateChange((event, session) => { setSession(session); }); }, []); useEffect(() => { console.log("현재 세션 상태:", { session, isLoaded }); }, [session, isLoaded]); // 값이 바뀔 때마다 실행됨 return <RootRoute />; } 이렇게 하고 새로고침하면 콘솔에는 찍히긴 합니다 단순한 리덕스 툴킷 버그인지콘솔에 찍히니까 넘어가도 상관없는지 모르겠습니다.
-
미해결한입 리액트 실전 라이브러리 키트 - Zustand, Tanstack Query, TailwindCSS
섹션2 tailwindcss 퀴즈에 대해서
강의 잘 듣고있습니다. tailwindcss@v4로 변경되고 이후론 사용을 안해봐서 이번 강의가 매우 도움이 되고있습니다. 섹션2 모든 챕터 수강 이후 퀴즈를 푸는데 문제 1개가 강의에 사용되는 버전인 v4가 아닌 v3에 대한 내용이 있는 것 같아서 Q&A 올렸습니다.공식문서 - docs - getting started - upgrade guide에서도 확인 가능합니다. 강의 감사합니다.
-
해결됨한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS 편
next.js 강의에서도 리액트 라이브러리들을 다뤄주시나요?
안녕하세요.next.js 강의에서도 zustand나 tanstack query 같은 리액트 라이브러리들을 다뤄주시나요?해당 강의도 도서 관련 프로젝트 실습을 하는 것 같은데 인증이나 무한스크롤, 낙관적 업데이트를 다루는 지 궁금합니다!