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 を参照することができています。