Skip to content

refactor: improve tab state management in ResponsiveTabs component#6687

Merged
bijin-bruno merged 1 commit intousebruno:mainfrom
abhishek-bruno:fix/responsive-tabs-count-issue
Jan 6, 2026
Merged

refactor: improve tab state management in ResponsiveTabs component#6687
bijin-bruno merged 1 commit intousebruno:mainfrom
abhishek-bruno:fix/responsive-tabs-count-issue

Conversation

@abhishek-bruno
Copy link
Member

@abhishek-bruno abhishek-bruno commented Jan 6, 2026

Description

The change addresses a React state staleness issue where tab indicators (e.g., notification badges or activity dots) weren't updating properly when stored as full tab objects.

Now, only keys are stored in state. On each render, fresh tab objects are looked up from the tabs prop, ensuring indicators and labels always reflect the latest values.

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • Chores
    • Optimized internal state management for the responsive tabs component to reduce unnecessary re-renders.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

Walkthrough

Refactored ResponsiveTabs state management to store tab keys instead of full tab objects, comparing keys for equality rather than entire arrays. Tab objects are now derived from keys during render by mapping against the current tabs prop, while maintaining all existing visibility and active-tab logic.

Changes

Cohort / File(s) Summary
Tab State Refactoring
packages/bruno-app/src/ui/ResponsiveTabs/index.js
Replaced tab object arrays with key arrays in state; introduced areKeysEqual() comparison; derived visibleTabs and overflowTabs from keys; ensured active tab visibility via key-based logic; reconstructed tab lists before rendering overflow menu

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • bruno#6490: Modifies the same ResponsiveTabs component file with related tab visibility and expandability logic changes

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • bijin-bruno

Poem

Keys unlock the tabs today, 🔑
No more comparing all the way,
State slims down, the logic gleams,
Derived and mapped, it's not as it seems! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: refactoring tab state management in ResponsiveTabs to use keys instead of full objects, which directly addresses the PR's core objective of fixing stale indicator updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46bacd6 and ead27f8.

📒 Files selected for processing (1)
  • packages/bruno-app/src/ui/ResponsiveTabs/index.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-app/src/ui/ResponsiveTabs/index.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: Unit Tests
  • GitHub Check: CLI Tests

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/bruno-app/src/components/EditableTable/index.js (1)

29-49: Optimize useEffect with dependency array.

The effect currently runs after every render, but it only needs to run when focusPlaceholderRef.current changes. While the early return prevents side effects, adding a dependency array improves performance.

🔎 Proposed optimization

Track the ref value in state or use a cleanup pattern:

+ const [shouldFocus, setShouldFocus] = useState(null);
+
  // Restore focus to placeholder row after an empty row is removed
  useEffect(() => {
-   if (focusPlaceholderRef.current && tableRef.current) {
-     const { columnKey } = focusPlaceholderRef.current;
+   if (shouldFocus && tableRef.current) {
+     const { columnKey } = shouldFocus;
      // Find the last row (placeholder) and focus on the appropriate input
      const tbody = tableRef.current.querySelector('tbody');
      const lastRow = tbody?.querySelector('tr:last-child');
      if (lastRow) {
        const cell = lastRow.querySelector(`[data-testid="column-${columnKey}"]`);
        const input = cell?.querySelector('input, textarea, [contenteditable]');
        if (input) {
          input.focus();
          // Place cursor at the end
          if (input.setSelectionRange && typeof input.value === 'string') {
            input.setSelectionRange(input.value.length, input.value.length);
          }
        }
      }
-     focusPlaceholderRef.current = null;
+     setShouldFocus(null);
    }
- });
+ }, [shouldFocus]);

Then update line 151:

-         focusPlaceholderRef.current = { columnKey: key };
+         setShouldFocus({ columnKey: key });
packages/bruno-app/src/ui/ResponsiveTabs/index.js (1)

216-218: Consider memoizing the key-to-object mapping.

The mapping from keys to tab objects runs on every render. While the performance impact is minimal for typical tab counts, wrapping these derivations in useMemo would avoid unnecessary recalculations.

🔎 Proposed optimization
+ const visibleTabs = useMemo(
+   () => visibleTabKeys.map((key) => tabs.find((t) => t.key === key)).filter(Boolean),
+   [visibleTabKeys, tabs]
+ );
+ const overflowTabs = useMemo(
+   () => overflowTabKeys.map((key) => tabs.find((t) => t.key === key)).filter(Boolean),
+   [overflowTabKeys, tabs]
+ );
- // Map stored keys to fresh tab objects from props (ensures indicators stay up-to-date)
- const visibleTabs = visibleTabKeys.map((key) => tabs.find((t) => t.key === key)).filter(Boolean);
- const overflowTabs = overflowTabKeys.map((key) => tabs.find((t) => t.key === key)).filter(Boolean);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8300abe and 46bacd6.

📒 Files selected for processing (2)
  • packages/bruno-app/src/components/EditableTable/index.js
  • packages/bruno-app/src/ui/ResponsiveTabs/index.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/ui/ResponsiveTabs/index.js
  • packages/bruno-app/src/components/EditableTable/index.js
🧠 Learnings (2)
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-app/src/ui/ResponsiveTabs/index.js
  • packages/bruno-app/src/components/EditableTable/index.js
📚 Learning: 2025-12-02T20:48:05.670Z
Learnt from: NikHillAgar
Repo: usebruno/bruno PR: 6279
File: packages/bruno-app/src/components/RequestPane/PromptVariables/PromptVariablesModal/index.js:79-86
Timestamp: 2025-12-02T20:48:05.670Z
Learning: In React 19+, ref callbacks support returning cleanup functions. When a ref callback returns a function, React will call it when the element unmounts or the ref changes, similar to useEffect cleanup. This is documented at https://react.dev/learn/manipulating-the-dom-with-refs#how-to-manage-a-list-of-refs-using-a-ref-callback

Applied to files:

  • packages/bruno-app/src/components/EditableTable/index.js
🧬 Code graph analysis (1)
packages/bruno-app/src/ui/ResponsiveTabs/index.js (4)
packages/bruno-app/src/components/ResponsePane/index.js (1)
  • tabs (33-33)
packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js (1)
  • tabs (40-40)
packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js (1)
  • tabs (50-50)
packages/bruno-app/src/components/RequestTabPanel/index.js (1)
  • tabs (49-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: Unit Tests
  • GitHub Check: CLI Tests
🔇 Additional comments (6)
packages/bruno-app/src/components/EditableTable/index.js (3)

1-1: LGTM!

The useEffect import is correctly added and necessary for the focus restoration logic.


25-25: LGTM!

The focusPlaceholderRef is appropriately declared to track focus restoration state between renders.


146-154: LGTM!

The filtering logic correctly removes empty rows when showAddRow is enabled and schedules focus restoration appropriately. The logic ensures that empty placeholder rows are regenerated by the rowsWithEmpty memo.

packages/bruno-app/src/ui/ResponsiveTabs/index.js (3)

13-17: LGTM!

The areKeysEqual function is a clean and efficient replacement for object array comparison, correctly checking length and element equality.


29-30: LGTM!

The state refactor from tab objects to tab keys is the core change addressing the staleness issue described in the PR. This allows fresh tab objects to be derived from props on each render.


82-91: LGTM!

The key extraction and conditional state updates using areKeysEqual correctly prevent unnecessary re-renders while ensuring state updates when tab visibility actually changes.

@pull-request-size pull-request-size bot added size/S and removed size/M labels Jan 6, 2026
@abhishek-bruno abhishek-bruno force-pushed the fix/responsive-tabs-count-issue branch from fe4dc17 to ead27f8 Compare January 6, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants