React/Next.jsでテキストエリアの自動リサイズ機能を実装する方法を、実際のコード例と使用方法を含めてメモ
1. 基本的な高さ調整ロジック
高さ調整の核となる関数は、テキストエリアの高さを内容に合わせて動的に変更する:
// 高さを調整する関数 - 依存を最小化
const adjustHeight = useCallback((): void => {
const textarea = textareaRef.current;
if (!textarea) return;
// 一度高さをリセットしてから、内容に合わせて再設定
textarea.style.height = 'auto';
textarea.style.height = `${textarea.scrollHeight}px`;
}, []);
2. 入力イベントの監視
ユーザーの入力に応じて高さを調整するためのイベントリスナー設定:
// 入力イベント用のハンドラー
const handleInput = useCallback((): void => {
if (autoResize) {
adjustHeight();
}
}, [autoResize, adjustHeight]);
// 入力イベントリスナーの設定
useEffect(() => {
const textarea = textareaRef.current;
if (!textarea || !autoResize) return;
// 初期高さ調整
adjustHeight();
textarea.addEventListener('input', handleInput);
return () => {
textarea.removeEventListener('input', handleInput);
};
}, [autoResize, adjustHeight, handleInput]);
3. リロード時と初期値対応のための複数タイミング調整
リロードや初期値読み込み時の高さ調整を確実にするための複数タイミング実行:
// 初期レンダリング後の高さ調整を最適化
useEffect(() => {
if (!autoResize || !textareaRef.current) return;
// ResizeObserverを使用して要素のサイズ変更を監視
const resizeObserver = new ResizeObserver(() => {
adjustHeight();
});
resizeObserver.observe(textareaRef.current);
// リロード時や初期値読み込みのタイミング差をカバーするため複数回調整
const timers = [
setTimeout(() => {
adjustHeight();
}, 0), // 即時実行
setTimeout(() => {
adjustHeight();
}, 50), // DOMレンダリング直後
setTimeout(() => {
adjustHeight();
}, 100), // 少し遅いタイミング
setTimeout(() => {
adjustHeight();
}, 300), // さらに遅いタイミング
setTimeout(() => {
adjustHeight();
}, 500), // リロード完了後
];
return () => {
if (textareaRef.current) {
resizeObserver.unobserve(textareaRef.current);
}
resizeObserver.disconnect();
timers.forEach(clearTimeout);
};
}, [autoResize, adjustHeight, props.value]);
4. 値の変更検知と高さ調整
props.valueが変更された場合の高さ調整:
// 値が変更された場合の高さ調整
useEffect(() => {
if (!autoResize || !textareaRef.current) return;
// マイクロタスクキューを使用してDOM更新後に実行
queueMicrotask(() => {
adjustHeight();
});
}, [props.value, autoResize, adjustHeight]);
5. 複数のrefの管理
React Hook Formのrefとコンポーネントのrefを適切に管理:
const setRefs = (element: HTMLTextAreaElement | null): void => {
textareaRef.current = element;
if (typeof ref === 'function') {
ref(element);
} else if (ref) {
ref.current = element;
}
// React Hook Formのrefを設定
if (typeof registerRef === 'function') {
registerRef(element);
}
};
6. スタイリングの最適化
自動リサイズ時のスタイル調整:
<textarea
id={id}
ref={setRefs}
className={twMerge(
finalClassName,
'w-full rounded-lg p-2.5 focus:outline-none',
autoResize ? 'overflow-hidden' : 'overflow-auto'
)}
aria-invalid={isError ? 'true' : 'false'}
{...props}
{...registerProps}
/>
使用方法
import { TextArea } from '@/components/ui-parts/forms/TextArea';
const MyComponent = () => {
return (
<TextArea
id="description"
placeholder="説明を入力してください"
autoResize
rows={3}
/>
);
};
実装上の注意点
-
複数のタイミングでの調整
- 単一のタイミングでは捉えきれない様々なケースに対応するため、複数のタイミングで高さ調整を実行
- これはハックに近いアプローチだが、実用的な解決策として機能する
-
ResizeObserverの活用
- 要素のサイズ変更を監視する標準APIを活用
- 外部要因によるサイズ変更にも対応可能
-
クリーンアップの徹底
- メモリリークを防ぐため、すべてのイベントリスナー、タイマー、Observerを適切に解除
-
依存配列の最適化
- 必要最小限の依存関係を設定し、不要な再レンダリングを防止