icon안동민 개발노트

폼 제출 및 데이터 처리


 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>
  )
}

서버 액션을 이용한 폼 제출

 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>
  )
}

실시간 유효성 검사 구현

 사용자 경험을 개선하기 위해 실시간 유효성 검사를 구현할 수 있습니다.

'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>
  )
}

10장 인증 및 권한 관리와의 연결

 8장의 폼 제출 및 데이터 처리는 10장의 인증 및 권한 관리와 밀접하게 연관됩니다. 예를 들어, 로그인 폼이나 사용자 등록 폼을 구현할 때 이 장에서 배운 기술을 적용할 수 있습니다. 또한, 폼 제출 시 사용자 인증 상태를 확인하고 권한에 따라 데이터 접근을 제어하는 등의 작업이 필요할 수 있습니다.

실습 : 복잡한 다단계 폼 구현

 다음 요구사항을 만족하는 다단계 사용자 등록 폼을 구현해보세요.

  1. 개인 정보 입력 단계 (이름, 이메일)
  2. 주소 정보 입력 단계 (주소, 도시, 우편번호)
  3. 계정 설정 단계 (사용자명, 비밀번호)
  4. 각 단계마다 유효성 검사 수행
  5. 서버 액션을 사용하여 최종 제출 처리

 구현 예시:

'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>
  )
}

 이 실습을 통해 복잡한 다단계 폼을 구현하고, 각 단계별로 유효성 검사를 수행하며, 최종적으로 서버 액션을 통해 데이터를 제출하는 과정을 경험할 수 있습니다. 이는 실제 애플리케이션에서 자주 사용되는 패턴으로, Next.js에서의 고급 폼 처리 기술을 익히는 데 도움이 됩니다.