責務を理解して実装を心がけよう
Published on September 17, 2025
Category: つぶやき
はじめに
久しぶりの投稿です、最近はとても楽しく開発と勉強を進めつつ、個人プロダクトの基盤整備のため、MVPから見直して少しづつ進捗が出てきたとこ露です。
また、所属企業でチーム開発は難しいなと感じながらも、ユーザーへの価値提供を基準に何が必要か、意思決定が下せるようになってきたのは成長かなと感じます。
本題に入ります、とあるプロジェクトで発生していたエラー事象について学んだことをこちらに記載します。
結論
UIライブラリの責務を理解して、各UIライブラリの想定しているユースケースに沿って実装しよう。
🚨 発生した問題
企業検索機能で、**一文字目に a
, b
, c
を入力すると選択不可になる**謎のバグが発生
こちらはとある実装を確認した時に発生していた事象です。
// 問題のあった実装
<DropdownMenu>
<DropdownItem key="search">
<Input placeholder="企業を検索..." />
</DropdownItem>
{companies.map(company => (
<DropdownItem key={company.id}>{company.name}</DropdownItem>
))}
</DropdownMenu>
---
🔍 根本原因:内部制御との衝突
DropdownMenuの内部実装
// HeroUIが内部で自動実行(開発者は制御不可)
const handleKeyDown = (event) => {
const key = event.key.toLowerCase();
// 該当項目を自動検索・選択
const matchingItem = items.find(item =>
item.textContent.toLowerCase().startsWith(key)
);
if (matchingItem) {
setSelectedItem(matchingItem); // 🔥 自動選択
scrollIntoView(matchingItem); // 🔥 自動スクロール
}};
衝突の流れ
ユーザーが 's' を入力
↓
DropdownMenu内部「's'で始まる項目を選択!」
↓
Input「検索文字として's'を入力!」
↓
制御権の奪い合い → 選択不可状態 💥
⚡ heroui/DropdownMenuの本来の責務
設計思想:**選択専用**コンポーネント
- キーボードナビゲーション機能が**内蔵**
- 「選択肢から選ぶ」ための最適化
- 検索機能は**想定外の用途**
内部で自動制御される機能
- キーボードイベントの捕捉
- 該当項目の検索ロジック
- 選択状態の自動変更
- フォーカス管理
→ 開発者が制御できない領域で勝手に動作
---
💀 無理やり修正の沼
内部制御を止めようと、どんどん複雑な実装に...
<DropdownMenu>
<DropdownItem key="search" selectionMode="none" // 選択機能を無効化>
<Input onKeyDown={(e) => e.stopPropagation()} // イベント伝播を遮断ref={inputRef/>
</DropdownItem>
{/* setTimeout でフォーカス制御の奪い返し... */}
</DropdownMenu>
なぜ複雑になるのか?
- 内部制御との戦い = 設計思想に逆らった実装
- 1つの回避策 → 新たな問題 → さらなる回避策...
複雑な回避策 = 設計の問題のサイン ⚠️
---
✨ 正しい解決策
HeroUIのAutocompleteを使用
// シンプルで自然な実装
<Autocomplete placeholder="企業を検索..." onInputChange={handleSearch} onSelectionChange={handleSelect}>
{companies.map(company => (
<AutocompleteItem key={company.id}>
{company.name}
</AutocompleteItem>
))}
</Autocomplete>
なぜAutocompleteが正解?
- 検索機能が**設計段階から組み込み済み**
- キーボード入力は検索に専念(内部制御が適切)
- 直接入力と選択の両方をサポート
設計思想に沿った自然な実装
📚 学んだ教訓
1. コンポーネントの責務を理解しよう
- DropdownMenu = 選択専用(内部でキーナビゲーション制御)
- Autocomplete = 検索+選択(内部で検索制御)
2. 内部制御を意識した選択
UIライブラリは「見た目」だけでなく「振る舞い」も提供
3. 適材適所の重要性
- 「なぜこのコンポーネントが存在するのか?」を考える
4. 複雑な回避策は危険信号
無理な実装 → 内部制御との戦い → バグの温床 → 技術的負債