useCallback の依存リストに props.onChange を渡すと ESLint の警告が出る
- react
useCallback
を使ってコールバックのメモ化を行うとき、依存リストに依存している値を渡します。以下の例では、この依存リストにprops.onChange
を渡しています。
type Props = {
value: string;
onChange: (value: string) => void;
};
const Child: React.FC<Props> = (props) => {
const onChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
props.onChange(event.target.value);
},
[props.onChange],
);
return (
<input value={props.value} onChange={onChange} />
);
};
const Parent: React.FC = () => {
const [value, setValue] = React.useState<string>('');
const onChange = React.useCallback((value: string) => {
// do something
}, []);
return (
<Child value={value} onChange={onChange} />
);
};
一見問題なさそうですが、ESLint のreact-hooks/exhaustive-deps
の警告が出てしまいます。警告の内容は次の通りです。
React Hook React.useCallback has a missing dependency: 'props'. Either include it or remove the dependency array. However, 'props' will change when any prop changes, so the preferred fix is to destructure the 'props' object outside of the useCallback call and refer to those specific props inside React.useCallback react-hooks/exhaustive-deps
どうやらprops.onChange
ではなくprops
を依存リストに渡す必要があるようです。しかし、props
を依存リストに渡してしまうとonChange
以外のprops
のプロパティ(この例ではvalue
)に変更があった場合もコールバックが生成されてしまいます。そのため、useCallback
の外で分割代入を行い、onChange
を依存リストに追加するのが望ましいようです。
const Child: React.FC<Props> = ({ value, onChange }) => {
const handleChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value);
},
[onChange],
);
return (
<input value={value} onChange={handleChange} />
);
};
props
ではなくonChange
を依存リストに渡すのが望ましいのは理解できます。しかし、そもそもprops.onChange
では警告が出てしまう理由が不明だったため、調べてみました。
The reason plugin sees it differently is because by doing
props.whatever()
you are actually passingprops
itself as athis
value towhatever
. So technically it would see stale props. https://github.com/facebook/react/issues/16265
props.foo()
marksprops
as a dependency because it has athis
value. https://github.com/facebook/react/blob/487c693846dbe58a68a410c658699d27386323b3/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L966
props.onChange()
はオブジェクトprops
のメソッドonChange
を呼び出す形を取るため、this
にはprops
が設定されます。つまり、onChange
はprops
の別のプロパティをthis
経由で参照している可能性があり、props.onChange
のみを依存リストに渡してしまうと、その別のプロパティの変更を検知できないことになります。そのため、依存リストにprops
全体を渡すよう警告を出しているようです。
なお、分割代入を行った場合、props.onChange
を変数onChange
に代入するため、onChange
内のthis
はundefined
となります。そのため、props.onChange
と異なりprops
を依存リストに渡す必要はありません。
参考
- Warning for 'exhaustive-deps' keeps asking for the full 'props' object instead of allowing single 'props' properties as dependencies
- https://github.com/facebook/react/blob/487c693846dbe58a68a410c658699d27386323b3/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L966
- this - JavaScript | MDN
- 分割代入 - JavaScript | MDN