LoginSignup
54
48

More than 5 years have passed since last update.

React.Hooks で 大量の input 更新ハンドラーをまとめる小技

Last updated at Posted at 2019-03-06

Hooks で form 周りが大分楽になりましたね。さて、useState と useCallback を使って form を作るとき、大量の input 要素向けに callbackHandler と state を用意するのって、結構大変ですよね。たった 要素が4つでも、それなりにしんどいです。

import * as React from 'react'
import { useState, useCallback } from 'react'
// ___________________________________
//
// @ Types

type Props = {
  className?: string
  firstName: string
  lastName: string
  comment: string
  address: string
  onChangeFirstName: (event: React.ChangeEvent<HTMLInputElement>) => void
  onChangeLastName: (event: React.ChangeEvent<HTMLInputElement>) => void
  onChangeComment: (event: React.ChangeEvent<HTMLInputElement>) => void
  onChangeAddress: (event: React.ChangeEvent<HTMLInputElement>) => void
}
// ___________________________________
//
// @ Component

const Component: React.FC<Props> = props => (
  <form className={props.className}>
    <input
      name="first_name"
      type="text"
      value={props.firstName}
      onChange={props.onChangeFirstName}
    />
    <input
      name="last_name"
      type="text"
      value={props.lastName}
      onChange={props.onChangeLastName}
    />
    <input
      name="comment"
      type="text"
      value={props.comment}
      onChange={props.onChangeComment}
    />
    <input
      name="address"
      type="text"
      value={props.address}
      onChange={props.onChangeAddress}
    />
  </form>
)
// ___________________________________
//
// @ Container

const Container: React.FC = () => {
  const [firstName, setFirstName] = useState('')
  const [lastName, setLastName] = useState('')
  const [comment, setComment] = useState('')
  const [address, setAddress] = useState('')
  const onChangeFirstName = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setFirstName(event.target.value)
    },
    [firstName]
  )
  const onChangeLastName = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setLastName(event.target.value)
    },
    [lastName]
  )
  const onChangeComment = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setComment(event.target.value)
    },
    [comment]
  )
  const onChangeAddress = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setAddress(event.target.value)
    },
    [address]
  )
  return (
    <Component
      firstName={firstName}
      lastName={lastName}
      comment={comment}
      address={address}
      onChangeFirstName={onChangeFirstName}
      onChangeLastName={onChangeLastName}
      onChangeComment={onChangeComment}
      onChangeAddress={onChangeAddress}
    />
  )
}

export default Container

解決策

  • state を一つの object にまとめる
  • state の key は、name属性にあわせる

こうすることで、update関数での更新処理が [event.target.name]: event.target.value で済みます。

import * as React from 'react'
import { useState, useCallback } from 'react'
// ___________________________________
//
// @ Types

type State = {
  first_name: string
  last_name: string
  comment: string
  address: string
}
type Props = {
  className?: string
  onChangeInputText: (event: React.ChangeEvent<HTMLInputElement>) => void
} & State
// ___________________________________
//
// @ Component

const Component: React.FC<Props> = props => (
  <form className={props.className}>
    {(['first_name', 'last_name', 'comment', 'address'] as const).map(name => (
      <input
        key={name}
        name={name}
        type="text"
        value={props[name]}
        onChange={props.onChangeInputText}
      />
    ))}
  </form>
)
// ___________________________________
//
// @ Container

const Container: React.FC = () => {
  const [state, update] = useState<State>({
    first_name: '',
    last_name: '',
    comment: '',
    address: ''
  })
  const onChangeInputText = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      event.persist()
      update(prev => ({
        ...prev,
        [event.target.name]: event.target.value
      }))
    },
    []
  )
  return <Component {...state} onChangeInputText={onChangeInputText} />
}

export default Container

余談

value={props[name]} が通用しているのは、このコードが TypeScript3.4 で書いているためです。
as const を使うことで String Literal Types になるので、props を参照することができています。

54
48
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
48