Skip to content

🐛 Fix shared dropdownRef across .map() in suggestions/recommendations#4311

Merged
clubanderson merged 1 commit intomainfrom
fix/4232-dropdown-ref
Apr 2, 2026
Merged

🐛 Fix shared dropdownRef across .map() in suggestions/recommendations#4311
clubanderson merged 1 commit intomainfrom
fix/4232-dropdown-ref

Conversation

@clubanderson
Copy link
Copy Markdown
Collaborator

Summary

  • Fixes the shared dropdownRef bug in MissionSuggestions.tsx and CardRecommendations.tsx where a single useRef was assigned inside .map() loops, causing it to always point to the last rendered dropdown element
  • Replaced ref={dropdownRef} with id-based lookup (document.getElementById) using the currently expanded item ID, ensuring click-outside detection always references the correct active dropdown

Fixes #4232

Changes

  • MissionSuggestions.tsx: Removed dropdownRef, added id={mission-dropdown-${suggestion.id}} to dropdown divs, updated click-outside handler to use document.getElementById(mission-dropdown-${expandedId})
  • CardRecommendations.tsx: Removed dropdownRef, added id={rec-dropdown-${rec.id}} to the expanded-panel dropdown div (minimized view already had it), updated click-outside handler to use document.getElementById(rec-dropdown-${expandedRec})

Test plan

  • Open Dashboard with multiple Recommended Actions chips visible
  • Expand a chip that is NOT the last in the list
  • Click inside the dropdown (e.g., on description text) — should NOT close
  • Click outside the dropdown — should close
  • Repeat for Recommended Cards chips
  • Verify Escape key still closes dropdowns
  • Verify keyboard navigation (ArrowDown/ArrowUp) still works within dropdowns

…#4232)

Replace single useRef with document.getElementById lookup using the
currently expanded item ID. This ensures the click-outside handler
always references the correct active dropdown element, not whichever
element happened to render last in the .map() loop.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Copilot AI review requested due to automatic review settings April 2, 2026 19:17
@kubestellar-prow kubestellar-prow bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Apr 2, 2026
@kubestellar-prow
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign clubanderson for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 2, 2026

Deploy Preview for kubestellarconsole ready!

Name Link
🔨 Latest commit 4bbfad4
🔍 Latest deploy log https://app.netlify.com/projects/kubestellarconsole/deploys/69cec0c5003540000867a5f8
😎 Deploy Preview https://deploy-preview-4311.console-deploy-preview.kubestellar.io
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@clubanderson clubanderson merged commit c4afedd into main Apr 2, 2026
16 of 17 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

👋 Hey @clubanderson — thanks for opening this PR!

🤖 This project is developed exclusively using AI coding assistants.

Please do not attempt to code anything for this project manually.
All contributions should be authored using an AI coding tool such as:

This ensures consistency in code style, architecture patterns, test coverage,
and commit quality across the entire codebase.


This is an automated message.

@kubestellar-prow kubestellar-prow bot added the size/S Denotes a PR that changes 10-29 lines, ignoring generated files. label Apr 2, 2026
@kubestellar-prow kubestellar-prow bot deleted the fix/4232-dropdown-ref branch April 2, 2026 19:17
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Thank you for your contribution! Your PR has been merged.

Check out what's new:

Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a bug where click-outside detection for dropdowns in dashboard “Recommended Actions” and “Recommended Cards” could reference the wrong DOM node due to a shared ref being assigned inside .map().

Changes:

  • Removed the shared dropdownRef usage in both components.
  • Added per-item dropdown ids and updated click-outside logic to resolve the active dropdown via document.getElementById(...).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
web/src/components/dashboard/MissionSuggestions.tsx Replaces shared ref with ID-based active dropdown lookup for click-outside closing.
web/src/components/dashboard/CardRecommendations.tsx Same ref-to-ID approach applied to recommendation dropdowns (inline + expanded).

Comment on lines +121 to +123
// Use the currently expanded ID to find the correct dropdown element
const activeDropdown = document.getElementById(`mission-dropdown-${expandedId}`)
if (activeDropdown && !activeDropdown.contains(e.target as Node)) {
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

handleClickOutside only closes when activeDropdown is found. If the expanded suggestion disappears from the rendered list (the suggestions array is periodically regenerated in useMissionSuggestions), document.getElementById(...) can return null and the dropdown state/listeners will never be cleared via outside clicks. Consider treating a missing activeDropdown as “outside” and calling setExpandedId(null) (or resetting expandedId when suggestions change).

Suggested change
// Use the currently expanded ID to find the correct dropdown element
const activeDropdown = document.getElementById(`mission-dropdown-${expandedId}`)
if (activeDropdown && !activeDropdown.contains(e.target as Node)) {
// Use the currently expanded ID to find the correct dropdown element.
// If the suggestion was re-rendered away, treat the missing element as outside.
const activeDropdown = document.getElementById(`mission-dropdown-${expandedId}`)
if (!activeDropdown || !activeDropdown.contains(e.target as Node)) {

Copilot uses AI. Check for mistakes.
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
// Use the currently expanded ID to find the correct dropdown element
const activeDropdown = document.getElementById(`rec-dropdown-${expandedRec}`)
if (activeDropdown && !activeDropdown.contains(e.target as Node)) {
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

handleClickOutside only closes when the dropdown element is found. If the expanded recommendation is removed/filtered out while expandedRec is still set (recommendations can change over time), getElementById(...) returns null and outside clicks won’t clear expandedRec or the event listeners. Consider closing when activeDropdown is null (or resetting expandedRec when the visible list changes).

Suggested change
if (activeDropdown && !activeDropdown.contains(e.target as Node)) {
if (!activeDropdown || !activeDropdown.contains(e.target as Node)) {

Copilot uses AI. Check for mistakes.
Comment on lines 120 to 124
const handleClickOutside = (e: MouseEvent) => {
// Check if click is outside the dropdown content
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
// Use the currently expanded ID to find the correct dropdown element
const activeDropdown = document.getElementById(`mission-dropdown-${expandedId}`)
if (activeDropdown && !activeDropdown.contains(e.target as Node)) {
setExpandedId(null)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

This change fixes click-outside detection for the active dropdown, but there’s no regression test covering the reported scenario (open a non-last chip, click inside should not close; click outside should close). Since the repo has Playwright e2e coverage, consider adding an e2e test for Mission Suggestions dropdown interactions to prevent this from regressing.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines 97 to 101
const handleClickOutside = (e: MouseEvent) => {
// Check if click is outside the dropdown content
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
// Use the currently expanded ID to find the correct dropdown element
const activeDropdown = document.getElementById(`rec-dropdown-${expandedRec}`)
if (activeDropdown && !activeDropdown.contains(e.target as Node)) {
setExpandedRec(null)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

This bug fix changes the click-outside behavior for the dropdown, but there’s no regression test ensuring (1) expanding a non-last recommendation chip stays open when clicking inside, and (2) closes on outside click / Escape. Given the existing Playwright e2e suite, consider adding coverage for this interaction to prevent regressions.

Copilot generated this review using guidance from repository custom instructions.
@clubanderson
Copy link
Copy Markdown
Collaborator Author

🔄 Auto-Applying Copilot Code Review

Copilot code review found 2 code suggestion(s) and 2 general comment(s).

@copilot Please apply all of the following code review suggestions:

  • web/src/components/dashboard/MissionSuggestions.tsx (line 123): // Use the currently expanded ID to find the correct dropdown element. // ...
  • web/src/components/dashboard/CardRecommendations.tsx (line 100): if (!activeDropdown || !activeDropdown.contains(e.target as Node)) {

Also address these general comments:

  • web/src/components/dashboard/MissionSuggestions.tsx (line 124): This change fixes click-outside detection for the active dropdown, but there’s no regression test covering the reported
  • web/src/components/dashboard/CardRecommendations.tsx (line 101): This bug fix changes the click-outside behavior for the dropdown, but there’s no regression test ensuring (1) expanding

Push all fixes in a single commit. Run cd web && npm run build && npm run lint before committing.


Auto-generated by copilot-review-apply workflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. size/S Denotes a PR that changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MissionSuggestions and CardRecommendations Share a Single dropdownRef Across Multiple Rendered Dropdowns

3 participants