Skip to content

fix(useVirtual): calculateVisibleItems: return if itemHeight is 0 #21442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

grolu
Copy link

@grolu grolu commented May 20, 2025

Description

This PR fixes an issue in useVirtual composable for dynamic item heights. If a static item-height is not provided, the height is calculated for each item dynamically. However, depending on the timing when the table gets rendered initially, it may happen that the calculateVisibleItems function is being called before the item heights have been determined.
If all heights are still 0, binaryClosest function calculates an index in the middle of the item array which leads to all items being rendered instead of lazy rendering.
See the behavior in the playground here

Modifying items in onMounted causes the issue.
The table only recovers if you clear the items or if you add more items and scroll past the initial virtual page.

The proposed solution adds an additional circuit breaker to the _calculateVisibleItems function in case itemHeight has not yet been calculated.

This PR also fixes #18806

Markup:

<template>
  <v-data-table-virtual
    ref="elTable"
    :headers="headers"
    :item-key="itemKey"
    :items="virtualBoats"
    height="400"
    item-value="name"
    fixed-header
  />
  <div class="mt-8 pa-4">
    RENDERED ROWS: <strong>{{ renderedRows }}</strong>
  </div>
  <div class="mt-8 pa-4">
    Total Boats in List: <strong>{{ virtualBoats.length }}</strong>
  </div>
</template>

<script setup>
  import { onMounted, ref } from 'vue'
  import GRow from './GRow.vue'

  const renderedRows = ref(0)
  const elTable = ref()
  const c = ref(1)

  setInterval(() => {
    renderedRows.value =
      elTable.value?.$el.querySelectorAll('tbody tr').length ?? 0
  }, 100)

  const headers = [
    { title: 'Boat Type', key: 'name' },
    { title: 'Speed(knots)', key: 'speed' },
    { title: 'Length(m)', key: 'length' },
    {
      title: 'Price($)',
      key: 'price',
      value: item => formatPrice(item.price),
    },
    { title: 'Year', key: 'year' },
  ]

  const boats = [
    {
      name: 'Speedster',
      speed: 35,
      length: 22,
      price: 300000,
      year: 2021,
    },
    {
      name: 'OceanMaster',
      speed: 25,
      length: 35,
      price: 500000,
      year: 2020,
    },
    {
      name: 'Voyager',
      speed: 20,
      length: 45,
      price: 700000,
      year: 2019,
    },
    {
      name: 'WaveRunner',
      speed: 40,
      length: 19,
      price: 250000,
      year: 2022,
    },
    {
      name: 'SeaBreeze',
      speed: 28,
      length: 31,
      price: 450000,
      year: 2018,
    },
    {
      name: 'HarborGuard',
      speed: 18,
      length: 50,
      price: 800000,
      year: 2017,
    },
    {
      name: 'SlickFin',
      speed: 33,
      length: 24,
      price: 350000,
      year: 2021,
    },
    {
      name: 'StormBreaker',
      speed: 22,
      length: 38,
      price: 600000,
      year: 2020,
    },
    {
      name: 'WindSail',
      speed: 15,
      length: 55,
      price: 900000,
      year: 2019,
    },
    {
      name: 'FastTide',
      speed: 37,
      length: 20,
      price: 280000,
      year: 2022,
    },
  ]

  const virtualBoats = ref([])
  virtualBoats.value = makeBoats(1000)

  function formatPrice (value) {
    return `$${value.toFixed(0).replace(/\d(?=(\d{3})+$)/g, '$&,')}`
  }

  function makeBoats (c) {
    return [...Array(c).keys()].map(i => {
      const boat = { ...boats[i % 10] }
      boat.name = `${boat.name} - ${(Math.random() + 1)
        .toString(36)
        .substring(7)}`
      return boat
    })
  }

  function addBoats (prepend = false, c = 1) {
    const boats = makeBoats(c)
    if (prepend) {
      virtualBoats.value.unshift(...boats)
    } else {
      virtualBoats.value.push(...boats)
    }
  }

  onMounted(() => {
    addBoats(true)
  })

  const itemKey = item => {
    return item.raw.name
  }
</script>

@MajesticPotatoe MajesticPotatoe added T: bug Functionality that does not work as intended/expected E: Virtual Virtual composable labels May 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
E: Virtual Virtual composable T: bug Functionality that does not work as intended/expected
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug Report][3.4.4] VDataTableVirtual renders all rows when items are updated
2 participants