1. 문제 발생❓
React 폼 컴포넌트를 여러 곳에서 재사용하면서 발생한 문제입니다:
< 배경 >
- 같은 입력 폼을 여러 페이지에서 재사용해야 함
- 각 페이지별로 자료 유형(materialType)만 다름
- 자료 유형은 4가지로 고정: case-studies, company-intro, 2025-dxreport, digital-skill-standard
< 구현 방식 >
- 폼 컴포넌트에 material prop을 전달해 구분
- 폼 제출 시 스프레드 연산자를 사용해 formData에 materialType 추가:
body: JSON.stringify({ ...formData, materialType: material })
- 문제점: 폼 데이터를 서버에 전송할 때 materialType 필드가 페이로드에는 있지만, 검증 후의 데이터에서는 사라지는 문제가 발생
< 초기 설정 코드 >
클라이언트 측 코드 (RequestMaterialForm.tsx):
function RequestMaterialForm({ material }: { material: string }) {
const form = useForm<RequestMaterialFormValues>({
resolver: zodResolver(requestMaterialFormSchema),
defaultValues: DEFAULT_VALUES,
});
const onSubmit: SubmitHandler<RequestMaterialFormValues> = async (formData) => {
// ...
const request = new Request("/api/request-materials", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ ...formData, materialType: material }),
});
// ...
};
// 나머지 폼 렌더링 코드...
}
스키마 정의(schema.ts):
export const requestMaterialFormSchema = z.object({
name: inquiryFormSchema.shape.name,
company: inquiryFormSchema.shape.company,
department: inquiryFormSchema.shape.department,
phoneNumber: inquiryFormSchema.shape.phoneNumber,
email: inquiryFormSchema.shape.email,
acquisitionChannel: inquiryFormSchema.shape.acquisitionChannel,
// materialType은 정의되지 않음
});
API 엔드포인트(route.ts):
export async function POST(request: Request) {
const payload = await request.json();
console.log("페이로드 =>", payload);
try {
const parsedResult = requestMaterialFormSchema.safeParse(payload);
if (!parsedResult.success) {
return NextResponse.json({ error: "invalid request" }, { status: 400 });
}
const { data } = parsedResult;
console.log("데이터 =>", data);
// ...
} catch (error) {
// ...
}
}
콘솔 로그 결과:
페이로드 => {
name: 'dd',
company: 'dd',
department: 'dd',
phoneNumber: '10020393939',
email: 'choijming211@gmail.com',
acquisitionChannel: 'web-search',
materialType: '2025-dxreport'
}
데이터 => {
name: 'dd',
company: 'dd',
department: 'dd',
phoneNumber: '10020393939',
email: 'choijming211@gmail.com',
acquisitionChannel: 'web-search'
}
2. 원인 추론 🔎
- Zod로 폼 유효성 검증을 하고 있음
- Zod 스키마에서 TypeScript 타입이 자동 생성됨
- materialType은 스키마에 정의되지 않아 타입 오류 발생
- API 엔드포인트에서 검증 후 materialType 필드가 누락됨
이는 Zod 스키마 검증 과정에서 스키마에 정의되지 않은 필드가 제거되기 때문입니다. 결국 "폼 검증 로직과 API 데이터 구조 간의 불일치" 문제가 발생했습니다.
3. 해결 과정 📋
< 시도했던 해결책 >
방법: 스키마 materialType 필드 추가
export const requestMaterialFormSchema = z.object({
// ...기존 필드들...
materialType: z.string(),
});
이 방법을 시도했지만, 폼 제출 과정에서 콘솔에 아무것도 찍히지 않는 결과가 나왔습니다. 이는 폼 입력 시점에 materialType이 사용자 입력으로 들어오지 않았기 때문입니다.
< 최종 해결책 >
API 엔드포인트에서 페이로드를 분해한 후 검증된 데이터 materialType을 다시 추가하는 방식으로 해결했습니다.
export async function POST(request: Request) {
const payload = await request.json();
const { materialType, ...formFields } = payload; // materialType 분리
console.log("페이로드 =>", payload);
try {
// formFields만 검증 (materialType 제외)
const parsedResult = requestMaterialFormSchema.safeParse(formFields);
if (!parsedResult.success) {
return NextResponse.json({ error: "invalid request" }, { status: 400 });
}
// 검증된 데이터에 materialType 다시 추가
const data = {
...parsedResult.data,
materialType
};
console.log("데이터 =>", data);
// ...
} catch (error) {
// ...
}
}
이 해결책의 결과:
페이로드 => {
name: 'dd',
company: 'dd',
department: 'dd',
phoneNumber: '10020393939',
email: 'choijming211@gmail.com',
acquisitionChannel: 'web-search',
materialType: '2025-dxreport'
}
데이터 => {
name: 'dd',
company: 'dd',
department: 'dd',
phoneNumber: '10020393939',
email: 'choijming211@gmail.com',
acquisitionChannel: 'web-search',
materialType: '2025-dxreport'
}
< 추가 문제 >
API 엔드포인트에서 데이터 처리 문제를 해결한 후, 슬랙 메세지 전송 함수에서 새로운 타입 오류가 발생했습니다.
export function sendRequestMaterialSlackMessage(
requestMaterialFormValues: RequestMaterialFormValues
) {
console.log("마지막 관문 =>", requestMaterialFormValues);
let message = `
新しい資料請求がありました。(자료청구)
資料名:: ${requestMaterialFormValues.materialType} // 오류: 'materialType' 속성이 'RequestMaterialFormValues' 타입에 존재하지 않습니다.
...
`;
return sendSlackMessage(message);
}
이 문제는 RequestMaterialFormValues 타입에 materialType 필드가 정의되어 있지 않기 때문에 발생했습니다.
< 해결책: 교차 타입(intersection Type) 사용 >
이 방법은 기존 타입에 새로운 속성을 추가하는 교차 타입을 즉시 정의합니다.
export function sendRequestMaterialSlackMessage(
requestMaterialFormValues: RequestMaterialFormValues & { materialType: string }
) {
console.log("마지막 관문 =>", requestMaterialFormValues);
let message = `
新しい資料請求がありました。(자료청구)
資料名:: ${requestMaterialFormValues.materialType}
氏名: ${requestMaterialFormValues.name}
...
`;
return sendSlackMessage(message);
}
< 해결책 분석 >
- 문제의 원인
- Zod는 스키마에 정의된 필드만 유지하고 나머지는 제거합니다. materialType은 스키마에 정의되지 않았기 때문에 검증 과정에서 제거되었습니다,
- 왜 스키마에 추가하는 방법이 적합하지 않았는가?
- 사용자가 직접 입력하는 값이 아니기 때문에 폼 검증에 포함시키면 문제가 발생
- 폼 입력과 API 요청 사이에 추가되는 값이라 폼 검증 로직과 분리하는 것이 적절
- 해결책의 장점:
- 스키마는 사용자 입력에 집중
- materialType은 API 엔드포인트에서 안전하게 처리
- 코드 구조가 명확해지고 책임분리가 잘 됨
4. 결과 ❤🔥
이로써 슬랙으로 메세지가 잘 전달하게 되었습니다!
콘솔메세지 =>
新しい資料請求がありました。(자료청구)
資料名:: 2025-dxreport
氏名: 최지민
会社名: 내배캠
部署名: 프론트엔드
電話番号: 01012345678
メールアドレス: email@gmail.com
スパルタを知ったきっかけ: YouTube
'인턴' 카테고리의 다른 글
[ Troubleshooting🛠️ ] 패키지 매니저 충돌 트러블 슈팅 npm, yarn, pnpm 간의 전환 (2) | 2025.04.01 |
---|---|
Next.js의 Link 컴포넌트와 target="_blank" 속성 이해하기 (0) | 2025.03.31 |
Next.js에서 서버와 클라이언트 간 모바일 감지 연동하기 (0) | 2025.03.27 |
Next.js 프로젝트의 효율적인 폴더 구조 개선하기 📂 (0) | 2025.03.26 |
React와 styled-components로 만드는 커스텀 토글 버튼 (0) | 2025.03.25 |