adobe / react-spectrum

A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
https://react-spectrum.adobe.com
Apache License 2.0
12.88k stars 1.12k forks source link

Feature request: Window scrolling for Virtualizer #3965

Open datagutt opened 1 year ago

datagutt commented 1 year ago

šŸ™‹ Feature Request

The company I work in has built a UI library using React-Aria hooks. We are currently implementing a Table that contains a lot of products. Instead of scrolling inside the virtualized table, we want table to adapt to the window height. Currently we can not do this without loading all table rows into the DOM at once.

What would be ideal here, is to have a "window scroll" implementation for the virtualizer.

šŸ¤” Expected Behavior

I can scroll the whole page to load more table rows. Similar to these projects: https://virtuoso.dev/window-scrolling/ https://bvaughn.github.io/react-virtualized/#/components/WindowScroller

šŸ˜Æ Current Behavior

I can scroll inside the table/ScrollView only, instead of being able to scroll the page.

šŸ’ Possible Solution

From what i see, these are the options:

šŸ”¦ Context

The user experience of our site is impacted, because the user has to scroll inside the table instead of scrolling the whole page to load more products. Using a non-virtualized table instead, causes performance issues when a lot of products are loaded.

šŸ’» Examples

See the links in "Expected Behavior". I am willing to provide more context, design mocks or project link if required.

šŸ§¢ Your Company/Team

External company

šŸŽ Tracking Ticket (optional)

LFDanLu commented 1 year ago

Just to clarify, this would be a case where there aren't any elements below the table since the scrolling down would just load the additional items in the table correct?

The user experience of our site is impacted, because the user has to scroll inside the table instead of scrolling the whole page to load more products

I'm curious about what you mean here, what kind of user experience issues have you ran into with the current Virtualizer experience (aka scrolling the table/scrollview)? Is it that the user would need to tab out of the table to resume scrolling the page?

datagutt commented 1 year ago

Hi, Our site contains a filtrering menu for product criteria, and a table next to it. chrome_7bRQyL14cs

We have an some general information and a footer element below the table. Part of the issue is that the user would try to scroll the page and see the elements below (footer etc) instead of the rest of the products.

This can be compared to our current site, where you can simply scroll the whole page.

dsmmcken commented 1 year ago

I would really really like this as an option for anywhere the virtualizer is used. Given a list of a known size, the non-viewported infinite loading model can be really annoying to scroll to the end.

Imagine this example, but sorted alphabetically and you want to select "zubat" which you know will be near the end of the list. https://react-spectrum.adobe.com/react-spectrum/Picker.html#asynchronous-loading

In a viewported virtualized list, you can immediately scroll to the end (setting the cursor to load data proportional to the scroll position) and immediately select "zubat". In the current implementation you have to wait and wait for each page to load in as you scroll.

If the collection size is known (which if it's loaded from an API, length should be knowable) and you fixed height items (which is often the case), then I find the usability much better.

edit: I don't mean using the document scrollbar which I see now is what this ticket was referring to, I meant just a "viewported" virtualized list, perhaps there's another ticket for that?

snowystinger commented 1 year ago

@dsmmcken I don't think we have any sort of ticket for that right now. Couldn't you just create a collection with the known # of items, then make a bunch of API calls on your own to update the items in the list? You wouldn't have to use the async loading for that. We know there are cases that useAsyncList won't cover, you are encouraged to write your own useXList hooks depending on your needs. They can be based on ours if that makes it easier. Just make sure to keep keys the same when populating the empty collection and when you update the items.

dsmmcken commented 1 year ago

Thanks, I was planning on trying that out anyways to see if it would work.

strogonoff commented 10 months ago

@dsmmcken Couldn't you just create a collection with the known # of items, then make a bunch of API calls on your own to update the items in the list?

@snowystinger In this sense it is not windowing, because all item containers are in the DOM, right? With high enough numbers of items it starts to hamper performance.

This might not be the right ticket for it, but a big part of windowing (or virtualization), and how react-virtualized does it, is having an almost completely empty inner container with dimensions large enough for intuitive, predictable scrolling on the outer container. (In the simple case of a vertical list, the inner container would be <item count> * <item height> tall.) The component ensures that inner containerā€™s DOM only contains enough items to fill the visible window and maintain the illusion of scrolling. As you scroll up, DOM elements below get detached and DOM elements above get added, since they are mere containers browsers recycle them and handle it really efficiently. This way displaying a list or a table with millions of items takes only about as much compute and memory for the browser as displaying a dozen of them that fit in the viewport. (The actual item components within those containers, as youā€™d expect, receive each its own item identifier as a prop and load requisite data asynchronously on mount.)

When it works well, from user experience perspective it is much more preferable to infinite scrolling. You as a user might be exploring a dataset, knowing that thereā€™re items of interest to you somewhere halfway through a large list (and perhaps thereā€™s the iOS-style initial letter based quick scroll helper to get you there); I as developer know exactly how many items there is in totalā€¦ It would be completely silly for me to make you go through ā€œload moreā€¦ā€ dozens of times rather than let you jump around at will.

I wouldnā€™t say this is something this library must take care ofā€”I imagine thereā€™s no issue with using react-window or the like together with this libraryā€”however, it is a bit frustrating because searching across the docs ā€œvirtualizedā€ and ā€œvirtualizationā€ shows up numerous times for many components like grids, lists, tables, etc., but there is no detail on how to make use of it or certainty that itā€™s even real. On one hand, the Collection interface does look like it should support virtualization: it features size, getKeys(), and getItem() (single, as it should be). On the other hand, components like Menu, ListBox, are said to have virtualization, but their props require direct iterables of full items, not keys, which seems to defeat the point of windowing.

snowystinger commented 10 months ago

Hey! Thanks for the message. Just to clear a few things up to make sure we're all talking about the same things.

Window scrolling for virtualizer in this issue means deferring the scrolling from the virtualizing container to the document body (or some other ancestor).

React Spectrum collection based components mostly all support virtualization out of the box.

React Aria hooks based components can support virtualization, but do not come with it out of the box. We used all of the hooks in creating React Spectrum, so we know it can be done.

React Aria Components do not yet support virtualization; we'll hopefully pick that up sometime next year. We're also working on making our documentation clearer around this topic.

I don't have a good word for "assuming an empty collection with a known number of elements, scroll to position Y, load specifically only items around that spot", but that's what https://github.com/adobe/react-spectrum/issues/3965#issuecomment-1466496123 was asking for. The closest phase I can come up with is "Viewported loading", I think we can move forward with that so we know what we're talking about.

Theoretically I think that "Viewported loading" is possible in our virtualized components. The steps I'd take are roughly as follows.

  1. Get the size of the data set through an API call
  2. Create a collection of "data size" length with dummy unique keys and empty properties
  3. when scrolled to a certain position, make an API call for the items you know should be around that spot
  4. replace dummy items in the collection with the results
  5. continue watching for scrolling until entire collection is filled in This wouldn't use useAsyncList because that hook assumes paginated data and handles a bunch of things around those expectations
strogonoff commented 10 months ago

Thanks for sorting out the terminology.

Regarding the steps you listed, my concern (based on what I saw in the docs so far) is that container elements corresponding to all dummy collection items are still present in DOM. Is that how these virtualized components work? Even if we assume that no API calls are needed at all and all items are merely strings, thereā€™s a massive performance difference between maintaining an array of N strings in memory and rendering all N elements in DOM (when N is sufficiently large). The above N would correspond to ā€œdata sizeā€ in your comment.

Before I encountered this in my own experience, I thought browsers would somehow ā€œvirtualizeā€ and offload excess invisible elements, but it turns out they donā€™t do that at allā€”so the virtualziation approach I described in my previous comment makes a major difference (from waiting entire seconds just for the GUI to update to smooth and fluid experience).

Happy holidays!

snowystinger commented 10 months ago

Which component are you using? Or what hooks? I'm assuming a React Spectrum one since I've ruled out RAC supporting virtualization at this time and I assume it isn't our hooks because then you'd implement the virtualization yourself. If it's a React Spectrum component, you might just be missing a height on the component. Something to prevent it from growing vertically to fit all the content.