재생산하기 어려운 코드를 작성했다... 수정해보자
/* 먼저 추가한 부분 (기존 코드는 아래에 있습니다)
특정 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>
);
}
코드 길이는 차이가 없지만 엄격한 타입관리와 가독성?이 올라갔다