refactor: improve tab state management in ResponsiveTabs component#6687
Conversation
WalkthroughRefactored 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
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
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. Comment |
There was a problem hiding this comment.
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.currentchanges. 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
useMemowould 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
📒 Files selected for processing (2)
packages/bruno-app/src/components/EditableTable/index.jspackages/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()notfunc ()
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.jspackages/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.jspackages/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
useEffectimport is correctly added and necessary for the focus restoration logic.
25-25: LGTM!The
focusPlaceholderRefis appropriately declared to track focus restoration state between renders.
146-154: LGTM!The filtering logic correctly removes empty rows when
showAddRowis enabled and schedules focus restoration appropriately. The logic ensures that empty placeholder rows are regenerated by therowsWithEmptymemo.packages/bruno-app/src/ui/ResponsiveTabs/index.js (3)
13-17: LGTM!The
areKeysEqualfunction 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
areKeysEqualcorrectly prevent unnecessary re-renders while ensuring state updates when tab visibility actually changes.
fe4dc17 to
ead27f8
Compare
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:
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
✏️ Tip: You can customize this high-level summary in your review settings.