Akryum / vue-virtual-scroller

⚡️ Blazing fast scrolling for any amount of data
https://vue-virtual-scroller-demo.netlify.app
9.69k stars 915 forks source link

Replacing all items using RecycleScroller #256

Open mcfarljw opened 5 years ago

mcfarljw commented 5 years ago

I'm trying to use this plugin with data that can be filtered, but when I set the items with a new array it causes strange things to happen. Things will get out of order and include blank gaps. I'm including a watered down example. If you click regenerate, eventually you'll see things happen as shown in the screenshot where there is a gap between item1 and item2.

Is there some other way to regenerate all items that I'm missing?

Screen Shot 2019-07-11 at 4 31 15 PM

Example: https://codesandbox.io/s/vuetify-with-vue-virtual-scroller-khl8d

mcfarljw commented 5 years ago

I've got it working when using v-if="items.length" on the recycler component, then settings items to [] which forces the everything to rerender and waiting for this.$nextTick, but that doesn't seem like the intended way of handling things.

Example: https://codesandbox.io/s/vuetify-with-vue-virtual-scroller-working-yv0rz

KyKlersy commented 5 years ago

@mcfarljw

226 I think also shares this same problem.

Being unable to refresh the computed size property data on the lower components.

I had this issue in use case, to reuse a single dynamic scroller but change the inner view items with v-if on them. Problem being the data staying the same, but the sizing being different between views. As the data is not changing, no computed getters are being re-triggered.

The fix was to bind key to a property you control so you can toggle re-rendering the component, which forces it to update everything[The whole component]. I'm not sure if this is the best way or if there are better, but it does work for me.

I have modified your codesandbox below, if you open console you can watch how every button click of regenerate does match the random item amount.

https://codesandbox.io/embed/vuetify-with-vue-virtual-scroller-working-i1b25

citrus327 commented 5 years ago

@mcfarljw

226 I think also shares this same problem.

Being unable to refresh the computed size property data on the lower components.

I had this issue in use case, to reuse a single dynamic scroller but change the inner view items with v-if on them. Problem being the data staying the same, but the sizing being different between views. As the data is not changing, no computed getters are being re-triggered.

The fix was to bind key to a property you control so you can toggle re-rendering the component, which forces it to update everything[The whole component]. I'm not sure if this is the best way or if there are better, but it does work for me.

I have modified your codesandbox below, if you open console you can watch how every button click of regenerate does match the random item amount.

https://codesandbox.io/embed/vuetify-with-vue-virtual-scroller-working-i1b25

I also met this issue, I'm trying to fix this using your solution, but the solution is a bit tricky. Could you please help me understand this?

So basically you add a key to the recycle-scroller, and when the data changes, you give it a new key. I haven't read any source code about "Vue.js", but by my understand, the :key is used for computing a list changes, and update each item by specific key.

Is there any reason to add this? If add a key to a non-list(v-for) component, does it force it update or compute when the key changes?

Please help, thank you very much.

KyKlersy commented 5 years ago

@phshy0607 Adding the key and toggling it, is 'hack' based on the fact vue uses the "key" prop binding for component identity in the vue virtual dom. Changing the key forces vue to recreate a new recycle-scroller node which it will then patch into the real dom.

It should be considered a last resort to get behavior you are attempting. Using it like this should be scrutinized greatly if you start running into performance impact as this creates a new recycle-scroller component that then has to rebuild its computed sizes cache for your list data.

citrus327 commented 5 years ago

@phshy0607 Adding the key and toggling it, is 'hack' based on the fact vue uses the "key" prop binding for component identity in the vue virtual dom. Changing the key forces vue to recreate a new recycle-scroller node which it will then patch into the real dom.

It should be considered a last resort to get behavior you are attempting. Using it like this should be scrutinized greatly if you start running into performance impact as this creates a new recycle-scroller component that then has to rebuild its computed sizes cache for your list data.

Thank you very much. This is very well explained, and thank you for the solution, it works for now.

nathanlesage commented 5 years ago

I have pretty much the same problem. And I may have a theory where this comes from. I have a list of files I render in a sidebar. Everything is perfect, but obviously the user can change the name of these files. The files have an ID, which in my case is a simple hash of the absolute file path. So on rename, this file path changes, and so does the hash a.k.a. the ID.

If then I re-order the items (because one can sort the directory differently, obviously), the bug appears with the renamed file actually appearing twice, even though the hash changed, which means that the respective component is being re-used and then rendered on the original position (not the new one in the different sorting).

Here's how it looks:

image

As you can see, the file list itself only contains the file once, but it is being rendered twice. Completely removing the fileList and using a timeout to re-fill the list after a DOM rendering reflow fixes this, but obviously with a display glitch as the empty list is being rendered for a fraction of a second.

So it seems that the bug originates in the components not being re-validated. It doesn't check whether or not the respective hash/keyField exists more than once.

EDIT: I should add that another file is below the duplicate renamed file, so the duplicate file actually perfectly covers the file that's supposed to show up at the old position.

skayi commented 3 years ago

when there are thounds of data, and scrolling very fast with the scrollbar, this problem reappears easily. image

Demo: https://codesandbox.io/s/vue-virtual-selector-60sds?file=/src/App.vue

GrRivero commented 1 year ago

Sorry to revival this topic but i found work around.

If you use a v-show in the parent of dynamicscroll the spaces are recalculated:

<div v-show="showListTik">
    <DynamicScroller
        :items="galleryItems"
        .
        .
        .
.
.
.

get galleryItems():[] {
    const elements:[] = [......]

    // WA
    this.showListTik = false
    setTimeout(() => {
        this.showListTik = true
    }, 10)

    return elements
}

Wroks well for me :)

qualle-admin commented 1 year ago

I am seeing this behavior when running Array.sort on the items, and the list doesn't refresh unless user starts scrolling.

Update

Confirmed I can get this working with this workaround

watch(
  () => computedEntities.value,
  _ => {
    isUpdating.value = true

    setTimeout(() => {
      isUpdating.value = false
    }, 10)
  },
  { deep: true },
)
</script>

<template>
  <VirtualScroller
    v-if="!isUpdating.value"
  >
    ...
  </VirtualScroller>
fire1 commented 1 year ago
    v-if="!isUpdating.value"

A less-than-ideal solution, but it worked... For me was too much to refresh a complete list of items when a single one is changed.

My workaround was to fork the source code and add an additional property and event into the RecycleScroller to listen for a change and execute the method this.updateVisibleItems(false), the emitted event was used to revert the value to default (for a next change in the same item).

I don't know why the author of the project does not attach this method updateVisibleItems to some kind of property with options for additional external change. My socket update was not able to display up-to-date list information.

I could share my fix with more details if there's interest.

qualle-admin commented 1 year ago

I actually fixed this. sorry for late reply - I do it by assigning a new index on sort like this:

So every list that is passed in, whether 999 - 0 or 0 - 999 always has an index of 0 - 999

const mutableEntities = ref(
  useArrayMap(entities.value, (entity, index) => ({
    ...entity,
    index,
  })).value,

  const computedEntities = computed({
  get() {
    return mutableEntities.value
  },
  set(value) {
    mutableEntities.value = value
  },
})

<VirtualScroller
    emit-update
    :items="entities"
    v-slot="{ item, active, index }"
/>
eliottvincent commented 1 year ago

@fire1 if you don't mind, could you please share your code?

Creazybird commented 1 year ago

when there are thounds of data, and scrolling very fast with the scrollbar, this problem reappears easily. image

Demo: https://codesandbox.io/s/vue-virtual-selector-60sds?file=/src/App.vue

props updateInterval can make it work

OfekA-IAI commented 9 months ago

when there are thounds of data, and scrolling very fast with the scrollbar, this problem reappears easily. image Demo: https://codesandbox.io/s/vue-virtual-selector-60sds?file=/src/App.vue

props updateInterval can make it work

Could you elaborate? I couldn't make it work :/ Any help would be greatly appreciated

fire1 commented 8 months ago

@fire1 if you don't mind, could you please share your code?

Sorry, but I over-customized the source code for an application to optimize it to work with 10k rows per 5 cols with dynamic data.

adoniscoder commented 7 months ago

I'm facing this issue as well, and hiding the list when updating the data is not an option for me, so does anyone have a better solution to fix this issue ?

OfekA-IAI commented 7 months ago

I'm facing this issue as well, and hiding the list when updating the data is not an option for me, so does anyone have a better solution to fix this issue ?

After looking for many solutions, we ended up adding :update-interval="100" as a prop. It's not 100% bullet proof, but we went from it happening like 10% of the time to 0.01%, so extremely rare.

vsambor commented 3 months ago

I had the same issue with the RecycleScroller, when the list changes sometimes (especially the first time, but not only) the items would overlap. And as a workaround I found that you have to recreate the entire component. This is how I did it.

const shouldUpdateScroller = ref(true);

// When my list data updates
watch(list, () => {
    // Force scroller to refresh;
    shouldUpdateScroller.value = false;
    nextTick(() => {
        shouldUpdateScroller.value = true;
    });
});

// Then in the template

<RecycleScroller
            v-if="list.length > 0 && shouldUpdateScroller"
            ...
 />

It's sad the lib is not maintained anymore :(

vsambor commented 3 months ago

Also make sure you import the CSS.

import { RecycleScroller } from 'vue-virtual-scroller';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';

I saw this may happen because the resizer dom element created by the virtual scroller, does not have the proper css.

Note that after adding the CSS, you will have to adjust properly the item size, instead of keeping it 1px.