Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 3, 2025

Fix: BTable row-details get hidden on "Load more"

Fixes row-details being hidden when items array is updated with new object references.

Problem

When using BTable/BTableLite with row-details, if the items array is replaced with new objects (even with the same data), the row-details would be hidden. This commonly happens in "Load more" scenarios where the entire items array is recreated.

Root Cause: The detailsMap used a WeakMap with object references as keys. When items were replaced with new objects, the map lost track of which rows had details open.

Solution

Updated BTableLite to dynamically use the appropriate map type and track by primary key when available:

  • Use WeakMap when no primaryKey - for memory efficiency (garbage collection of unused items)
  • Use Map when primaryKey is defined - to support string keys for primary key tracking
  • Watch for primaryKey changes - recreate the map when the primaryKey prop changes
  • Modified generateDetailsItem to return the primary key string when primaryKey prop is provided and the item contains that key
  • Added helper functions for type-safe map operations that handle the union type correctly
  • Updated watcher logic to preserve existing details state when items are replaced with the same primary key value
  • Falls back to object reference tracking when no primaryKey is provided (maintains full backward compatibility)
  • Added concise documentation about primary key behavior in row details section

Usage

To enable row-details persistence across item updates, provide a primary-key prop:

<BTable :items="items" :fields="fields" primary-key="id">
  <template #cell(actions)="row">
    <BButton @click="row.toggleDetails">
      {{ row.detailsShowing ? 'Hide' : 'Show' }} Details
    </BButton>
  </template>
  <template #row-details="row">
    <BCard>Details for {{ row.item.name }}</BCard>
  </template>
</BTable>

When items are updated (e.g., in a "Load more" scenario):

// Items are replaced with new object references but same IDs
items.value = [
  { id: 1, name: 'Alice', age: 30 },  // New object but id: 1 remains
  { id: 2, name: 'Bob', age: 25 },    // New object but id: 2 remains
  { id: 3, name: 'Charlie', age: 35 } // New item added
]
// Row-details for items with id 1 and 2 will persist if they were open

Demo

The fix is demonstrated with two tables - one with primary-key and one without:

  • Top table (with primary-key): Alice's details remain open after clicking "Load More" ✅
  • Bottom table (without primary-key): Alice's details are closed after clicking "Load More" (expected behavior for backward compatibility)

Changes

Core Implementation (BTableLite.vue):

  • detailsMap: Dynamically initialized as WeakMap (no primaryKey) or Map (with primaryKey)
  • Added watcher for primaryKey prop changes - clears and recreates map with appropriate type
  • generateDetailsItem: Returns primary key string when primaryKey prop is provided
  • Added helper functions:
    • hasDetailsValue: Type-safe .has() check
    • setDetailsValueByKey: Type-safe .set() operation
    • getDetailsMapKey: Consistent key generation
    • getDetailsValue: Type-safe .get() for items
    • setDetailsValue: Type-safe .set() for items
  • Updated all map operations to use helper functions

Documentation (table.md):

  • Added concise note in Row details support section explaining primary key behavior
  • Mentions persistence with primary key, WeakMap usage without primary key, and garbage collection behavior

Tests (table-lite.spec.ts):

  • Added test: "persists details when items are replaced with new object references but same primary key"
  • Added test: "falls back to object reference when primaryKey is not provided"
  • Added test: "clears details when primaryKey prop changes"
  • Added test: "clears details when primaryKey is removed"
  • Added test: "clears details when primaryKey is changed and then changed back"
  • All existing tests continue to pass (1635 tests total)

Behavior

Scenario primaryKey prop Behavior
Items replaced with new objects (same IDs) ✅ Provided Details persist
Items replaced with new objects ❌ Not provided Details close (uses WeakMap)
primaryKey prop changes Either Map is cleared, all details close
primaryKey removed (set to undefined) Changed to undefined Converts to WeakMap, all details close

Memory Efficiency

Using WeakMap when no primaryKey is provided ensures:

  • Items that are no longer referenced can be garbage collected
  • No memory leaks from holding references to old item objects
  • Automatic cleanup when items are removed from the table

Backward Compatibility

✅ Fully backward compatible - when no primaryKey is provided, uses WeakMap (memory efficient, original behavior). When primaryKey is provided, uses Map (supports primary key tracking). This is a progressive enhancement that only activates when the primary-key prop is used.

Testing

  • ✅ All unit tests pass (1635 tests)
  • ✅ Linting passes
  • ✅ Build succeeds
  • ✅ Manual testing confirms fix works as expected
  • ✅ Documentation added and verified

Fixes #2870

Original prompt

This section details on the original issue you should resolve

<issue_title>[BTable] row-details get hidden on "Load more"</issue_title>
<issue_description>### Describe the bug

Steps to reproduce (in the reproduction link)

  1. Click "Show details" to toggle row-details of a row
  2. Click "Load more", which adds one more item to the BTable items prop
  3. The row-details gets hidden - here I would expect that it remains open, because all the rows except the last one didn't change

Reproduction

https://stackblitz.com/edit/vue-bdwu1e7h?file=package.json%2Csrc%2Fcomponents%2FBTableTest.vue

Used Package Manager

yarn</issue_description>

<agent_instructions>In BTableLite, update the generate details to be either object | string, where the string is the object[primaryKey], doing this allows you to keep track of items by their primary key, avoiding issues with object references being disposed of. If no primary key is provided, or the item in props.items does not contain key of "primary key" (from the prop) it should default to using the object reference.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@VividLemon This is due to the way you're changing the reference of the object. In your displayed items, once you add a new dispalyed count, you are building new objects based on rawItems. Therefore, the new object isn't active, unlike the previous one.
let table = [];

      this.rawItems.forEach((item, index) => {
        let newItem = {
          isActive: item.isActive,
          age: item.age,
          name: item.name,
        };
        //adding a note on some items
        if (index % 2 === 0) {
          newItem['notes'] = 'test note';
        }
        table.push(newItem);
      });

Doing this is making new objects that you are displaying, therefore it is not a reference to the last object.

because all the rows except the last one didn't change

According to js, it did change, since its a different object reference. Changing to let newItem = item would work, because it is the same reference.

The implementation could possibly use an update that uses primary keys to keep track of similar items, and defaults to current behavior if not used. </comment_new>

Fixes #2870

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@coderabbitai
Copy link

coderabbitai bot commented Oct 3, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.


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

Copilot AI changed the title [WIP] [BTable] row-details get hidden on "Load more" fix(BTableLite): Use primary key to persist row-details state when items change Oct 3, 2025
Copilot AI requested a review from VividLemon October 3, 2025 20:35
@VividLemon
Copy link
Member

@xvaara @dwgray I'm not sure if this behavior is wanted. Originally when I implemented this functionality I thought it would be wanted to close details if you did something like switch pages in certain contexts. By adding primary key support here, this stuff would stay open in certain contexts. I'm just not sure the best UX if they should close again or not. The situation is if object references change.

@VividLemon
Copy link
Member

VividLemon commented Oct 9, 2025

When thinking about it, this is after all the intended purpose of the primary key. To persist data... But it leaves issues with the current implementation of WeakMap. I think it may need to dynamically choose between weakmap and map since strings don't fulfill the constraints of weakmap

// Fall back to object reference
return [item, item._showDetails]
}
const detailsMap = ref(new Map<object | string, boolean | undefined>())
Copy link
Member

Choose a reason for hiding this comment

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

@copilot we still want to use a WeakMap when primary key is not defined because we don't want to hold around unnecessary resources when possible. We should likely make it a Map when primary key is defined, and weakmap when not.

In addition to that, you also have to be careful of when primary key changes. When it changes, or perhaps goes to undefined, then it should wipe out the map, and/or convert from Map to WeakMap and visa versa.

This switching behavior should also be tested. One can do it I would think by switching the primary key, and then back, should result in the loss of the value in the map.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the implementation in commit 77bf95b:

  • Now uses WeakMap when primaryKey is not defined (for memory efficiency and automatic garbage collection)
  • Uses Map when primaryKey is defined (to support string keys)
  • Added watcher for primaryKey prop changes that clears and recreates the map with the appropriate type
  • Added helper functions for type-safe map operations (hasDetailsValue, setDetailsValueByKey, etc.)
  • Added comprehensive tests for the switching behavior:
    • Clears details when primaryKey changes to a different field
    • Clears details when primaryKey is removed (set to undefined)
    • Clears details when primaryKey is changed and then changed back

All 1635 tests passing.

…iency

- Changed to dynamically use WeakMap when no primaryKey (memory efficient) and Map when primaryKey is defined
- Added watcher to recreate map when primaryKey prop changes
- Added helper functions for type-safe map operations
- Added tests for primaryKey switching behavior to verify map is cleared when primaryKey changes
- All 1635 tests passing

Co-authored-by: VividLemon <[email protected]>
Added comprehensive documentation section explaining:
- How primary key affects row details persistence
- Behavior with and without primary key prop
- Example usage with code snippets
- Important notes about primary key changes and uniqueness

Co-authored-by: VividLemon <[email protected]>
@VividLemon
Copy link
Member

@copilot The documentation doesn't need to be that robust. Meerly a note about how primary key will effect row details and allow persistence even when the object is disposed, or it is a new object entirely. We don't need an example here, but note that it is a weakmap when no primary key is set, and if the objects are collected by memory, then it is destroyed from the map

Reduced documentation to a single note explaining:
- Primary key enables row details persistence with new object references
- WeakMap usage without primary key for memory efficiency
- Garbage collection behavior

Removed code examples and detailed sections per feedback.

Co-authored-by: VividLemon <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 17, 2025

@copilot The documentation doesn't need to be that robust. Meerly a note about how primary key will effect row details and allow persistence even when the object is disposed, or it is a new object ent...

Simplified the documentation in commit 89b6c9f to a concise note that explains:

  • Primary key allows row details persistence even when objects are replaced
  • WeakMap usage without primary key for memory efficiency
  • Garbage collection behavior

Removed all code examples and detailed sections.

@VividLemon VividLemon marked this pull request as ready for review November 17, 2025 23:37
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 17, 2025

bsvn-vite-ts

npm i https://pkg.pr.new/bootstrap-vue-next/bootstrap-vue-next@2871
npm i https://pkg.pr.new/bootstrap-vue-next/bootstrap-vue-next/@bootstrap-vue-next/nuxt@2871

commit: 89b6c9f

@VividLemon VividLemon merged commit a933f96 into main Nov 17, 2025
5 checks passed
@github-actions github-actions bot mentioned this pull request Nov 17, 2025
xvaara added a commit to xvaara/bootstrap-vue-next that referenced this pull request Nov 30, 2025
* upstream/main: (28 commits)
  fix(BToggle)! Remove redundant attribute cleanup & update docs for accessibility attributes on show/hide components (bootstrap-vue-next#2918)
  chore: release main (bootstrap-vue-next#2912)
  fix: allow custom component props in orchestrator create methods with type safety (bootstrap-vue-next#2922)
  fix(BModal): prevent focus trap error when no tabbable elements exist (bootstrap-vue-next#2864)
  Update package description for clarity (bootstrap-vue-next#2917)
  Update project description for clarity and detail
  test: add event emission tests for BTable and BTableLite (bootstrap-vue-next#2915)
  fix(BTableLite): Use primary key to persist row-details state when items change (bootstrap-vue-next#2871)
  chore: release main (bootstrap-vue-next#2896)
  feat(BTable): add configurable debouncing
  ci: Add publint to the build process (bootstrap-vue-next#2909)
  docs(Offcanvas): Parity pass (bootstrap-vue-next#2900)
  fix(directives): Robustness fixes for directives (bootstrap-vue-next#2906)
  docs(BFormInput): document @wheel.prevent for disabling mousewheel events (bootstrap-vue-next#2903)
  fix(typings): Fix paths to `*.d.mts` files (bootstrap-vue-next#2907)
  feat: add name and form props to BFormRating for form submission (bootstrap-vue-next#2895)
  docs: refactor docs to avoid duplication and boilerplate code (bootstrap-vue-next#2891)
  docs(BToast): Parity (bootstrap-vue-next#2887)
  docs(BModal): fix attribute to hide footer (bootstrap-vue-next#2888)
  docs(BPlaceholder): Parity pass (bootstrap-vue-next#2886)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BTable] row-details get hidden on "Load more"

2 participants