React + TypeScript: useCallbackフックの使い方と使いどころ

[`useCallback`](https://react.dev/reference/react/useCallback)は、再レンダリング間で関数定義をキャッシュするReactのフックです。

本稿はReact公式サイト「[`useCallback`](https://react.dev/reference/react/useCallback)」にもとづき、useCallbackはどう使うのか、およびどのような場合に使うとよいのかを解説します。説明内容と順序は、公式ドキュメントにしたがいました。ただし、解説はわかりやすく改め、またコード例とサンプル([StackBlitz](https://stackblitz.com/))はTypeScriptを加えたうえで修正した部分が少なくありません。


# 構文

```react
const cachedFn = useCallback(fn, dependencies)
```

`useCallback`はコンポーネントのトップレベルで呼び出して、再レンダリング間で関数の定義をキャッシュします。

```react
import { useCallback } from 'react';
import type { FC } from 'react';

type Props = {

};
export const ProductPage: FC<Props> = ({ productId, referrer, theme }) => {
	const handleSubmit = useCallback((orderDetails) => {
		post('/product/' + productId + '/buy', {
			referrer,
			orderDetails,
		});
	}, [productId, referrer]);

};
```

### 引数
* `fn`: キャッシュする関数型の値。渡す引数も戻り値も任意です。Reactは、まず最初のレンダー時には関数をそのまま返します(呼び出しはしません)。次回以降のレンダリングについてはつぎのとおりです。
    - 直前のレンダー時と依存値(第2引数の配列要素値)が変わっていないとき: Reactは前と同じ関数を返します。
    - 直前のレンダー時から依存値が変わった場合: 返されるのは今回のレンダー時に渡された関数です。そして、再利用に備えて保存されます。Reactは関数を返すだけで、呼び出しはしません。関数をいつ呼び出すかは、開発者が決めることです。
* `dependencies`: 第1引数`fn`のコード内で参照されるすべてのリアクティブ値の(依存)配列。リアクティブな値に含まれるのは、プロパティと状態、およびコンポーネント本体に直接宣言された変数と関数です。[React用に設定](https://ja.react.dev/learn/editor-setup#linting)されたリンターであれば、リアクティブな値がすべて依存関係に正しく指定されているかを確かめます。依存配列には、特定の依存値が要素として含まれなければなりません。インラインで、[`dep1`, `dep2`, `dep3`]という記述です。Reactは、各依存値を直前の値と[`Object.is`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/is)メソッドで比較します。

### 戻り値
最初のレンダー時は、`useCallback`に渡された引数の関数`fn`です。

以降のレンダーでは、依存値が変わったかどうかにより、返す値は変わります。

* 依存値に変更がない場合: 返されるのは前のレンダリング時に保存した関数です。
* 依存値が変わったとき: このレンダー時に渡された関数`fn`が返されます。

### 注意
* `useCallback`はフックなので、[呼び出せるのは**コンポーネントまたはカスタムフックのトップレベルからのみ**です](https://ja.react.dev/reference/rules/rules-of-hooks#only-call-hooks-at-the-top-level)。ループ文や条件文の中からは呼び出しできません。それが必要なときは、コンポーネントを新たに切り出し、状態はその中に移してください。
* Reactは、**特別な理由がないかぎり、キャッシュされた値を破棄しません**。開発環境ならReactは、たとえばコンポーネントのファイルが編集されたら、キャッシュを破棄します。開発と本番の両環境で、Reactがキャッシュを破棄するのは、コンポーネントの初期マウント完了に至らなかった場合です。
Reactは、将来的にキャッシュの破棄が利用できる機能をさらに追加することもありえます。たとえば、仮想化リストの組み込みサポートを加えるようになった場合です。仮想化されたテーブルビューポートからスクロールアウトするアイテムのキャッシュを破棄することは意味があるでしょう。
パフォーマンスの最適化が目的で`useCallback`を用いることは問題ありません。そうでない場合は、[状態変数](https://ja.react.dev/reference/react/useState#im-trying-to-set-state-to-a-function-but-it-gets-called-instead)や[`ref`](https://ja.react.dev/reference/react/useRef#avoiding-recreating-the-ref-contents)を活用する方がより適切なこともありえます。


# 使い方
## コンポーネントの再レンダーを省く
### 子コンポーネントに渡す関数を`useCallback`でキャッシュする
レンダーのパフォーマンス最適化のために、子コンポーネントに渡す関数をキャッシュすることが役立つかもしれません。コンポーネントの再レンダー間で関数をキャッシュするには、その定義を`useCallback`フックで包んでください。

```react
export const ProductPage: FC<Props> = ({ productId, referrer, theme }) => {
	const handleSubmit = useCallback((orderDetails) => {
		post('/product/' + productId + '/buy', {
			referrer,
			orderDetails,
		});
	}, [productId, referrer]);

};
```

`useCallback`に渡す引数はふたつです。

1. 関数定義: 再レンダー間でキャッシュする関数。
2. 依存関係のリスト: 関数が用いるコンポーネント内のすべての依存値を配列で渡します。

初期レンダー時: `useCallback`から返される関数は、フックを呼び出すときに渡した第1引数の定義そのものです。

次回以降のレンダー時: Reactは各レンダーごとに、依存関係を直近に渡された依存値と比較します。依存関係に([Object.is()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/is)による比較で)変わりなければ、`useCallback`が返すのは前回と同じ関数です。変更があったら、フックは今回のレンダーで渡された新たな関数を返します。

つまり、`useCallback`は、依存関係が変わらないかぎり、再レンダリングの間関数定義をキャッシュするのです。

### `useCallback`でキャッシュした関数を`memo`に包んだコンポーネントへ渡す
`useCallback`がどう役立つのか、コード例で見ていきましょう。つぎのモジュール`src/ProductPage.tsx`は、子コンポーネント`ShippingForm`に`onSubmit`プロパティの値として関数`handleSubmit`を渡しています。

```react:src/ProductPage.tsx
export const ProductPage: FC<Props> = ({ productId, referrer, theme }) => {
	const handleSubmit: (orderDetails: OrderDetails) => void = (orderDetails) => {
		post('/product/' + productId + '/buy', {
			referrer,
			orderDetails,
		});
	};
	return (
		<div className={theme}>
			<ShippingForm onSubmit={handleSubmit} />
		</div>
	);
};
```

また、`ProductPage`コンポーネントのプロパティ`theme`は、親(`App.tsx`)から受け取った値です。

```react:src/App.tsx
export default function App() {
	const [isDark, setIsDark] = useState(false);
	return (
		<>
			<label>
				<input
					type="checkbox"
					checked={isDark}
					onChange={({ target: { checked } }) => setIsDark(checked)}
				/>
				Dark mode
			</label>
			<hr />
			<ProductPage

				theme={isDark ? 'dark' : 'light'}
			/>
		</>
	);
}
```

つぎのサンプル001で親コンポーネントのチェックボックス(`<input type="checkbox"`)を切り替えたとき、画面カラーの反応が遅く感じるかもしれません(あえて、`ShippingForm`コンポーネントのレンダーに遅延を加えました)。

#### サンプル001■React + TypeScript: useCallback 01
https://stackblitz.com/edit/vitejs-vite-par8pl?file=src%2FApp.tsx

**デフォルトでは、コンポーネントがレンダーされると、Reactはその子要素すべてを再帰的に再レンダーします**。そのため、`ProductPage`が異なる`theme`でレンダリングし直されると、`ShippingForm`コンポーネントも再レンダーされるのです。

もっとも、レンダリング負荷の少ないコンポーネントであれば、とくに問題ありません。再レンダーはできるだけ省きたいというとき、ひとつ考えられるのはコンポーネントを[`memo`](https://ja.react.dev/reference/react/memo)でラップすることです。渡されたプロパティ(`props`)が前回のレンダー時と同じ場合には、再レンダーを省けます。

```react:src/ShippingForm.tsx
import { memo, useState } from 'react';

export const ShippingForm: FC<Props> = memo(({ onSubmit }) => {

});
```

コンポーネント`ShippingForm`の受け取るプロパティ(`props`)がすべて同じなら、再レンダーされなくなりました。ただし、ここで重要になってくるのが関数のキャッシュです。まだ、`ProductPage`コンポーネントが子へ渡す関数(`handleSubmit`)に`useCallback`は使っていません。

```react:src/ProductPage.tsx
export const ProductPage: FC<Props> = ({ productId, referrer, theme }) => {

	const handleSubmit: (orderDetails: OrderDetails) => void = (orderDetails) => {

	};
	return (
		<div className={theme}>
			<ShippingForm onSubmit={handleSubmit} />
		</div>
	);
};
```

このままでは、`ProductPage`が再レンダーされるたびに、`handleSubmit`は**新たな関数定義として扱われてしまいます**。オブジェクトリテラルで、中身はまったく同じでも、参照が異なればJavaScriptからは別物とみなされるのと同じです。コンポーネントのプロパティ(`props`)として渡される関数も、中身が変わらなくても、同じとは評価されません。

そこで、必要なのが`useCallback`です。**フックで包めば、依存配列が変わらないかぎり、つねに同じ関数が返されます**。

```react:src/ProductPage.tsx
export const ProductPage: FC<Props> = ({ productId, referrer, theme }) => {
	// const handleSubmit: (orderDetails: OrderDetails) => void = (orderDetails) => {
	const handleSubmit: (orderDetails: OrderDetails) => void = useCallback(
		(orderDetails) => {

		},
		[productId, referrer]
	);

};
```

この変更を加えたサンプル002は、チェックボックス(`theme`)の反応が速くなったでしょう。この例は、`useCallback`でラップした関数を、`memo`に包んだコンポーネントへ渡す手法のご紹介でした。ただ、とくに理由がなければ`useCallback`は使わなくて構いません。

#### サンプル002■React + TypeScript: useCallback 02
https://stackblitz.com/edit/vitejs-vite-raiigu?file=src%2FApp.tsx

[注記] **`useCallback`はパフォーマンスを最適化するためにお使いください**。フックなしにはコードが動かないという場合は、まず原因を探って修正するべきです。そのうえで、必要があれば`useCallback`を用いましょう。

## `useCallback`と`useMemo`
`useCallback`と似た機能を果たすフックに`useMemo`があります。どちらも、子コンポーネントを最適化するために有用です。両フックともに、プロパティとして渡す値を[メモ化](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E5%8C%96)できます。

```react:ProductPage.tsx
import { useMemo, useCallback } from 'react';

export const ProductPage: FC<Props> = ({ productId, referrer, theme }) => {
	const product = useData('/product/' + productId);

	const requirements = useMemo(() => { // 関数を呼び出して結果はキャッシュする
		return computeRequirements(product);
	}, [product]);

	const handleSubmit: (orderDetails: OrderDetails) => void = useCallback(
		(orderDetails) => {

		},
		[productId, referrer]
	);

	return (
		<div className={theme}>
			<ShippingForm requirements={requirements} onSubmit={handleSubmit} />
		</div>
	);
};
```

ふたつのフックの違いは、キャッシュする中身です。

* [`useMemo`](https://qiita.com/FumioNonaka/items/86f6a91b98844beaff0d): **引数の関数を呼び出した戻り値がキャッシュされます**。
    - 上記コード例では、依存配列の値`product`が変わらないかぎり、`computeRequirements(product)`は、再呼び出しされません。前にキャッシュした戻り値が用いられるのです。
    - `requirements`プロパティを渡された子コンポーネント`ShippingForm`は、無駄に再レンダーされません。
    - 依存値`product`が変わったとき、Reactはレンダー中に引数の関数を再呼び出して返します。
* `useCallback`: **キャッシュするのは引数の関数そのものです**。関数は呼び出しません。
    - 上記コード例では、依存配列の値`productId`または`referrer`が変わらないかぎり、関数`handleSubmit`は、キャッシュした定義が用いられます。
    - `onSubmit`のコールバックとして`handleSubmit`を渡された子コンポーネント`ShippingForm`は、無駄に再レンダーされません。
    - 関数`handleSubmit`が呼び出されるのは、子コンポーネント`ShippingForm`の[Submit]ボタンを押したとき(`onSubmit`イベント)です。

すでに[`useMemo`](https://qiita.com/FumioNonaka/items/86f6a91b98844beaff0d)フックをご存じであれば、`useCallback`はつぎのように捉えればよいでしょう(「[関数をメモ化する](https://ja.react.dev/reference/react/useMemo#memoizing-a-function)」参照)。なお、TypeScriptによる型づけは省きました(興味のある方は「[useCallback](https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/hooks/#usecallback)」をお読みください)。

```react
// 簡略化したReact内部の実装
function useCallback(fn, dependencies) {
	return useMemo(() => fn, dependencies);
}
```

### `useCallback`はどこに使うべきか
メモ化はどこに用いるべきで、どういうときは不要か、大まかに比べるなら大きくふたつです。

* インタラクションがさほど多くなければ、`useCallback`を使わななくて構いません。
    - ページの切り替えや画面の一部の変更で済む場合。
* 細かなインタラクションが頻繁に生じるときは、メモ化は有効でしょう。
    - 描画エディターのアプリケーションで、図形を移動させるなど。

#### `useCallback`で関数をキャッシュすることが役立つ場合
`useCallback`で関数をキャッシュすることが役立つのは、具体的にはつぎのふたつの例でしょう。

* [`memo`](https://ja.react.dev/reference/react/memo)で包まれた子コンポーネントに関数をプロパティ(`props`)として渡す場合。
    - `useCallback`の依存値が変わらないかぎり、子コンポーネントは再レンダーされない。
    - 依存値が変わったときだけ、レンダリングされる。
* 関数が他のフックの依存値に用いられている場合。
    - 他の`useCallback`でラップされた関数が依存している。
    - `useEffect`の依存配列に関数が含まれる。

これらの場合以外は、関数を`useCallback`でラップしても、効果はないでしょう。かといって、問題となることも考えにくいです。とりあえず、メモ化してしまうという開発チームもあります。デメリットとしては、コードが読みにくくなることです。また、レンダーごとに変わる値がひとつでも関数に含まれていれば、メモ化する意味はありません。

`useCallback`は、関数をつくらないわけではありません。関数はつねに定義されます(それは問題とはなりません)。ただし、前回のレンダーと変わりなければ、Reactにより新たな定義は無視され、キャッシュされた関数が返されるのです。

#### 不要なメモ化を避けるための5つの原則
メモ化はつねに有効とはかぎりません。**つぎの5つの原則にしたがえば、不要なメモ化が避けられるでしょう**。

1. コンポーネントが子コンポーネントを視覚的にラップするときは、[子のJSXは`children`として受け取る](https://ja.react.dev/learn/passing-props-to-a-component#passing-jsx-as-children)ようにします。子を包む親コンポーネントが自身の状態を更新しても、Reactは子の再レンダーは要らないとわかるからです。
2. 状態はできるだけローカルに持ち、無闇にコンポーネントツリー上を引き上げないようにします(「[React + TypeScript: コンポーネント間で状態を共有する](https://qiita.com/FumioNonaka/items/611bd5e0cd254c0e9ea0)」参照)。フォーム入力や項目へのホバーといった状態は、頻繁に変わるインタラクションです。ツリーのトップやグローバルの状態ライブラリに保持しないでください。
3. [レンダーロジックを純粋に保ちましょう](https://qiita.com/FumioNonaka/items/2ba530a906bf97480f80#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92%E7%B4%94%E7%B2%8B%E3%81%AB%E4%BF%9D%E3%81%A4keeping-components-pure)。コンポーネントの再レンダーで意図しない動きになったり、表示に明らかな問題が起こるとすれば、それはバグです。メモ化で避けようとするのでなく、原因をつきとめて修正してください。
4. [不必要に状態を更新するエフェクトは避けましょう](https://qiita.com/FumioNonaka/items/f6bfe4ca77e590f9e73f)。Reactアプリケーションでパフォーマンスの問題を引き起こす原因の多くが、エフェクトによる連鎖的な状態の更新です。コンポーネントはそのたびに再レンダーされてしまいます。
5. [エフェクトから要らない依存値は除いてください](https://qiita.com/FumioNonaka/items/1ca250ec6bf7234a7afd)。メモ化しなくても、オブジェクトや関数をエフェクトの中あるいは外に移すだけで、簡単に解決できることもあります。

以上の原則にしたがっても、遅いと感じるインタラクションが残るかもしれません。そのような場合には、[React Developer Tools](https://ja.react.dev/learn/react-developer-tools)のProfilerパネルをお使いください。どのコンポーネントをメモ化すればもっとも効果的かが確かめられます。このように開発を進めることで、デバッグしやすく、わかりやすいコードが書けるでしょう。[React公式サイトが推奨する](https://ja.react.dev/reference/react/useCallback#usage:~:text=%E3%81%93%E3%82%8C%E3%82%89%E3%81%AE%E5%8E%9F%E5%89%87%E3%82%92%E5%AE%88%E3%82%8B%E3%81%93%E3%81%A8%E3%81%A7%E3%80%81%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E3%82%84%E7%90%86%E8%A7%A3%E3%81%8C%E5%AE%B9%E6%98%93%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%9F%E3%82%81%E3%80%81%E5%B8%B8%E3%81%AB%E5%8E%9F%E5%89%87%E3%81%AB%E5%BE%93%E3%81%86%E3%81%93%E3%81%A8%E3%82%92%E3%81%8A%E3%81%99%E3%81%99%E3%82%81%E3%81%97%E3%81%BE%E3%81%99%E3%80%82)理由です。将来に向けては、自動的にメモ化する研究も進められています(「[React without memo](https://www.youtube.com/watch?v=lGEMwh32soc)」参照)。

### 関数に`useCallback`を用いるかどうかによる違い
前掲サンプル001は`useCallback`なし、サンプル002には`useCallback`を用いました。どちらも、コンポーネント`ShippingForm`は、[`memo`](https://ja.react.dev/reference/react/memo)で包んでいます。また、`ShippingForm`の描画には、あえて負荷をかけました。ふたつのサンプルの違いを改めて確かめましょう。

#### `useCallback`で子コンポーネントの無駄な再レンダーを省いた場合
サンプル002では、親コンポーネント(`App.tsx`)のチェックボックス([Dark mode])で切り替えるプロパティ`theme`の画面への反映は速やかです。プロパティを渡されたコンポーネント`ProductPage`の`handleSubmit`には、**`useCallback`が用いられており、`theme`の値には依存しません**。したがって、子コンポーネント`ShippingForm`に与えるプロパティ`handleSubmit`は変わらず、再レンダーされないからです。**`memo`による`ShippingForm`のラップが有効に働きました**。

他方で、カウンターの増減は反応が遅いです。カウンターの値(`count`)は`ShippingForm`コンポーネントの状態なので、再レンダーせざるを得ません。このコード例では予期された動きです。

#### 関数に`useCallback`を使わない場合
サンプル001でも、カウンターの増減は反応が遅いです(理由はサンプル002と同じ)。さらに、チェックボックス([Dark mode])で切り替えるプロパティ`theme`の画面への反映まで遅延します。**`useCallback`を使わない関数`handleSubmit`は、処理がまったく変わらなくても、つねに新たな関数として定義される**からです。`ShippingForm`コンポーネントは、そのたびに再レンダーされます。

ここで、`ShippingForm`コンポーネントの意図的な遅延を除いてみましょう。反応の遅れは気にするほどのものですか。インタラクションの速さが問題にならないかぎり、あえてメモ化するには及びません。

```react:src/ShippingForm.tsx
export const ShippingForm: FC<Props> = ({ onSubmit }) => {

	/* let startTime = performance.now();
	while (performance.now() - startTime < 500) {
		// 意図的にコードを遅くするため500ミリ秒何もしない
	} */

};
```

なお、アプリケーションの遅れを根本的に解決するには、Reactは本番モードで実行すべきです。[React Developer Tools](https://ja.react.dev/learn/react-developer-tools)も無効にし、想定するユーザーの実機で確かめなければなりません。

### メモ化されたコールバックから状態を更新する
メモ化されたコールバックから、直前の状態を更新したい場合があり得ます。たとえば、つぎのコード例の`handleAddTodo`関数です。新たなTodoリストをつくるため、状態変数`todos`に依存します。

```react
const TodoList = () => {
	const [todos, setTodos] = useState<Todo[]>([]);

	const handleAddTodo = useCallback((text: string) => {
		const newTodo = { id: nextId++, text };
		setTodos([...todos, newTodo]);
	}, [todos]);

};
```

メモ化した関数の依存値は、できるかぎり減らしたいところです。直近の状態から新たな状態を単純に定める場合、状態設定関数(`setTodos`)には[更新用関数](https://ja.react.dev/reference/react/useState#updating-state-based-on-the-previous-state)が渡せます。関数が引数に受け取るのは、現在の状態値です。これで、`useCallback`から状態変数`todos`への依存が除けます。

```react
const TodoList = () => {
	const [todos, setTodos] = useState<Todo[]>([]);

	const handleAddTodo = useCallback((text: string) => {
		const newTodo = { id: nextId++, text };
		// setTodos([...todos, newTodo]);
		setTodos((todos) => [...todos, newTodo]);
	// }, [todos]);
	}, []); // ✅ todosへの依存は不要

};
```

### エフェクトが無駄に実行されるのを防ぐ
エフェクトの中から関数を呼び出したいこともあるでしょう。けれど、つぎのコードは問題です。[すべてのリアクティブな値は、依存配列に含めなければなりません](https://qiita.com/FumioNonaka/items/a24567403fd7bb20be55#react%E3%81%8C%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%AA%E3%82%A2%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96%E3%81%AA%E5%80%A4%E3%82%92%E4%BE%9D%E5%AD%98%E9%96%A2%E4%BF%82%E3%81%AB%E5%90%AB%E3%82%81%E3%81%9F%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E7%A2%BA%E3%82%81%E3%82%8B)。`createOptions`を依存値として宣言すると、エフェクトがつねにチャットルームに再接続してしまうからです。

```react
const ChatRoom: FC<Props> = ({ roomId }) => {

	const createOptions = () => {
		return {
			serverUrl: 'https://localhost:1234',
			roomId
		};
	}

	useEffect(() => {
		const options = createOptions();
		const connection = createConnection();
		connection.connect();
		return () => connection.disconnect();
	}, [createOptions]); // 🔴 NG: 依存がレンダーのたびに変わる

};
```

これを解決するには、エフェクトから呼び出さなければならない関数は`useCallback`で包んでしまえばよいでしょう。`useCallback`の依存値`roomId`が変わらないかぎり、再レンダー間で関数`createOptions`は同じだと保証されるからです。

```react
const ChatRoom: FC<Props> = ({ roomId }) => {

	// const createOptions = () => {
	const createOptions = useCallback(() => {
		return {
			serverUrl: 'https://localhost:1234',
			roomId: roomId
		};
	// }
	}, [roomId]); // ✅ OK: 依存値roomIdが変わると更新される

	useEffect(() => {
		const options = createOptions();

	}, [createOptions]); // ✅ OK: メモ化されたcreateOptionsが変わったときのみ更新される

};
```

もっとも、**関数型の依存値を除ければさらに望ましいでしょう**。このコード例の場合、関数`createOptions`はエフェクトの中に移せます。`useCallback`は使わずに済み、コードもシンプルになりました(「[リアクティブなオブジェクトや関数をエフェクトの中に含める](https://qiita.com/FumioNonaka/items/1ca250ec6bf7234a7afd#%E3%83%AA%E3%82%A2%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96%E3%81%AA%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%84%E9%96%A2%E6%95%B0%E3%82%92%E3%82%A8%E3%83%95%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%B8%AD%E3%81%AB%E5%90%AB%E3%82%81%E3%82%8B)」参照)。

```react
const ChatRoom: FC<Props> = ({ roomId }) => {

	useEffect(() => {
 		const createOptions = () => { // ✅ OK: useCallbackは使わない
			return {
				serverUrl: 'https://localhost:1234',
				roomId: roomId
		};

		const options = createOptions();

	}, [roomId]); // ✅ OK: roomIdが変わったときのみ更新される

};
```

### カスタムフックを最適化する
[カスタムフック](https://qiita.com/FumioNonaka/items/34a2b6dfa8a6edc14f23)を書く場合、返す関数はすべて`useCallback`でラップしてください。利用する開発者が、必要に応じてコードを最適化できるからです。

```react
const useRouter = () => {
	const { dispatch } = useContext(RouterStateContext);
	const navigate = useCallback((url: string) => {
		dispatch({ type: 'navigate', url });
	}, [dispatch]);
	const goBack = useCallback(() => {
		dispatch({ type: 'back' });
	}, [dispatch]);
	return {
		navigate,
		goBack,
	};
};
```


# トラブルへの対応 
## コンポーネントがレンダーされるたびに`useCallback`から返される関数が異なる
コンポーネントがレンダーされるたびに`useCallback`から返される関数が異なるという場合は、まず第2引数の依存配列を忘れていないか確かめましょう。第2引数がなければ、`useCallback`の戻り値となる関数はレンダーのたびに新たになります。

```react
const ProductPage: FC<Props> = ({ productId, referrer }) => {

	const handleSubmit = useCallback((orderDetails) => {
		post('/product/' + productId + '/buy', {
			referrer,
			orderDetails,
		});
	}); // 🔴 NG: 依存配列が与えられないとつねに新たな関数が返される

};
```

`useCallback`に、依存配列は正しく与えなければなりません。

```react
const ProductPage: FC<Props> = ({ productId, referrer }) => {

	const handleSubmit = useCallback((orderDetails) => {
		post('/product/' + productId + '/buy', {
			referrer,
			orderDetails,
		});
	// });
	}, [productId, referrer]); // ✅ OK: 依存が変わらないかぎり戻り値の関数は同じ

};
```

それでもレンダーのたびに`useCallback`から異なる関数が返されるなら、依存値の少なくともひとつは毎回変わっているということです。その値をつきとめなければなりません。コンポーネントから依存値を`console.log`でコンソールに出力しましょう。

```react
const ProductPage: FC<Props> = ({ productId, referrer }) => {

	const handleSubmit = useCallback((orderDetails) => {

	}, [productId, referrer]);
	console.log([productId, referrer]);

};
```

異なる再レンダーからコンソールにログ出力された値を右クリックすると、[グローバル変数として保存]が選べます。最初の値が`temp1`、2番目は`temp2`といった具合です。すると、ブラウザコンソールでそれらの値を`Object.is`で同じかどうか確かめられます。

```console
// 2回のレンダー間で配列要素が一致しているかを確かめる
Object.is(temp1[0], temp2[0]);
Object.is(temp1[1], temp2[1]);
Object.is(temp1[2], temp2[2]);
```

メモ化を損なう依存値があったら、除いてください。あるいは、[その値をメモ化しましょう](https://ja.react.dev/reference/react/useMemo#memoizing-a-dependency-of-another-hook)。

## ループ内のリスト項目から`useCallback`は呼び出せない
リスト項目からループ処理で、複数の子コンポーネントをつくる場合の問題です。以下の`ReportList`は、配列(`items`)から`map`で子コンポーネント`Chart`を生成しています。`Chart`は、[`memo`](https://ja.react.dev/reference/react/memo)で包まれている想定です。子にプロパティ(`onClick`)として渡している関数`handleClick`はメモ化して、すべての子コンポーネントが無駄に再レンダーされるのを避けたい場合もあるでしょう。

けれど、ループ処理の中で`useCallback`は呼び出せません。[フックが使えるのはReact関数のトップレベルのみ](https://ja.react.dev/reference/rules/rules-of-hooks#only-call-hooks-at-the-top-level)だからです。

```react
const ReportList: FC<Props> = ({ items }) => {
	return (
		<article>
			{items.map((item) => {
				// 🔴 NG: ループの中からuseCallbackは呼び出せない
				const handleClick = useCallback(() => {
					sendReport(item)
				}, [item]);
				return (
					<figure key={item.id}>
						<Chart onClick={handleClick} />
					</figure>
				);
			})}
		</article>
	);
};
```

この場合、`useCallback`をトップレベルで呼び出せるよう、JSXとともに別コンポーネント(`Report`)に切り出せばよいでしょう。

```react
const ReportList: FC<Props> = ({ items }) => {
	return (
		<article>
			{items.map((item) =>
				<Report key={item.id} item={item} />
			)}
		</article>
	);
};

const Report: FC<Props> = ({ item }) => {
	// ✅ OK: useCallbackはトップレベルで呼び出す
	const handleClick = useCallback(() => {
		sendReport(item)
	}, [item]);
	return (
		<figure>
			<Chart onClick={handleClick} />
		</figure>
	);
};
```

あるいは、今回のコード例でしたら、切り分けた子コンポーネント(`Report`)を`memo`でラップしても構いません。渡されたプロパティ`item`が変わらなければ、子の`Chart`コンポーネントも含めて再レンダーが省かれます。関数`handleClick`も、`item`に依存させる`useCallback`で包む必要がありません。

```react
const Report: FC<Props> = memo(({ item }) => {
	const handleClick = () => {
		sendReport(item);
	}
	return (
		<figure>
			<Chart onClick={handleClick} />
		</figure>
	);
});
```