React-Hook-Form 으둜 μž…λ ₯μƒνƒœλ₯Ό 효과적으둜 μ œμ–΄ν•˜κΈ° (1). μ œμ–΄ μ»΄ν¬λ„ŒνŠΈμ— λŒ€ν•΄

1. λ“€μ–΄κ°€λ©΄μ„œ

ν”„λ‘ νŠΈμ—”λ“œ 개발자의 μž…μž₯μ—μ„œ 데이터λ₯Ό λ‹€λ£¨λŠ” 방법은 μ—¬λŸ¬κ°€μ§€κ°€ μžˆμŠ΅λ‹ˆλ‹€.

  • 차트 라이브러리 등을 ν™œμš©ν•΄ 데이터λ₯Ό μ‹œκ°ν™”ν•˜κΈ°
  • 폼을 톡해 데이터λ₯Ό μˆ˜μ§‘ν•˜κΈ°
  • ν…Œμ΄λΈ”μ„ 톡해 데이터λ₯Ό λ‚˜μ—΄ν•˜κΈ°

이 쀑에 μ‚¬μš©μžμ˜ μž…λ ₯ κ°’ 등을 λ°›μ•„ 데이터λ₯Ό μˆ˜μ§‘ν•˜λŠ” Form 을 효과적으둜 μ œμ–΄ν•  수 μžˆλŠ” React-Hook-Form 에 λŒ€ν•΄ μ•Œμ•„λ³΄κ²Œ 될 ν…λ°μš”.

κ·Έ 전에 μ•žμ„œ Form 을 μ²˜λ¦¬ν•˜λŠ”λ° ν•„μš”ν•œ 사전 지식을 μ•Œμ•„ λ΄…λ‹ˆλ‹€.

ν•„μš”ν•œ 사전 지식은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • μ œμ–΄ μ»΄ν¬λ„ŒνŠΈ
  • λΉ„μ œμ–΄ μ»΄ν¬λ„ŒνŠΈ

2. μ œμ–΄ μ»΄ν¬λ„ŒνŠΈ

κ°„λ‹¨ν•˜κ²Œ μ„€λͺ…ν•˜λ©΄ μ‚¬μš©μžμ˜ μž…λ ₯을 μ²˜λ¦¬ν•˜λŠ”λ° React 에 μ˜ν•΄ 값이 μ œμ–΄λ˜λŠ” μž…λ ₯ 폼 μ—˜λ¦¬λ¨ΌνŠΈλ₯Ό μ œμ–΄ μ»΄ν¬λ„ŒνŠΈλΌκ³  ν•©λ‹ˆλ‹€.

https://ko.legacy.reactjs.org/docs/forms.html#controlled-components

μ‚¬μš©μžμ˜ μž…λ ₯을 λ°›λŠ” μ»΄ν¬λ„ŒνŠΈ (λŒ€ν‘œμ μœΌλ‘œ input) 에 event 객체λ₯Ό μ΄μš©ν•΄ useState 의 setState() ν•¨μˆ˜λ₯Ό 톡해 값을 μ œμ–΄ν•˜λŠ” 방식을 μ˜λ―Έν•©λ‹ˆλ‹€. λ¦¬μ•‘νŠΈμ— μ˜ν•΄ 값이 μ œμ–΄λ˜κΈ°μ— 이λ₯Ό μ œμ–΄ μ»΄ν¬λ„ŒνŠΈλΌκ³  λΆ€λ¦…λ‹ˆλ‹€.

μ œμ–΄ μ»΄ν¬λ„ŒνŠΈλŠ” μ‚¬μš©μžκ°€ μž…λ ₯ν•œ κ°’κ³Ό μ €μž₯λ˜λŠ” 값이 μ‹€μ‹œκ°„μœΌλ‘œ 동기화 λ©λ‹ˆλ‹€. 즉 항상 μ΅œμ‹ κ°’μ„ μœ μ§€ν•˜λŠ”λ°, μ΄λŠ” μƒˆλ‘œμš΄ μž…λ ₯ 값이 생길 λ•Œλ§ˆλ‹€ μƒνƒœλ₯Ό μƒˆλ‘­κ²Œ κ°±μ‹ ν•©λ‹ˆλ‹€.

μ΄λŸ¬ν•œ λ‚΄μš©λ“€μ„ ꡬ체적으둜 μ„€λͺ…ν•˜κΈ° μœ„ν•΄ μ‚¬μš©μžμ˜ 이름과 λΉ„λ°€λ²ˆν˜Έλ₯Ό λ°›μ•„ λ‘œκ·ΈμΈμ„ κ°€μ •ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§Œλ“€μ–΄ λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

controlled components

이 μ»΄ν¬λ„ŒνŠΈλ₯Ό 톡해 μ„œλ²„μ— μš”μ²­μ„ 보낼 λ•Œ 확인해야 ν•  사항은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • μ‚¬μš©μž 이름, λΉ„λ°€λ²ˆν˜Έκ°€ μ μ ˆν•˜κ²Œ μž…λ ₯이 λ˜μ—ˆλŠ”μ§€
  • μ μ ˆν•˜κ²Œ μž…λ ₯λ˜μ§€ μ•Šμ•˜μ„ λ•Œμ˜ μ—λŸ¬ λ©”μ‹œμ§€ 처리

이λ₯Ό μœ„ν•΄ ν”„λ‘ νŠΈμ—”λ“œ κ°œλ°œμ— ν•„μš”ν•œ 사항을 λ‚˜μ—΄ν•΄ λ³΄κ² μŠ΅λ‹ˆλ‹€.

  1. 이름, λΉ„λ°€λ²ˆν˜Έμ˜ 값을 μƒνƒœ κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ 각각의 state

    const [username, setUsername] = useState<string>('')
    const [password, setPassword] = useState<string>('')
  2. 이름, λΉ„λ°€λ²ˆν˜Έμ˜ μ—λŸ¬ λ©”μ‹œμ§€ 값을 μƒνƒœ κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ 각각의 state

    const [usernameError, setUsernameError] = useState<string>('')
    const [passwordError, setPasswordError] = useState<string>('')
  3. 이름, λΉ„λ°€λ²ˆν˜Έμ˜ μž…λ ₯값이 λ³€κ²½λ˜λŠ” 이벀트λ₯Ό κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ onChange Handler ν•¨μˆ˜

    const handleUsernameChange = (
     event: React.ChangeEvent<HTMLInputElement>
    ) => {
     setUsername(event.target.value)
    }
    
    const handlePasswordChange = (
     event: React.ChangeEvent<HTMLInputElement>
    ) => {
     setPassword(event.target.value)
    }
  4. 이름, λΉ„λ°€λ²ˆν˜Έμ˜ 정합성을 νŒλ‹¨ν•˜λŠ” validation ν•¨μˆ˜

    const validateName = (name: string) => {
     let isValidated = false
    
     if (name === '') {
       setUsernameError('이름을 μž…λ ₯ν•΄μ£Όμ„Έμš”.')
     } else if (name.length < 2) {
       setUsernameError('이름은 2자 이상 μž…λ ₯ν•΄μ£Όμ„Έμš”.')
     } else {
       isValidated = true
       setUsernameError('')
     }
    
     return isValidated
    }
    
    const validatePassword = (password: string) => {
     let isValidated = false
    
     if (password === '') {
       setPasswordError('λΉ„λ°€λ²ˆν˜Έλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.')
     } else if (password.length < 8) {
       setPasswordError('λΉ„λ°€λ²ˆν˜ΈλŠ” 8자 이상 μž…λ ₯ν•΄μ£Όμ„Έμš”.')
     } else {
       isValidated = true
       setPasswordError('')
     }
    
     return isValidated
    }
  5. 이름, λΉ„λ°€λ²ˆν˜Έλ₯Ό 가지고 API λ₯Ό μš”μ²­ν•˜κΈ° μœ„ν•œ onSubmit ν•¨μˆ˜

    const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
     event.preventDefault()
     const isValidatedName = validateName(username)
     const isValidatedPassword = validatePassword(password)
    
     if (isValidatedName && isValidatedPassword) {
       console.log('username', username)
       console.log('password', password)
       setUsername('')
       setPassword('')
       setUsernameError('')
       setPasswordError('')
     }
    }

2-1. μ œμ–΄ μ»΄ν¬λ„ŒνŠΈμ˜ 문제 1

μ œμ–΄ μ»΄ν¬λ„ŒνŠΈλ‘œ μž…λ ₯값을 닀루기 μœ„ν•΄μ„œ 각각의 μž…λ ₯κ°’ λ§ˆλ‹€ ν•˜λ‚˜ ν•˜λ‚˜ μƒνƒœλ₯Ό μ„ μ–Έν•΄ μ£Όκ³ , 핸듀링 ν•¨μˆ˜λ₯Ό κ°œλ³„μ μœΌλ‘œ λ§Œλ“€κ³ , μ—λŸ¬λ₯Ό μœ„ν•œ μƒνƒœ, 검증을 μœ„ν•œ ν•¨μˆ˜ 등을 μ μš©ν•˜λŠ”λ° μ΄λ§ŒνΌμ΄λ‚˜ λ§Žμ€ μ½”λ“œλ“€μ΄ μ‚¬μš©λ©λ‹ˆλ‹€.

λ¬Όλ‘  λ‹€μ–‘ν•œ μž…λ ₯값듀을 ν•˜λ‚˜μ˜ state 객체λ₯Ό 톡해 κ΄€λ¦¬ν•˜κ³  κ³΅ν†΅μ˜ 이벀트λ₯Ό κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ onChange ν•¨μˆ˜λ₯Ό μ•„λž˜μ™€ 같이 λ¦¬νŒ©ν† λ§ν•˜μ—¬ μ‚¬μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

const [inputs, setInputs] = useState({
  username: '',
  password: '',
})

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  const { value, name } = event.target
  setInputs({
    ...inputs,
    [name]: value,
  })
}

ν•˜μ§€λ§Œ μž…λ ₯값이 λ‹€μ–‘ν•΄μ§ˆ 수둝 (이메일, νœ΄λŒ€μ „ν™”, μ£Όμ†Œ λ“±λ“±) 값에 λ”°λ₯Έ validation λ˜ν•œ λ‹€λ₯Ό 것이고 μ•ˆλ‚΄μ— ν•„μš”ν•œ μ—λŸ¬λ©”μ‹œμ§€λ„ μΆ”κ°€λ˜μ–΄μ•Ό ν•  κ²ƒμž…λ‹ˆλ‹€. μ΄λŸ¬ν•œ 점 λ•Œλ¬Έμ— μž‘μ„±ν•΄μ•Ό ν•  μ½”λ“œλ“€μ΄ 더 λ§Žμ•„μ§€κ³  onSubmit ν•¨μˆ˜μ˜ 둜직이 λ³΅μž‘ν•˜κ³  λΉ„λŒ€ν•΄ μ§€κ²Œ 될 κ²ƒμž…λ‹ˆλ‹€.

2-1. μ œμ–΄ μ»΄ν¬λ„ŒνŠΈμ˜ 문제 2

λ¨Όμ € μ•„μ‹œκ² μ§€λ§Œ λ¦¬μ•‘νŠΈμ—μ„œ λ¦¬λ Œλ”λ§μ΄ λ°œμƒν•˜λŠ” 쑰건에 λŒ€ν•΄ μ•Œμ•„λ³΄κ³  λ„˜μ–΄κ°€κ² μŠ΅λ‹ˆλ‹€.

  1. Props κ°€ λ³€κ²½λ˜μ—ˆμ„ λ•Œ
  2. State κ°€ λ³€κ²½λ˜μ—ˆμ„ λ•Œ (!)
  3. forceUpdate() λ₯Ό μ‹€ν–‰ν•˜μ˜€μ„ λ•Œ
  4. λΆ€λͺ¨ μ»΄ν¬λ„ŒνŠΈκ°€ λ Œλ”λ§ λ˜μ—ˆμ„ λ•Œ

μœ„μ—μ„œ μ˜ˆμ‹œλ‘œ μž‘μ„±ν•œ μ œμ–΄ μ»΄ν¬λ„ŒνŠΈμ—μ„œλŠ” λͺ¨λ“  값이 state 둜 κ°œλ³„ μ»΄ν¬λ„ŒνŠΈμ— μ—°κ²°λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 이둜 인해 ν•˜λ‚˜μ˜ 값이 λ³€ν•  λ•Œλ§ˆλ‹€ μ—¬λŸ¬ 개의 μžμ‹ μ»΄ν¬λ„ŒνŠΈ λ“€μ—μ„œ 무수히 λ§Žμ€ λ¦¬λ Œλ”λ§μ΄ λ°œμƒν•˜κ²Œ λ©λ‹ˆλ‹€.

예λ₯Ό λ“€λ©΄ μ‚¬μš©μžκ°€ username 의 μž…λ ₯값을 μž…λ ₯ν•˜λŠ” λ™μ•ˆ λ‹€λ₯Έ μžμ‹ μ»΄ν¬λ„ŒνŠΈ (예: Selectbox, DatePicker λ“±) λ“±μ˜ μ»΄ν¬λ„ŒνŠΈκ°€ λ¦¬λ Œλ”λ§μ΄ λ˜λŠ” 경우λ₯Ό μ˜λ―Έν•˜λ©° μ΄λŠ” λΆˆν•„μš”ν•œ λ Œλ”λ§μœΌλ‘œ μ„±λŠ₯μƒμ˜ 이슈λ₯Ό μ•ΌκΈ°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ•žμ„œ μ„€λͺ… λ“œλ Έλ“―μ΄ μ œμ–΄ μ»΄ν¬λ„ŒνŠΈλŠ” 화면에 μž…λ ₯ν•œ λ°μ΄ν„°μ˜ μƒνƒœμ™€ μ €μž₯ν•œ λ°μ΄ν„°μ˜ μƒνƒœκ°€ 항상 μΌμΉ˜ν•©λ‹ˆλ‹€. 이 말은 μ‚¬μš©μžκ°€ μž…λ ₯ν•˜λŠ” λͺ¨λ“  데이터가 동기화 λœλ‹€λŠ” μ˜λ―ΈμΈλ°μš”.

controlled components2

단어 ν•˜λ‚˜ ν•˜λ‚˜ μž…λ ₯ν•  λ•Œλ§ˆλ‹€ κ·Έ 값이 κ°±μ‹ λ˜μ–΄ 버리기 λ•Œλ¬Έμ— λΆˆν•„μš”ν•œ λ¦¬λ Œλ”λ§, λΆˆν•„μš”ν•œ API μš”μ²­μœΌλ‘œ μžμ› λ‚­λΉ„μ˜ 문제둜 연결될 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

λΆˆν•„μš”ν•œ μš”μ²­ 등을 λ°©μ§€ν•˜κΈ° μœ„ν•΄ μŠ€λ‘œν‹€λ§μ΄λ‚˜ λ””λ°”μš΄μ‹±μ„ μ‚¬μš©ν•  수 μžˆλŠ”λ° form 의 μš”μ†Œκ°€ 증가할 수둝 λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈμ— μŠ€λ‘œν‹€λ§μ΄λ‚˜ λ””λ°”μš΄μ‹±μ„ κ±ΈκΈ°λŠ” νž˜λ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

2-2. μ œμ–΄ μ»΄ν¬λ„ŒνŠΈ λŒ€μ‹  λΉ„μ œμ–΄ μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‚¬μš©ν•˜κΈ°

이제 μ•„λž˜μ™€ 같은 κ°œμ„ μ„ μœ„ν•΄ λΉ„μ œμ–΄ μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‚¬μš©ν•  수 있고 κ·Έ λŒ€ν‘œμ μΈ 라이브러리 쀑 ν•˜λ‚˜μΈ β€œreact-hook-form” 에 λŒ€ν•΄ μ„€λͺ…ν•  μ‹œκ°„μž…λ‹ˆλ‹€.

μ•„λž˜λŠ” λΉ„μ œμ–΄ μ»΄ν¬λ„ŒνŠΈμ˜ λŒ€ν‘œμ μΈ νŠΉμ§•μž…λ‹ˆλ‹€. μžμ„Έν•œ μ„€λͺ…은 λ‹€μŒ κΈ€μ—μ„œ 이어 λ‚˜κ°€ λ³΄κ² μŠ΅λ‹ˆλ‹€.

  • μ‚¬μš©λ˜λŠ” μ½”λ“œμ™€ λ‘œμ§μ„ κ°„μ†Œν™” ν•΄ 쀄 수 있고, μž…λ ₯κ°’κ³Ό μ—λŸ¬λ©”μ‹œμ§€λ₯Ό 직접 κ΄€λ¦¬ν•˜λŠ” 방법보닀 훨씬 직관적이고 κ°„κ²°ν•œ λ‘œμ§μ„ ꡬ성할 수 있으며
  • μ‚¬μš©μžκ°€ μž…λ ₯ν•˜κ±°λ‚˜ 값을 λ³€κ²½ν•  λ•Œ μΌμ–΄λ‚˜λŠ” λ¦¬λ Œλ”λ§μ„ 쀄여주며
  • μ œμ–΄ μ»΄ν¬λ„ŒνŠΈ 보닀 νŽ˜μ΄μ§€μ— λ§ˆμš΄νŠΈλ˜λŠ” 속도가 더 λΉ λ₯Έ μž₯점이 있음

μ˜ˆμ‹œ 전체 μ½”λ“œ

import React, { useState } from 'react'

const TestPage = () => {
  const [username, setUsername] = useState<string>('')
  const [password, setPassword] = useState<string>('')
  const [usernameError, setUsernameError] = useState<string>('')
  const [passwordError, setPasswordError] = useState<string>('')

  const handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    console.log(event.target.value)
    setUsername(event.target.value)
  }

  const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setPassword(event.target.value)
  }

  const validateName = (name: string) => {
    let isValidated = false

    if (name === '') {
      setUsernameError('이름을 μž…λ ₯ν•΄μ£Όμ„Έμš”.')
    } else if (name.length < 2) {
      setUsernameError('이름은 2자 이상 μž…λ ₯ν•΄μ£Όμ„Έμš”.')
    } else {
      isValidated = true
      setUsernameError('')
    }

    return isValidated
  }

  const validatePassword = (password: string) => {
    let isValidated = false

    if (password === '') {
      setPasswordError('λΉ„λ°€λ²ˆν˜Έλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.')
    } else if (password.length < 8) {
      setPasswordError('λΉ„λ°€λ²ˆν˜ΈλŠ” 8자 이상 μž…λ ₯ν•΄μ£Όμ„Έμš”.')
    } else {
      isValidated = true
      setPasswordError('')
    }

    return isValidated
  }

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    const isValidatedName = validateName(username)
    const isValidatedPassword = validatePassword(password)

    if (isValidatedName && isValidatedPassword) {
      console.log('username', username)
      console.log('password', password)
      setUsername('')
      setPassword('')
      setUsernameError('')
      setPasswordError('')
    }
  }
  return (
    <section>
      <h1 style={{ marginBottom: '12px' }}>μ œμ–΄ μ»΄ν¬λ„ŒνŠΈμ˜ μ˜ˆμ‹œμž…λ‹ˆλ‹€.</h1>
      <form onSubmit={handleSubmit}>
        <section>
          <label htmlFor="username">μ‚¬μš©μž 이름</label>
          <input
            id="username"
            name="username"
            type="text"
            onChange={handleUsernameChange}
            value={username}
          />
          {usernameError && (
            <span style={{ color: 'red' }}>{usernameError}</span>
          )}
        </section>
        <section>
          <label htmlFor="password">λΉ„λ°€λ²ˆν˜Έ</label>
          <input
            id="password"
            name="password"
            type="text"
            onChange={handlePasswordChange}
            value={password}
          />
          {passwordError && (
            <span style={{ color: 'red' }}>{passwordError}</span>
          )}
        </section>
        <button>Login</button>
      </form>
    </section>
  )
}

export default TestPage

Written by@[DotoriMook]
ν”„λ‘ νŠΈμ—”λ“œ μ£Όλ‹ˆμ–΄ 개발자 λ„ν† λ¦¬λ¬΅μ˜ 기술개발 λΈ”λ‘œκ·Έ μž…λ‹ˆλ‹€.

GitHubMediumTwitterFacebook