ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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://medium.com/@shavaizali159/next-js-api-routes-vs-server-actions-which-one-to-use-and-why-809f09d5069b    

    https://makerkit.dev/how-to/next-supabase/api-routes-vs-server-actions

    https://youtu.be/Se-zqHnxhaE

     

    할 때는 꽤 오래걸렸는데, 막상 쓸 내용은 그렇게 많지 않은 것 같다. 

    대부분 이전꺼를 드러내고, 교체하는 데 시간이 많이 들어서.

     

    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

    댓글

Designed by Tistory.