Skip to content

🧹 Refactor TagAutocompleteInput component into smaller parts#971

Open
is0692vs wants to merge 2 commits into
stagingfrom
🧹-code-health-improvement-description-7670127203445948716

Hidden character warning

The head ref may contain hidden characters: "\ud83e\uddf9-code-health-improvement-description-7670127203445948716"
Open

🧹 Refactor TagAutocompleteInput component into smaller parts#971
is0692vs wants to merge 2 commits into
stagingfrom
🧹-code-health-improvement-description-7670127203445948716

Conversation

@is0692vs

@is0692vs is0692vs commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🎯 What: Extracted the complex data-fetching and debouncing logic from TagAutocompleteInput into a new useTagSuggestions hook. Extracted the suggestions dropdown list into AutocompleteDropdown and the selected tags display into TagChips components.

💡 Why: TagAutocompleteInput had grown quite large (>240 lines) because it tightly coupled state management for API requests, debouncing timers, caching, and complex DOM rendering into a single monolithic component. By breaking it into a hook and smaller child components, the core input logic is isolated, making the component significantly easier to maintain, test, and read.

Verification:

  • Successfully passed the 6 specific unit tests for TagAutocompleteInput (bun run test apps/web/src/components/__tests__/tag-autocomplete-input.test.tsx).
  • Successfully passed the entire web workspace test suite (bun x vitest run apps/web).
  • Verified types and formatting with bun x tsc and bun x prettier.

Result: The TagAutocompleteInput is now significantly shorter and focused purely on rendering the input field and keyboard navigation state, delegating suggestions fetching and child rendering to specialized, single-responsibility functions.


PR created automatically by Jules for task 7670127203445948716 started by @is0692vs

Greptile Summary

240行超の TagAutocompleteInput コンポーネントから、データ取得・デバウンス・キャッシュロジックを useTagSuggestions フックへ、ドロップダウンUIを AutocompleteDropdown、タグチップ表示を TagChips コンポーネントへ抽出するリファクタリングです。

  • useTagSuggestions は元の実装からデバウンス・キャッシュ・競合リクエスト防止ロジックをそのまま移植し、機能面は同等に保たれています。
  • openhighlightedIndex の更新が suggestions 変化とは別の useEffect([suggestions]) で行われるようになったため、suggestions が空になった直後の 1 レンダリングで open=true かつ suggestions=[] という過渡的状態が発生します。AutocompleteDropdown 内のガードがこれを視覚的に抑制していますが、aria-expanded は一瞬 true のままになります。
  • TagChips への抽出はシンプルで問題なく、元のインラインJSXと動作は同等です。

Confidence Score: 4/5

全体的にクリーンなリファクタリングで、元の動作はほぼ完全に保たれています。open 状態の更新タイミングが 1 レンダリング遅れる設計上の変化がありますが、通常利用では視覚的に問題になりません。

フェッチ・デバウンス・競合制御ロジックは元の実装から忠実に移植されており、useTagSuggestions 単体の品質は高いです。ただし suggestionsopen/highlightedIndex の更新を別 useEffect に分離したことで、suggestions が空になる際に aria-expanded=true が一瞬残る過渡的な状態が生まれました。AutocompleteDropdown 内のガードが視覚的な問題を防いでいますが、このガードが必要な理由がコードから読み取りづらく、将来の変更で誤って削除されるリスクがあります。

open/highlightedIndex の更新ロジックが集中している tag-autocomplete-input.tsxuseEffect([suggestions]) 周辺と、内部ガードの意図が不明確な autocomplete-dropdown.tsx に注意してください。

Important Files Changed

Filename Overview
apps/web/src/components/tag-autocomplete-input.tsx フェッチ・デバウンス・キャッシュロジックを useTagSuggestions フックへ、レンダリングを子コンポーネントへ委譲し大幅にスリム化。open/highlightedIndex の制御が useEffect([suggestions]) に分離されたため、過渡的に aria-expanded が不正になる可能性あり。
apps/web/src/components/tag-autocomplete-input/use-tag-suggestions.ts デバウンス・キャッシュ・競合リクエスト防止ロジックを元の実装から忠実に移植。requestIdRef による競合制御も維持されており、動作は元コードと同等。
apps/web/src/components/tag-autocomplete-input/autocomplete-dropdown.tsx ドロップダウンUIを分離したコンポーネント。内部の suggestions.length === 0 ガードは防衛的コードに見えるが、実際には2段階更新の過渡状態を防ぐために必要。コメントがあると意図が明確になる。
apps/web/src/components/tag-autocomplete-input/tag-chips.tsx タグチップ表示を独立したコンポーネントに切り出したシンプルな変更。元のインラインJSXと動作は完全に同等。

Sequence Diagram

sequenceDiagram
    participant User as ユーザー入力
    participant TAI as TagAutocompleteInput
    participant Hook as useTagSuggestions
    participant API as /api/tags/suggest
    participant AD as AutocompleteDropdown
    participant TC as TagChips

    User->>TAI: テキスト入力
    TAI->>Hook: currentTrimmed, orgSlug を渡す
    Hook->>Hook: MIN_QUERY_LENGTH チェック
    Hook->>Hook: キャッシュ確認
    alt キャッシュヒット
        Hook-->>TAI: suggestions (キャッシュ)
    else キャッシュミス
        Hook->>Hook: debounce (300ms)
        Hook->>API: "GET /api/tags/suggest?q=..."
        API-->>Hook: "{ tags: string[] }"
        Hook->>Hook: キャッシュ保存
        Hook-->>TAI: suggestions (新規)
    end
    TAI->>TAI: useEffect([suggestions])
    Note over TAI: setOpen / setHighlightedIndex
    TAI->>AD: "open && suggestions, highlightedIndex"
    TAI->>TC: displayChips
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/web/src/components/tag-autocomplete-input.tsx:58-61
**`open` / `aria-expanded` が一瞬ずれるレンダリングが発生します**

`suggestions` の更新と `open``highlightedIndex` の更新が別々の `useEffect` に分離されたため、`suggestions``[]` に変化した際に「`open=true` かつ `suggestions=[]`」という一時的な状態が 1 フレーム存在します。この間、`AutocompleteDropdown` の内部ガード (`if (suggestions.length === 0) return null`) によって視覚的な表示は抑制されますが、`<input>``aria-expanded="true"` は維持されたままになります。スクリーンリーダーはドロップダウンが開いているとアナウンスしてしまう可能性があります。元のコードでは `suggestions``open``highlightedIndex` がすべて同一の `useEffect` 内で同時更新されていたため、この問題は発生しませんでした。`open` の制御を `useTagSuggestions` フックに含めるか、`open` の判定を `open && suggestions.length > 0` に戻すことで回避できます。

### Issue 2 of 2
apps/web/src/components/tag-autocomplete-input/autocomplete-dropdown.tsx:14
**この早期リターンは防衛的コードではなく正確性のために必要です**

親コンポーネントでは `{open && <AutocompleteDropdown .../>}` という条件付きレンダリングが行われていますが、`open``suggestions` は別々の `useEffect` によって更新されるため、`open=true` かつ `suggestions=[]` という過渡的な状態が存在します(suggestions が空になった直後の 1 レンダリング間)。この行はその過渡状態を防ぐために実際に機能しています。コードの意図を将来の読者に伝えるために、コメントを追加することをお勧めします。

Reviews (1): Last reviewed commit: "🧹 Refactor TagAutocompleteInput compone..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel

vercel Bot commented Jun 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
open-shelf Ignored Ignored Jun 7, 2026 2:36am

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@dosubot dosubot Bot added the javascript Pull requests that update javascript code label Jun 7, 2026
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@is0692vs, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 33 minutes and 7 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 92c82a5b-30af-4aab-a632-78aca708e789

📥 Commits

Reviewing files that changed from the base of the PR and between d6fa928 and 0580c80.

📒 Files selected for processing (5)
  • apps/web/src/components/__tests__/tag-autocomplete-input.test.tsx
  • apps/web/src/components/tag-autocomplete-input.tsx
  • apps/web/src/components/tag-autocomplete-input/autocomplete-dropdown.tsx
  • apps/web/src/components/tag-autocomplete-input/tag-chips.tsx
  • apps/web/src/components/tag-autocomplete-input/use-tag-suggestions.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 🧹-code-health-improvement-description-7670127203445948716

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown

このリポジトリでは staging 先行フローを採用しています。PR のターゲットを staging に変更しました。staging で動作確認後、stagingmain の PR を作成してください。

@github-actions github-actions Bot changed the base branch from main to staging June 7, 2026 02:18

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the TagAutocompleteInput component by modularizing its UI and logic into separate components (AutocompleteDropdown, TagChips) and a custom hook (useTagSuggestions). The review feedback recommends enforcing a maximum query length limit to prevent database stress from excessively long inputs, and verifying that the input element is still focused before opening the dropdown to avoid race conditions after a blur event.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +15 to +19
if (currentTrimmed.length < MIN_QUERY_LENGTH) {
setLoading(false);
setSuggestions([]);
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent unnecessary processing load and database stress from excessively long inputs, we should enforce a maximum length limit on the search query parameter before making the API request. Additionally, use a pre-defined constant (e.g., MAX_QUERY_LENGTH) instead of hardcoding the value to improve maintainability.

Suggested change
if (currentTrimmed.length < MIN_QUERY_LENGTH) {
setLoading(false);
setSuggestions([]);
return;
}
if (currentTrimmed.length < MIN_QUERY_LENGTH || currentTrimmed.length > MAX_QUERY_LENGTH) {
setLoading(false);
setSuggestions([]);
return;
}
References
  1. Search query parameters should have a maximum length limit (e.g., 100 characters) to prevent unnecessary processing load and database stress. This validation should be performed before any database operations to ensure the system does not process potentially malicious or inefficiently long strings.
  2. Use pre-defined constants for validation logic instead of hardcoding values to improve maintainability.

Comment on lines +58 to +61
useEffect(() => {
setHighlightedIndex(suggestions.length > 0 ? 0 : -1);
setOpen(suggestions.length > 0);
}, [suggestions]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent the autocomplete dropdown from unexpectedly popping open after the input has already been blurred (e.g., if a slow API request resolves after the user tabs away), we should verify that the input element is still the currently focused element before setting open to true.

  useEffect(() => {
    setHighlightedIndex(suggestions.length > 0 ? 0 : -1);
    if (document.activeElement === document.getElementById(id)) {
      setOpen(suggestions.length > 0);
    }
  }, [suggestions, id]);

@codecov

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.27273% with 7 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ents/tag-autocomplete-input/use-tag-suggestions.ts 85.36% 3 Missing and 3 partials ⚠️
...s/tag-autocomplete-input/autocomplete-dropdown.tsx 83.33% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Comment on lines +58 to +61
useEffect(() => {
setHighlightedIndex(suggestions.length > 0 ? 0 : -1);
setOpen(suggestions.length > 0);
}, [suggestions]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 open / aria-expanded が一瞬ずれるレンダリングが発生します

suggestions の更新と openhighlightedIndex の更新が別々の useEffect に分離されたため、suggestions[] に変化した際に「open=true かつ suggestions=[]」という一時的な状態が 1 フレーム存在します。この間、AutocompleteDropdown の内部ガード (if (suggestions.length === 0) return null) によって視覚的な表示は抑制されますが、<input>aria-expanded="true" は維持されたままになります。スクリーンリーダーはドロップダウンが開いているとアナウンスしてしまう可能性があります。元のコードでは suggestionsopenhighlightedIndex がすべて同一の useEffect 内で同時更新されていたため、この問題は発生しませんでした。open の制御を useTagSuggestions フックに含めるか、open の判定を open && suggestions.length > 0 に戻すことで回避できます。

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/src/components/tag-autocomplete-input.tsx
Line: 58-61

Comment:
**`open` / `aria-expanded` が一瞬ずれるレンダリングが発生します**

`suggestions` の更新と `open``highlightedIndex` の更新が別々の `useEffect` に分離されたため、`suggestions``[]` に変化した際に「`open=true` かつ `suggestions=[]`」という一時的な状態が 1 フレーム存在します。この間、`AutocompleteDropdown` の内部ガード (`if (suggestions.length === 0) return null`) によって視覚的な表示は抑制されますが、`<input>``aria-expanded="true"` は維持されたままになります。スクリーンリーダーはドロップダウンが開いているとアナウンスしてしまう可能性があります。元のコードでは `suggestions``open``highlightedIndex` がすべて同一の `useEffect` 内で同時更新されていたため、この問題は発生しませんでした。`open` の制御を `useTagSuggestions` フックに含めるか、`open` の判定を `open && suggestions.length > 0` に戻すことで回避できます。

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

highlightedIndex,
onSelect,
}: AutocompleteDropdownProps) {
if (suggestions.length === 0) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 この早期リターンは防衛的コードではなく正確性のために必要です

親コンポーネントでは {open && <AutocompleteDropdown .../>} という条件付きレンダリングが行われていますが、opensuggestions は別々の useEffect によって更新されるため、open=true かつ suggestions=[] という過渡的な状態が存在します(suggestions が空になった直後の 1 レンダリング間)。この行はその過渡状態を防ぐために実際に機能しています。コードの意図を将来の読者に伝えるために、コメントを追加することをお勧めします。

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/src/components/tag-autocomplete-input/autocomplete-dropdown.tsx
Line: 14

Comment:
**この早期リターンは防衛的コードではなく正確性のために必要です**

親コンポーネントでは `{open && <AutocompleteDropdown .../>}` という条件付きレンダリングが行われていますが、`open``suggestions` は別々の `useEffect` によって更新されるため、`open=true` かつ `suggestions=[]` という過渡的な状態が存在します(suggestions が空になった直後の 1 レンダリング間)。この行はその過渡状態を防ぐために実際に機能しています。コードの意図を将来の読者に伝えるために、コメントを追加することをお勧めします。

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com>
@pull-request-size pull-request-size Bot added size/XL and removed size/L labels Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update javascript code size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant