코드 개선 제대로 해보기

BellBoy·2024년 2월 23일
0
post-thumbnail
post-custom-banner

재생산하기 어려운 코드를 작성했다... 수정해보자

/* 먼저 추가한 부분 (기존 코드는 아래에 있습니다)
특정 string만 사용해서 type을 지정해주었습니다
checkbox도 여러개 만들어야 했는데 map으로 처리하기위해 작성했습니다
*/

type InquiryType = "1" | "2" | "3" | "4" | "5"

interface CheckboxOption {
  value: InquiryType;
  label: string;
}

// value와 label을 따로 나누지 않아도 되지만 나중에 재사용을 위해 만들었습니다.

const checkboxOptions: CheckboxOption[] = [
  {value: "1", label:"a"},
  {value: "2", label:"b"},
  {value: "3", label:"c"},
  {value: "4", label:"d"},
  {value: "5", label:"e"},
]

/*
두번째 수정 내용
*/

  const [creating, startCreating] = useTransition();
  const [type, setType] = React.useState("");
  const [title, setTitle] = React.useState("");
  const [name, setName] = React.useState("");
  const [email, setEmail] = React.useState("");
  const [content, setContent] = React.useState("");
  const [agree, setAgree] = React.useState(false);

/*
상당히 많은 state들이 불필요하다고 느껴서 Object 형식의 state를 만들어서 관리하기로하고 
기존에 redirect 함수를 사용했는데 use server 단에서 사용하는걸 이제 알아서
router를 선언해서 Push했습니다
*/

  const  router = useRouter();

  const [creating, startCreating] = useTransition()
  const [inquiry, setInquiry] = useState({
    type: "",
    title: "",
    name: "",
    email: "",
    content: "",
    agree: false
  })

  // 세번째 수정내용
  // 체크박스를 함수화하고
  
    const handleChange = (e: 
                          React.ChangeEvent<HTMLInputElement>|
                          React.MouseEvent<HTMLInputElement>| 
                          React.ChangeEvent<HTMLTextAreaElement>,
                          field: keyof typeof inquiry) =>{
    const target = e.target as HTMLInputElement;
    const value = target.value;
    setInquiry(prev => ({ ...prev, [field]: value }));
  }

  const handleCheckboxChange = () => {
    setInquiry((prev) => ({...prev, agree:!prev.agree}));
  }


 
  /*
  두개의 함수를 추가했습니다
  위 코드를 설명하려면 아래 랜더링 부분도 참고해야하는데 기존 랜더링에서는
  */

			onChange={(e) => setTitle(e.target.value)}

// 이런 방식으로 상태관리를 덮어썼지만 Object형식으로 변경되어 type과 value로
// object 상태 관리를합니다

            onChange={(e) => handleChange(e, "title")}
  
            
// handleClick 함수는 타입마다 toast를 띄워주는걸 하나로 변경했습니다
            
  const handleClick = async () => {

    if (!type) {
      return toast({
        title: "문의 유형을 선택해주세요",
        variant: "destructive",
      });
    }
    
    
    if (!title) {
      return toast({
        title: "제목을 입력해주세요",
        variant: "destructive",
      });
    }
    
    ...이어서
    
    
에서


 const handleClick = async () => {
    if (!inquiry.agree || Object.values(inquiry).some((v) => v === "")) {
      toast({
        title: "모든 필드를 채우고 개인정보 처리방침에 동의해주세요.",
        variant: "destructive",
      });
      return;
    }

 
   // startCreating 부분에서는 try catch 구문을 추가한 뒤
//redirect 구문을 router.push 구문으로 변경했습니다
   
   startCreating(async () => {


      const newInquiry = await createInquiry({ type, title, name, email, content });

      if (newInquiry) {
        toast({
          title: "문의가 성공적으로 등록되었습니다.",
          variant: "success",
        });
        redirect("/");
      }
    });

    if (creating) return;

  };

기존코드

"use client";

import React, { useTransition } from "react";
import { Button, Label, TextArea, TextInput } from "@/components/ui";
import { toast } from "@/components/ui/use-toast";
import { createInquiry } from "@/actions/createInquiry.action";
import { redirect } from "next/navigation";
import { CheckboxLite } from "@/components/ui/CheckboxLite";import { CheckboxInquiry } from "@/components/ui/CheckboxInquiry";

export function CreateInquiryForm() {

  const [creating, startCreating] = useTransition();
  const [type, setType] = React.useState("");
  const [title, setTitle] = React.useState("");
  const [name, setName] = React.useState("");
  const [email, setEmail] = React.useState("");
  const [content, setContent] = React.useState("");
  const [agree, setAgree] = React.useState(false);

  const handleClick = async () => {

    if (!type) {
      return toast({
        title: "유형을 선택해주세요",
        variant: "destructive",
      });
    }

    if (!title) {
      return toast({
        title: "제목을 입력해주세요",
        variant: "destructive",
      });
    }


    if (!name) {
      return toast({
        title: "이름을 입력해주세요",
        variant: "destructive",
      });
    }

    if (!email) {
      return toast({
        title: "이메일을 입력해주세요",
        variant: "destructive",
      });
    }

    if (!content) {
      return toast({
        title: "내용을 입력해주세요",
        variant: "destructive",
      });
    }

    if (!agree) {
      return toast({
        title: "개인정보 이용동의를 해주세요",
        variant: "destructive",
      });
    }

    startCreating(async () => {


      const newInquiry = await createInquiry({ type, title, name, email, content });

      if (newInquiry) {
        toast({
          title: "성공적으로 등록되었습니다.",
          variant: "success",
        });
        redirect("/");
      }
    });

    if (creating) return;

  };

  return (
    <div>

      <div className="grid gap-6 md:gap-8">
        <Label
          label="문의유형">
          <div className="pc:flex pc:w-auto grid grid-cols-3 grid-rows-2 w-80 gap-3">
            <CheckboxInquiry value="상표 출원" checked={type === "상표 출원"} onClick={(e) => {setType((e.target as HTMLButtonElement).value)}}/>
            <CheckboxInquiry value="디자인 출원" checked={type === "디자인 출원"} onClick={(e) => {setType((e.target as HTMLButtonElement).value)}}/>
            <CheckboxInquiry value="디자인 의뢰" checked={type === "디자인 의뢰"} onClick={(e) => {setType((e.target as HTMLButtonElement).value)}}/>
            <CheckboxInquiry value="네이밍 의뢰" checked={type === "네이밍 의뢰"} onClick={(e) => {setType((e.target as HTMLButtonElement).value)}}/>
            <CheckboxInquiry value="기타 문의" checked={type === "기타 문의"} onClick={(e) => {setType((e.target as HTMLButtonElement).value)}}/>

          </div>

        </Label>

        <Label
          label="문의제목">
          <TextInput
            placeholder="문의드립니다"
            onChange={(e) => setTitle(e.target.value)}
          />

        </Label>
        <Label
          label="이름/닉네임">
          <TextInput
            placeholder="홍길동"
            onChange={(e) => setName(e.target.value)}
          />

        </Label>
        <Label
          label="이메일">
          <TextInput
            id="email"
            name="email"
            autoComplete="email"
            placeholder="branvip@branvip.com"
            type="email"
            required
            onChange={(e) => setEmail(e.target.value)}
          />

        </Label>

        <Label label="문의내용">
          <TextArea
            placeholder="글자 제한 300자"
            maxLength={300}
            onChange={(e) => setContent(e.target.value)}
          />
          <div className="flex justify-end">
            <span className="text-gray-400">{content.length}/300</span>
          </div>
        </Label>
      </div>

      <div className="grid gap-2 mt-10">
        <h2 className="text-lg font-bold text-surface-900 md:text-2xl">
          개인정보이용동의
        </h2>
        <p className="font-medium text-surface-500 pc:text-lg">수집하는 개인정보 항목: 이메일 주소
          개인정보는 문의 접수, 고객 불편 사항 확인 및 처리 결과 회신에 이용되며 전자상거래법 등 관련 법령에 따라 3년간 보관됩니다.
          이용자는 본 동의를 거부할 수 있으나, 미동의 시 문의 접수가 불가능합니다.</p>
        <div className="flex gap-2">
          <CheckboxLite
            // className="checked:bg-primary-200 checked:ring-0 hover:bg-primary-50 checked:hover:bg-primary-600"
            onChange={() => agree ? setAgree(false) : setAgree(true)} />
          <p className="font-medium text-surface-500 md:text-lg">동의합니다.</p>
        </div>
      </div>
      <div className="flex justify-center mt-10">
        <Button
          onClick={handleClick}
          size="md"
        >
          {creating ? "문의 중..." : "확인"}
        </Button>
      </div>
    </div>

  );

}

먼저 변경된 코드

"use client";

import React, { useState, useTransition } from "react";
import { Button, Label, TextArea, TextInput } from "@/components/ui";
import { toast } from "@/components/ui/use-toast";
import { createInquiry } from "@/actions/createInquiry.action";
import { CheckboxLite } from "@/components/ui/CheckboxLite";
import { CheckboxInquiry } from "@/components/ui/CheckboxInquiry";
import { useRouter } from "next/navigation";

type InquiryType = "1" | "2" | "3" | "4" | "5"

interface CheckboxOption {
  value: InquiryType;
  label: string;
}

const checkboxOptions: CheckboxOption[] = [
  {value: "1", label:"a"},
  {value: "2", label:"b"},
  {value: "3", label:"c"},
  {value: "4", label:"d"},
  {value: "5", label:"e"},
]
export function CreateInquiryForm() {

  const  router = useRouter();

  const [creating, startCreating] = useTransition()
  const [inquiry, setInquiry] = useState({
    type: "",
    title: "",
    name: "",
    email: "",
    content: "",
    agree: false
  })

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>| React.MouseEvent<HTMLInputElement>
                          | React.ChangeEvent<HTMLTextAreaElement>, field: keyof typeof inquiry) =>{
    const target = e.target as HTMLInputElement;
    const value = target.type === "checkbox"? target.value : target.value;
    setInquiry(prev => ({ ...prev, [field]: value }));
  }

  const handleCheckboxChange = () => {
    setInquiry((prev) => ({...prev, agree:!prev.agree}));
  }


  const handleClick = async () => {
    if (!inquiry.agree || Object.values(inquiry).some((v) => v === "")) {
      toast({
        title: "모든 필드를 채우고 개인정보 처리방침에 동의해주세요.",
        variant: "destructive",
      });
      return;
    }

    startCreating(async () => {
      try {
        const newInquiry = await createInquiry(inquiry);
       if (newInquiry) {
          toast({
            title: "문의가 성공적으로 등록되었습니다.",
            variant: "success",
          });
        }
        router.push('/')
      } catch (error) {
        toast({
          title: "문의 등록에 실패했습니다.",
          variant: "destructive",
        });
      }
    });

    if (creating) return;

  };

  return (
    <div>

      <div className="grid gap-6 md:gap-8">
        <Label
          label="문의유형">
          <div className="pc:flex pc:w-auto grid grid-cols-3 grid-rows-2 w-80 gap-3">
            {checkboxOptions.map((option) => (
              <CheckboxInquiry
                key={option.value}
                value={option.value}
                checked={inquiry.type === option.value}
                onClick={(e) => handleChange(e, "type")}
              />
            ))}

          </div>

        </Label>

        <Label
          label="문의제목">
          <TextInput
            placeholder="문의드립니다"
            onChange={(e) => handleChange(e, "title")}
          />

        </Label>
        <Label
          label="이름/닉네임">
          <TextInput
            placeholder="홍길동"
            onChange={(e) => handleChange(e,"name")}
          />

        </Label>
        <Label
          label="이메일">
          <TextInput
            id="email"
            name="email"
            autoComplete="email"
            placeholder="branvip@branvip.com"
            type="email"
            required
            onChange={(e) => handleChange(e, "email")}
          />

        </Label>

        <Label label="문의내용">
          <TextArea
            placeholder="글자 제한 300자"
            maxLength={300}
            onChange={(e) => handleChange(e, "content")}
          />
          <div className="flex justify-end">
            <span className="text-gray-400">{inquiry.content.length}/300</span>
          </div>
        </Label>
      </div>

      <div className="grid gap-2 mt-10">
        <h2 className="text-lg font-bold text-surface-900 md:text-2xl">
          개인정보이용동의
        </h2>
        <p className="font-medium text-surface-500 pc:text-lg">수집하는 개인정보 항목: 이메일 주소
          개인정보는 문의 접수, 고객 불편 사항 확인 및 처리 결과 회신에 이용되며 전자상거래법 등 관련 법령에 따라 3년간 보관됩니다.
          이용자는 본 동의를 거부할 수 있으나, 미동의 시 문의 접수가 불가능합니다.</p>
        <div className="flex gap-2">
          <CheckboxLite
            // className="checked:bg-primary-200 checked:ring-0 hover:bg-primary-50 checked:hover:bg-primary-600"
            onChange={handleCheckboxChange} />
          <p className="font-medium text-surface-500 md:text-lg">동의합니다.</p>
        </div>
      </div>
      <div className="flex justify-center mt-10">
        <Button
          onClick={handleClick}
          size="md"
        >
          {creating ? "문의 중..." : "확인"}
        </Button>
      </div>
    </div>

  );

}

코드 길이는 차이가 없지만 엄격한 타입관리와 가독성?이 올라갔다

profile
리액트러버
post-custom-banner

0개의 댓글