フック出力の素通し props を getInputProps() で束ねる
TL;DR
- フックがバラバラの値を返し、親コンポーネントが個別に props として受け渡すと、素通し props が増えて Props 過多になる
- フック側で
getInputProps()を返し、value/onChange/onKeyDown/onBlur/ref/aria-*を 1 オブジェクトに束ねる - コンポーネントは
{...getInputProps()}で spread するだけ。Downshift / React Hook Form と同じアプローチ
問題
ColumnNameInput が props を 13 個(value / onChange / onKeyDown / onBlur / disabled / aria-* / inputRef / errorId …)持ち、そのほぼ全部がフック出力の素通しだった。
Props 過多を解消しようとして、フックの戻り値を個別に受け渡す形にした結果、コンポーネント側で別の props 過多を作ってしまう。本末転倒。
原因
フックがバラバラの値を返し、親コンポーネントがそれを個別に受け取って子へ渡す、という手続き的なパターンになっていた。
フックの出力とコンポーネントの入力が 1:1 で対応しているのに、その対応を「個別の props」という粒度で毎回手で繋いでいるのが無駄。フックの内部関心(どの属性を input に渡すべきか)がコンポーネント側に漏れている。
解決
フック側で getInputProps() メソッドを返し、input に渡すべき属性(value / onChange / onKeyDown / onBlur / ref / aria-*)を 1 オブジェクトに束ねる。コンポーネント側は spread するだけ。
Before(素通し props が並ぶ)
function useColumnNameInput(/* ... */) {
// ...
return { value, onChange, onKeyDown, onBlur, disabled, ref, ariaInvalid, errorId /* ... */ };
}
function ColumnNameInput() {
const {
value, onChange, onKeyDown, onBlur, disabled, ref, ariaInvalid, errorId,
} = useColumnNameInput(/* ... */);
return (
<input
value={value}
onChange={onChange}
onKeyDown={onKeyDown}
onBlur={onBlur}
disabled={disabled}
ref={ref}
aria-invalid={ariaInvalid}
aria-describedby={errorId}
/>
);
}
After(getInputProps で束ねる)
function useColumnNameInput(/* ... */) {
// ...
const getInputProps = () => ({
value,
onChange,
onKeyDown,
onBlur,
ref,
"aria-invalid": ariaInvalid,
"aria-describedby": errorId,
});
return { getInputProps /* 表示用の値など、束ねられないものだけ個別に */ };
}
function ColumnNameInput() {
const { getInputProps } = useColumnNameInput(/* ... */);
return <input {...getInputProps()} />;
}
Props は 13 → 5 に削減。フックの内部関心(どの属性を input に渡すか)がフック側に閉じ、コンポーネントは spread するだけになる。
補足
- これは Downshift(
getInputProps/getMenuProps…)や React Hook Form(registerの返り値 spread)と同じアプローチ。「props 返し hook」の慣行に沿っている。 - 束ねるのは「そのまま同じ要素に渡る属性群」だけ。表示ロジックや条件分岐が必要な値まで無理に束ねない。
getInputProps({ onChange: myHandler })のように呼び出し側の値をマージできる形にしておくと、イベントハンドラの合成にも対応できる。