Next.js에서 폼 제출을 처리하고 데이터 유효성을 검사하는 방법은 애플리케이션의 사용자 경험과 보안에 중요한 역할을 합니다. 이 절에서는 다양한 폼 처리 방법과 데이터 유효성 검사 기법에 대해 알아보겠습니다.
클라이언트 사이드 폼 처리
React의 state를 사용한 기본적인 폼 처리 방법입니다.
'use client'
import { useState } from 'react'
export default function ClientSideForm () {
const [ formData , setFormData ] = useState ({ name: '' , email: '' })
const handleChange = ( e ) => {
setFormData ({ ... formData , [e.target.name]: e . target . value })
}
const handleSubmit = ( e ) => {
e . preventDefault ()
console . log ( 'Form submitted:' , formData )
// 여기서 API 호출 또는 다른 처리를 수행할 수 있습니다.
}
return (
< form onSubmit = { handleSubmit } >
< input
type = "text"
name = "name"
value = { formData . name }
onChange = { handleChange }
placeholder = "Name"
/>
< input
type = "email"
name = "email"
value = { formData . email }
onChange = { handleChange }
placeholder = "Email"
/>
< button type = "submit" > Submit </ button >
</ form >
)
}
Copy
서버 액션을 이용한 폼 제출
Next.js의 서버 액션을 사용하면 폼 제출을 서버에서 직접 처리할 수 있습니다.
// app/actions.js
'use server'
export async function submitForm ( formData ) {
const name = formData . get ( 'name' )
const email = formData . get ( 'email' )
// 서버 사이드 유효성 검사
if (! name || ! email ) {
return { error: 'Name and email are required' }
}
// 데이터베이스 저장 로직 등을 여기에 구현
return { success: true , message: 'Form submitted successfully' }
}
// app/form/page.js
import { submitForm } from '../actions'
export default function ServerActionForm () {
return (
< form action = { submitForm } >
< input type = "text" name = "name" placeholder = "Name" required />
< input type = "email" name = "email" placeholder = "Email" required />
< button type = "submit" > Submit </ button >
</ form >
)
}
Copy
실시간 유효성 검사 구현
사용자 경험을 개선하기 위해 실시간 유효성 검사를 구현할 수 있습니다.
'use client'
import { useState } from 'react'
export default function FormWithValidation () {
const [ formData , setFormData ] = useState ({ name: '' , email: '' })
const [ errors , setErrors ] = useState ({})
const validateField = ( name , value ) => {
if ( name === 'name' && value . length < 2 ) {
return 'Name must be at least 2 characters long'
}
if ( name === 'email' && ! /\S + @\S +\. \S + / . test ( value )) {
return 'Invalid email address'
}
return ''
}
const handleChange = ( e ) => {
const { name , value } = e . target
setFormData ({ ... formData , [name]: value })
setErrors ({ ... errors , [name]: validateField ( name , value ) })
}
const handleSubmit = ( e ) => {
e . preventDefault ()
const formErrors = Object . keys ( formData ). reduce (( acc , key ) => {
const error = validateField ( key , formData [ key ])
if ( error ) acc [ key ] = error
return acc
}, {})
if ( Object . keys ( formErrors ). length === 0 ) {
console . log ( 'Form submitted:' , formData )
// 폼 제출 로직
} else {
setErrors ( formErrors )
}
}
return (
< form onSubmit = { handleSubmit } >
< div >
< input
type = "text"
name = "name"
value = { formData . name }
onChange = { handleChange }
placeholder = "Name"
/>
{ errors . name && < span className = "error" > { errors . name } </ span > }
</ div >
< div >
< input
type = "email"
name = "email"
value = { formData . email }
onChange = { handleChange }
placeholder = "Email"
/>
{ errors . email && < span className = "error" > { errors . email } </ span > }
</ div >
< button type = "submit" > Submit </ button >
</ form >
)
}
Copy
10장 인증 및 권한 관리와의 연결
8장의 폼 제출 및 데이터 처리는 10장의 인증 및 권한 관리와 밀접하게 연관됩니다. 예를 들어, 로그인 폼이나 사용자 등록 폼을 구현할 때 이 장에서 배운 기술을 적용할 수 있습니다. 또한, 폼 제출 시 사용자 인증 상태를 확인하고 권한에 따라 데이터 접근을 제어하는 등의 작업이 필요할 수 있습니다.
실습 : 복잡한 다단계 폼 구현
다음 요구사항을 만족하는 다단계 사용자 등록 폼을 구현해보세요.
개인 정보 입력 단계 (이름, 이메일)
주소 정보 입력 단계 (주소, 도시, 우편번호)
계정 설정 단계 (사용자명, 비밀번호)
각 단계마다 유효성 검사 수행
서버 액션을 사용하여 최종 제출 처리
구현 예시:
'use client'
import { useState } from 'react'
import { submitRegistration } from './actions'
export default function MultiStepForm () {
const [ step , setStep ] = useState ( 1 )
const [ formData , setFormData ] = useState ({
name: '' , email: '' , address: '' , city: '' , zipCode: '' , username: '' , password: ''
})
const [ errors , setErrors ] = useState ({})
const validateStep = ( step ) => {
const newErrors = {}
switch ( step ) {
case 1 :
if (! formData . name ) newErrors . name = 'Name is required'
if (! /\S + @\S +\. \S + / . test ( formData . email )) newErrors . email = 'Invalid email'
break
case 2 :
if (! formData . address ) newErrors . address = 'Address is required'
if (! formData . city ) newErrors . city = 'City is required'
if (! / ^ \d {5} $ / . test ( formData . zipCode )) newErrors . zipCode = 'Invalid zip code'
break
case 3 :
if ( formData . username . length < 4 ) newErrors . username = 'Username must be at least 4 characters'
if ( formData . password . length < 8 ) newErrors . password = 'Password must be at least 8 characters'
break
}
return newErrors
}
const handleChange = ( e ) => {
setFormData ({ ... formData , [e.target.name]: e . target . value })
}
const handleNextStep = () => {
const stepErrors = validateStep ( step )
if ( Object . keys ( stepErrors ). length === 0 ) {
setStep ( step + 1 )
} else {
setErrors ( stepErrors )
}
}
const handleSubmit = async ( e ) => {
e . preventDefault ()
const stepErrors = validateStep ( 3 )
if ( Object . keys ( stepErrors ). length === 0 ) {
const result = await submitRegistration ( formData )
if ( result . success ) {
alert ( 'Registration successful!' )
} else {
setErrors ({ submit: result . error })
}
} else {
setErrors ( stepErrors )
}
}
return (
< form onSubmit = { handleSubmit } >
{ step === 1 && (
< div >
< input name = "name" value = { formData . name } onChange = { handleChange } placeholder = "Name" />
{ errors . name && < span > { errors . name } </ span > }
< input name = "email" value = { formData . email } onChange = { handleChange } placeholder = "Email" />
{ errors . email && < span > { errors . email } </ span > }
</ div >
) }
{ step === 2 && (
< div >
< input name = "address" value = { formData . address } onChange = { handleChange } placeholder = "Address" />
{ errors . address && < span > { errors . address } </ span > }
< input name = "city" value = { formData . city } onChange = { handleChange } placeholder = "City" />
{ errors . city && < span > { errors . city } </ span > }
< input name = "zipCode" value = { formData . zipCode } onChange = { handleChange } placeholder = "Zip Code" />
{ errors . zipCode && < span > { errors . zipCode } </ span > }
</ div >
) }
{ step === 3 && (
< div >
< input name = "username" value = { formData . username } onChange = { handleChange } placeholder = "Username" />
{ errors . username && < span > { errors . username } </ span > }
< input name = "password" type = "password" value = { formData . password } onChange = { handleChange } placeholder = "Password" />
{ errors . password && < span > { errors . password } </ span > }
</ div >
) }
{ step < 3 ? (
< button type = "button" onClick = { handleNextStep } > Next </ button >
) : (
< button type = "submit" > Submit </ button >
) }
{ errors . submit && < div > { errors . submit } </ div > }
</ form >
)
}
Copy
이 실습을 통해 복잡한 다단계 폼을 구현하고, 각 단계별로 유효성 검사를 수행하며, 최종적으로 서버 액션을 통해 데이터를 제출하는 과정을 경험할 수 있습니다. 이는 실제 애플리케이션에서 자주 사용되는 패턴으로, Next.js에서의 고급 폼 처리 기술을 익히는 데 도움이 됩니다.