-
7. Front 회원가입 페이지 전면 수정 및 서버통신MERN Stack ChatApp 2024. 8. 4. 18:43
회원가입 페이지를 전면 수정했다.
현재 내가 보며 공부하고 있는
https://www.youtube.com/watch?v=nvjYCK9oDRU
에서는
react로 만들고 있고, chakra ui라는 거를 사용해 ui를 만들고 있는데서 우선 나랑 차이가 좀 난다. (나는 nextJS로 만들고 있음)
또, 이게 좀 옛날 동영상이다 보니 이때 이후로 많은게 업데이트 되었는데,
대표적으로 여기서는 form안의 input값들을 전부 useState로 관리하고 있고, 따로 유효성검사도 해주고있지 않다.
그런데 나는 유효성검사도 좀 해주고, 서버와 통신하는 방식도 살짝 다르게 해주고 싶었다.
그래서 전부 바꿔줬는데, 시간이 좀 걸렸다.
난이도는 그렇게까지 높지는 않았지만, 귀찮음 점수가 꽤 높았다.
난이도 : ⭐️⭐️
귀찮음 : ⭐️⭐️⭐️⭐️⭐️
어쨌든 그래서 현재 shadcn 을 부분적으로 이용해 signup ui를 바꿔줬고,
zod를 이용해 유효성 검사를 해주고 있고,
api route방식과 server action방식을 이용해 서버와 통신해주고 있다.
(한 방법을 선택해서 하면 된다. 나는 두가지 다 만들어봤다. 여기서는 이중에서 api route 방식을 쓸거다)
이 시리즈의 중점은 websocket을 이용하여 실시간으로 통신하는 앱을 만드는 게 주 목적이니, 꾸미거나 이런부분은 설명하지 않고 넘어가고, api route방식과 server action 방식에 대해서만 다루도록 하겠다. zod를 이용한 유효성검사 등도 인터넷에 좋은 글들이 많으니 그걸 보면 좋을 것 같다. (심지어 shadcn에도 어느정도 나와있다)
먼저 Signup.tsx를 이렇게 바꿔줬다.
zod, useForm, zodResolver를 npm install해준다. 그 외, shadcn, shadcn form, shadcn input등도 설치해준다.
그리고 schema/SignupSchema.tsx도 만들어 줬는데, 이 글에서 쓰는 거는 생략하겠다. github 참조바란다.
// frontend/components/Authentication/Signup.tsx "use client"; import React, { FormEvent, useState } from "react"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { FormControl, FormLabel, Form, FormField, FormItem, FormDescription, FormMessage, } from "../ui/form"; import { Input } from "../ui/input"; import { Button } from "../ui/button"; import { SignupSchema } from "@/schema/SignupSchema"; import { THIS_URL } from "@/lib/constants"; import { toast } from "sonner"; import { signup } from "@/action/userAction"; const Signup = () => { const [show, setShow] = useState<boolean>(false); // const signUpForm = const signupForm = useForm<z.infer<typeof SignupSchema>>({ resolver: zodResolver(SignupSchema), // defaultValues를 넣어줘야 경고가 뜨지 않는다. Warning: A component is changing an uncontrolled input to be controlled. defaultValues: { name: "", email: "", password: "", confirmPassword: "", }, }); // 비밀번호 show hide const handleClick = (e: FormEvent) => { e.preventDefault(); // 이거 안해주면 버튼누르면 바로 폼 제출해버리니깐 해줘야함. setShow(!show); }; // 폼 제출 // const onSubmit = (values: z.infer<typeof SignupSchema>) => { const onSubmit = async (values: z.infer<typeof SignupSchema>) => { // const result = SignupSchema.safeParse(values); // const res = await fetch(`${THIS_URL}/auth/signup`, { // method: "POST", // body: JSON.stringify(values), // }); // if (!res.ok) { // toast.error("회원가입 실패 ㅠㅠ", { // position: "top-center", // richColors: true, // closeButton: true, // }); // } // console.log("res : ", res); const res = await signup(values); console.log("serverAction - res : ", res); if (res.message) { toast.error(res.message, { position: "top-center", richColors: true, closeButton: true, }); } }; const submitHandler = () => {}; const postDetails = () => {}; return ( <Form {...signupForm}> <form className="space-y-2" onSubmit={signupForm.handleSubmit(onSubmit)}> <FormField control={signupForm.control} name="name" render={({ field }) => ( <FormItem> <FormLabel>Name 📍</FormLabel> <FormControl id="first-name"> <Input placeholder="Enter Your name" {...field} /> </FormControl> {/* <FormDescription>Required</FormDescription> */} </FormItem> )} /> <FormField control={signupForm.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email 📍</FormLabel> <FormControl id="email"> <Input placeholder="Enter Your Email" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={signupForm.control} name="password" render={({ field }) => ( <FormItem> <FormLabel>Password 📍</FormLabel> <div className="flex gap-2"> <FormControl id="password"> <Input placeholder="Enter Your Email" {...field} type={show ? "text" : "password"} /> </FormControl> <Button onClick={handleClick} variant={"ghost"}> {show ? "Hide" : "Show"} </Button> </div> </FormItem> )} /> <FormField control={signupForm.control} name="confirmPassword" render={({ field }) => ( <FormItem> <FormLabel>Confirm Password 📍</FormLabel> <div className="flex gap-2"> <FormControl id="confirmPassword"> <Input placeholder="Enter Your Email" {...field} type={show ? "text" : "password"} /> </FormControl> <Button onClick={handleClick} variant={"ghost"}> {show ? "Hide" : "Show"} </Button> </div> <FormMessage /> </FormItem> )} /> <FormField control={signupForm.control} name="pic" render={({ field }) => ( <FormItem> <FormLabel>Picture</FormLabel> <FormControl id="pic"> <Input placeholder="Enter Your Email" {...field} type="file" /> </FormControl> </FormItem> )} /> <Button className="bg-blue-400 w-full mt-10">Sign Up</Button> </form> </Form> ); }; export default Signup;
먼저 server action 방식부터 해보겠다. https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations
Data Fetching: Server Actions and Mutations | Next.js
Learn how to handle form submissions and data mutations with Next.js.
nextjs.org
frontend/action/userAction.ts 파일을 만들어 준다.
// frontend/action/userAction.ts "use server"; import { SignupType } from "@/schema/SignupSchema"; export const signup = async (form: SignupType) => { const res = await fetch(`${process.env.SERVER_URL}/api/user`, { method: "POST", headers: { // header 꼭 넣어줘야함. 안넣어주면 제대로 전송 안됨. "Content-Type": "application/json", }, body: JSON.stringify(form), }); // console.log("@@@ res : ", await res.json()); const result = await res.json(); // if (result._id) { // return result; // } else { // return result.message; // } return result; };
express.js로 만든 서버에 fetch요청을 보내 통신을 하는 걸 볼 수 있다.
다음으로 api route방식을 써서 서버와 통신해보겠다. https://nextjs.org/docs/app/building-your-application/routing/route-handlers
Routing: Route Handlers | Next.js
Create custom request handlers for a given route using the Web's Request and Response APIs.
nextjs.org
위 frontend/components/Authentication/signup.tsx 부분에서 위의 주석처리 됬던 부분을 풀어준다. 그리고 serverAction을 호출하는 쪽을 주석처리해준다.
// 폼 제출 const onSubmit = async (values: z.infer<typeof SignupSchema>) => { const result = SignupSchema.safeParse(values); const res = await fetch(`${THIS_URL}/auth/signup`, { method: "POST", body: JSON.stringify(values), }); if (!res.ok) { const result = await res.json(); console.log("result : ", result); toast.error(result.message, { position: "top-center", richColors: true, closeButton: true, }); } console.log("res : ", res); };
이제 api-route방식으로 해주기 위해 파일을 만들어준다.
// frontend/app/auth/signup/route.ts import { NextRequest, NextResponse } from "next/server"; export const POST = async (req: NextRequest) => { // console.log("req : ", await req.json()); console.log(process.env.URL); const data = await req.json(); const res = await fetch(`${process.env.SERVER_URL}/api/user`, { method: "POST", headers: { // header 꼭 넣어줘야함. 안넣어주면 제대로 전송 안됨. "Content-Type": "application/json", }, body: JSON.stringify(data), }); // console.log("res : ", await res.json()); const result = await res.json(); if (result._id) { return NextResponse.json({ result }); } else { // return NextResponse.json({ Error }); // return new NextResponse(result, { status: 400 }); return NextResponse.json({ ...result }, { status: 400 }); } return NextResponse.json({}); };
Server Action방식과 api route방식은 둘 다 같은 기능을 하게 만들 수 있다.
하지만 세부적으로 봤을 때, api route 방식이 좀 더 세밀하게 손볼 수 있는 요소들이 많다.
어떤 거를 쓸지는 본인의 선택이다. 아래자료들을 보고 선택하는데 좀 더 도움이 될 수 있다.
https://makerkit.dev/how-to/next-supabase/api-routes-vs-server-actions.
할 때는 꽤 오래걸렸는데, 막상 쓸 내용은 그렇게 많지 않은 것 같다.
대부분 이전꺼를 드러내고, 교체하는 데 시간이 많이 들어서.
github : https://github.com/Wunhyeon/ChatApp-MERNStack/tree/7.frontSignup
GitHub - Wunhyeon/ChatApp-MERNStack
Contribute to Wunhyeon/ChatApp-MERNStack development by creating an account on GitHub.
github.com
'MERN Stack ChatApp' 카테고리의 다른 글
9. Find Users API (0) 2024.08.05 8.Login (0) 2024.08.05 5. express server와 몽고DB 연결하기 (0) 2024.08.01 4. 로그인, 회원가입 폼 만들기 (0) 2024.07.31 3. 스키마생성 (0) 2024.07.31