bvaughn / react-virtualized

React components for efficiently rendering large lists and tabular data
http://bvaughn.github.io/react-virtualized/
MIT License
26.15k stars 3.06k forks source link

[STICKY] Tell me how you're using react-virtualized #147

Open bvaughn opened 8 years ago

bvaughn commented 8 years ago

A better understanding of how react-virtualized is being used will help me better prioritize features and shape the roadmap going forward. So if you're using this project, I'd love for you to leave a comment and tell me the following:

  1. What are you using react-virtualized for (eg. form controls, data grids, etc)?
  2. Which component(s) do you find most useful?
  3. (Optional) What product(s) / project(s) are you using react-virtualized in?
bvaughn commented 6 years ago

That looks very cool @Codelica 😄 Thanks for sharing!

Don't suppose you could share any of your custom cache code? 😄

Codelica commented 6 years ago

To be honest, after several variations (some using / managing my own CellMeasurerCache), where I ended up is almost embarrassingly simple. I sort of sole two ideas used by CellMeasuer (keyMapper and cache.rowHeight).

Basically every row that comes in (via websocket) has a unique ID, so I calculate heights and store that info in a Map as they come in (and remove rolled ones). Then I feed the List a getRowHeight function that pulls directly from the Map via the rows ID (rather than index). And bumped up my max log size before rolling so that I wouldn't have to recomputeRowHeights() until people are being real gluttons. ;) So a little compromise, but seems to work well.

bvaughn commented 6 years ago

Thanks for elaborating!

so I calculate heights and store that info in a Map as they come in

I'm mostly interested in how you do the calculations in a way that makes it faster than using CellMeasurer 😄 Anything I could learn from to improve the default behavior?

Codelica commented 6 years ago

Ahhh... I see. Well to be honest I'm not sure it would be any "quicker" mechanics-wise for lookups. (Map vs plucking from an object in the end?). I imagine CellMeasurer has some additional overhead in what it does calculating the heights, but in the end the reason I didn't use it wasn't directly related the performance of cached height lookups themselves. It was more about viewing performance when scrolling back (up).

Basically lots of log rows can come in quickly (hundreds a second at times) and it's trivial for me to compute their height as they come in so I always have a complete cache of all that info before any element is rendered in the list. So scrolling back up is always smooth, no jerkyness due to calculations of height data not in the cache yet (which would come going the CellMeasurer route in my case)

The only real hitch for me at this point is once the log starts to roll (at 100k entries currently) where recomputeRowHeights() has to hit, potentially quite frequently as new data pours in. There are things I can do about that (delaying the recalc until they decide to scroll up again, or maybe the flow has slowed or stopped a cycle).

But beyond that for my use case RV would need to have more control over and understanding of the manipulation of the list/grid data. Like telling it index 0-121 are gone now and it having an optimal way of just removing that range and recalculating, etc. I can't say I looked deep enough to understand or know what's going on completely, but it seemed like that would be a somewhat difficult given they way the internal caches are currently (object props based on index numbers IIRC).

Sorry if I'm not clear here. But basically just saying it would need to be able to better "understand" list modifications for my use case rather then a full recomputeRowHeights() with 10 new rows rolling through. (even though cached heights are at least used) The next step would just for that recompute to be minimized to only what's changed. But in order to do that it would have to have an understanding of the change and the underlying caches would have to be in some form that make manipulation like that possible/performant.

Wow.. that's some rambling. Time to cut myself off ;)

EDIT: Actually and now that I think about how row height have to be formulated and summed, it may not even be possible (or worthwhile) ! It's a tough problem for sure.

bvaughn commented 6 years ago

Ahhh... I see. Well to be honest I'm not sure it would be any "quicker" mechanics-wise for lookups. (Map vs plucking from an object in the end?). I imagine CellMeasurer has some additional overhead in what it does calculating the heights, but in the end the reason I didn't use it wasn't directly related the performance of cached height lookups themselves. It was more about viewing performance when scrolling back (up).

Ah, yes. Sounds like we're both using objects for our "maps". I wouldn't imagine much of a perf difference there. I was more asking about the process of measuring/calculating initially.

Basically lots of log rows can come in quickly (hundreds a second at times) and it's trivial for me to compute their height as they come in so I always have a complete cache of all that info before any element is rendered in the list. So scrolling back up is always smooth, no jerkyness due to calculations of height data not in the cache yet (which would come going the CellMeasurer route in my case)

Hm! This should be the case for CellMeasurer as well, because once an item has been measured, its measurements should come from the CellMeasurerCache and CellMeasurer should not measure it again.

But beyond that for my use case RV would need to have more control over and understanding of the manipulation of the list/grid data. Like telling it index 0-121 are gone now and it having an optimal way of just removing that range and recalculating, etc. I can't say I looked deep enough to understand or know what's going on completely, but it seemed like that would be a somewhat difficult given they way the internal caches are currently (object props based on index numbers IIRC).

Based on the index by default, but can be based on a more stable id if you provide the cache a keyMapper prop.

Sorry if I'm not clear here. But basically just saying it would need to be able to better "understand" list modifications for my use case rather then a full recomputeRowHeights() with 10 new rows rolling through. (even though cached heights are at least used) The next step would just for that recompute to be minimized to only what's changed. But in order to do that it would have to have an understanding of the change and the underlying caches would have to be in some form that make manipulation like that possible/performant.

I may be misunderstanding you, but this is part of why the measurements (CellMeasurerCache) are stored separately from positions (List/Grid internal cache).

All that being said, I believe that you found a way to improve perf in your app. I'd love to better understand how you did it though, in case anything could be applied to the built-in react-virtualized components. :smile: Don't suppose you could show me your code?

JobLeonard commented 6 years ago

So the application I've been working on is finally opened to the public:

https://github.com/linnarsson-lab/loom-viewer

I probably should start adding some gifs...

To quote my comment from last year:

We're building a data-browser for RNA data, where we have to quickly fetch and display data for a selection out of of 25000+ genes. The scrolling kinda breaks.. but we're basically using it more as a type-to-find input anyway

Funny enough, the scrolling (with the mouse) is currently broken too, but in this case it's a known issue with React-Select, not me ;)

Codelica commented 6 years ago

Hm! This should be the case for CellMeasurer as well, because once an item has been measured, its measurements should come from the CellMeasurerCache and CellMeasurer should not measure it again.

Yep, that part is true, but isn't really the snag I ran into using it. :) The key to what I was trying to avoid with it is actually mentioned -- "once the item has been measured". Basically at times my new log data can come in so quickly that many items are not rendered as they come in, and therefore are not measured (and height cached) by CellMeasurer as they come in.

For example, my list view may show 10-30 items at a time (depending on heights), and is usually pinned to the "end" of the list via scrollToIndex (although users can override that by moving away from the end). So if 400 new log entries hit during a render cycle, the majority wouldn't get measured and cached by CellMeasurer (unless I would have cranked overscanRowCount sky high perhaps? although that feels like a bad idea. :) )

The end effect of going that deferred measurement route, with chunks of missing heights in the CellMeasurer cache, was jerky upward scroll movement, as CellMeasurer would actually measure and cache heights that it didn't have a chance to previously. Basically it's what some people are trying to find a general solution for in #803 and #610.

Since I had an easy calculation I could run to generate and cache row heights for each item, as they came in, before each render, that just seemed to be the cleanest route. At first I did try using CellMeasurerCache myself to manually manage the height data (via set() and clear()), but once I looked at what its rowHeight() function was doing, I just decided to do my own map. Also to be insulated from any changes that may come to CellMeasurerCache.

Based on the index by default, but can be based on a more stable id if you provide the cache a keyMapper prop.

Yes.. and that's a concept I stole from CellMeasurer for my cache and measure functions also. Definitely needed in my case.

I think where I probably confused you is talking about several caches without being specific. Both CellMeasurerCache's height cache (when used with a keyMapper) and my simple height cache serve the same function -- caching raw height info for each row via some id. I just cached things manually so I was assured there was complete height cache, with entry for every row as mentioned above. I doubt there is much (if any) performance benefit beyond working around that scrolling quirk. If there was any, it would just be from reduced code for a height "measurement" as mine is trivial.

But then there are the other "higher level" internal caches for the list (_cellCache and _styleCache) which are used, and get constructed from the raw cached height data, and if I understand correctly, are cleared and rebuilt when a recomputeRowHeights() hits. While I understand that should be "quick" using cached measurements, when there are potentially 100k rows in my list, it's not trivial to do that a lot with new data streaming in. Since I'm just clearing entries off the top of the list and tacking some on the end each cycle, I'd like to think there is a better way than dumping those completely. However given how the heights are cumulatively summed, that's probably just reality. Any optimization there would probably have to come on my end, only calling recomputeRowHeights() when really needed, rather than every render cycle once the log starts to roll.

All that being said, I believe that you found a way to improve perf in your app. I'd love to better understand how you did it though, in case anything could be applied to the built-in react-virtualized components. 😄 Don't suppose you could show me your code?

Prepare for disappointment!! ;) The relevant parts are really trivial...

Basically I get new rows via via websocket in a parent container component. As they come in, I just call the appropriate method to calculate and cache height which are like:

// Set our calculated height for a PostAuth log row
setPostAuthRowHeight(row) {
  this.rowHeightCache.set(row.id, (20 + 20 * row.payload.extraRows));
}

...and of course delete id's from that rowHeighCache Map once they are pushed out of the log. So I always have fully populated cache for the log(s) based on ID. Then for my list:

<List
  style={{outline: 'none' }}
  width={width}
  height={height}
  rowCount={p.log.length}
  noRowsRenderer={this.noRowsRenderer.bind(this)}
  rowRenderer={this.rowRenderer.bind(this)}
  onScroll={this.logScrolled.bind(this)}
  scrollToIndex= {p.syncLog ? p.log.length -1 : undefined }
  rowHeight={this.getRowHeight.bind(this)}
  ref={p.sendListElement}
/>

Which gets the heights via getRowHeight() which is:

// Return our calculated hight for a log row
getRowHeight({index}) {
  return this.props.getRowHeight(this.props.log[index].id);
}

Which just uses the getRowHeight function prop that's passed in:

// Get our calculated height for a log row (sent as prop to WebConsole)
getRowHeight(id) {
  return this.rowHeightCache.get(id);
}

I'm sure you get the idea. Its just a raw Map being used. Sorry to give you any false hope. :)

Although now that I think about it, I may just try sending the calculated row hight in as part of the actual log entry. That should be about as direct as possible!

Sorry to get wordy, thanks again.

treyhoover commented 6 years ago

I put together a little slider/carousel with react-virtualized and react-motion.

demo

Feedback/PRs welcome! 🙂 https://github.com/treyhoover/react-sliderp

kvolkovich-sc commented 6 years ago

Hi! We builds filemanager for react using react-virtualized.

https://github.com/OpusCapita/filemanager

filemanager-demo

Now we use Table for files ListView, but in near future we'll implement GridView and TreeView using your awesome library. :smiley:

bvaughn commented 6 years ago

Cool! Thanks for sharing, @kvolkovich-sc 😁

ryanflowers commented 6 years ago

Hi there! Is anyone using react-virtualized with the cellMeasurer to renderer a List having truely dynamic row heights they can share? I mean row content is variable which is of unknown height and cannot be provided to the RV component. I see references to doing this in other issues but not seeing it actually being done in any examples. From what I can see the height is always provided in the examples. I am building a chat client which each message has content of variable height. I assume this is what the cellMeasurer is built for. In my case my measurerCache gets updated according to row content height so the measurer seems to be doing its job but the Grid ref is always undefined.

/Grid.js if (this.Grid) { this.Grid.invalidateCellSizeAfterRender({

Any examples of someone actually having RV calculate the height of the content would be a great help!

bvaughn commented 6 years ago

Hi @ryanflowers. This issue isn't for Q&A. 😄 That's better suited for Slack and Stack Overflow.

There's an example of what you're asking about on the react-virtualized demo page and the source is in this repo as well. If you have follow-up questions about this, please hop in the Slack and ask them there. 👍

Taylord93 commented 6 years ago

I am using this for a Cordova app I am building which lists out about 700+ objects in the form of component links that are pretty complex, lots of even handlers and dynamic data. I came across it while searching for a solution because when the whole list is rendered, the animations and transitions on the page broke down quite a bit and became unusable. That being said, WindowScroller and VirtualScroll are the only components I have used so far. Its been great! The app itself is basically a creature repository for D&D that is filterable/searchable, but there is a LOT of data processing, and react-virtualized has made my performance skyrocket.

Really the only issue I've come across is making a mobile 1 column grid responsive to become a 2 column grid on larger screen sizes.

cquiroz commented 6 years ago

I've created a partial Scala.js facade react-virtualized. For the moment just Table as that is the one I need but I may expand to cover the rest as needed. The facade is here

https://github.com/cquiroz/scalajs-react-virtualized

We use it on a realtime monitoring software on the Gemini Observatory where our primary language is scala and use scala.js for the front end

Thanks this project has been invaluable to make our UI much more usable

queicherius commented 6 years ago

What are you using react-virtualized for (eg. form controls, data grids, etc)?

Which component(s) do you find most useful?

Table / Column, WindowScroller, AutoSizer, List

(Optional) What product(s) / project(s) are you using react-virtualized in?

gw2efficiency for the virtualized tables (these are all the skins in the game Guild Wars 2, ordered by how many uses of the page have unlocked them and their unlock value).

Also, multiple internal projects (that I can sadly not share) use the select menus.

MarcMagnin commented 6 years ago

@Codelica great interface! BTW how do you deal with window resize? Do you recalculate all rows height? I like the font you've been using their! May I ask what is that font?

Codelica commented 6 years ago

@MarcMagnin Thx. Mobile (phone) support wasn't a concern for this one, so there is a reasonable min-width set to keep log lines from wrapping, and a fixed hight is set for the console view. But yes, changes like that would require a re-calc/render.

I used React Material UI on that (https://material-ui-next.com/) so the main layout font is Roboto. Console view uses Source Code Pro.

evanfrawley commented 6 years ago

What are you using react-virtualized for (eg. form controls, data grids, etc)?

andrewvmail commented 6 years ago

Using it in a mobile cordova app for chat.

sarathy-partha commented 6 years ago

Great work @bvaughn...thank you so much for your contribution...

Here is my Reference implementation,

List of popular movies from tmpb (over 18000 of them). Used flex-box, material-ui to display as cards.

Demo Source React-Virualized reference (InfiniteLoaded + WindowsScroller + AutoSizer + List)

<InfiniteLoader
              isRowLoaded={this.isRowLoaded}
              loadMoreRows={this.loadMoreRows}
              rowCount={
                this.props.page.page === this.props.page.totalPages
                  ? this.props.movies.length + 1
                  : this.props.movies.length
              }
              minimumBatchSize={5}
            >
              {({ onRowsRendered, registerChild }) => (
                <WindowScroller>
                  {({ height, isScrolling, scrollTop }) => (
                    <AutoSizer disableHeight>
                      {({ width }) => {
                        itemsPerRow = Math.floor(width / 415);
                        const rowCount = Math.ceil(this.props.movies.length / itemsPerRow);
                        return (
                          <List
                            ref={registerChild}
                            onRowsRendered={onRowsRendered}
                            isScrolling={isScrolling}
                            autoHeight
                            width={width}
                            height={height}
                            rowCount={rowCount}
                            rowHeight={820}
                            rowRenderer={this.rowRenderer}
                            scrollTop={scrollTop}
                          />
                        );
                      }}
                  </AutoSizer>
              )}
            </WindowScroller>
        )}
 </InfiniteLoader>
AndreyChernykh commented 5 years ago

@turnerniles Hi! How did you manage to make the sync scroll for the header and left column so smooth? I've looked at your code, but I see just a <ScrollSync> in use. Using the same component I still have a visible lag between scroll position of the main table and the header. And it is a known issue https://github.com/bvaughn/react-virtualized/issues/369

So, I would be great if you explain the solution briefly.

Thank you!

reyanshmishra commented 5 years ago

@bvaughn thank you for creating such an awesome project. I had a question I don't know if this is the right place to ask? Can I use error boundaries for an item in a list?

WhoAteDaCake commented 5 years ago
  1. Using a tool built on top of it https://github.com/SpotlightData/react-lazylog to be able to load massive text files without breaking the page
  2. VirtualList
  3. Nanowire (https://www.spotlightdata.co.uk/)
javidjamae commented 5 years ago

Thanks for all the great work you've done on this project! We use it for all of our grids on www.skipcard.com

Carrotzpc commented 4 years ago

Hi there, thanks a lot. I use react-virtualized to display massive logs on tenxcloud.com. But I have recently encountered a problem, I can only copy the log of the current page, but I can't copy the log across pages. I didn't find the relevant content in the official documentation. Does anyone know how to solve this problem?

PS: The problem can be reproduced on this page https://bvaughn.github.io/react-virtualized/#/components/List

benforloop commented 2 years ago

Hi there. Firstly this library had been hugely useful to me so thanks for taking the time to save a lot of mine. I have a simple question however I recently tried a simple implementation of the virtualized list component with a specified height and width in addition to using the the CellMeasurer component to calculate the heights of my rows dynamically. I have found that while varied div sizes are easily responding to these height discrepancies when I use the input form element with multiline functionality the height of the containing element is used as if no text were expanding the cell and I was wondering if there is any way to account for the size changes of the input.

I am not certain how the CellMeasurer acquires its calculated heights but I'm slightly confused that the measurement of the JSX elements prior to the entry of the state into the input is used when I have seen through a ref on the top level element that the size seems to have been accounted for on the first render within the JSX. Any help would be appreciated.

Subha commented 2 years ago

This is a stable library that we are using for a while, I am wondering if it supports web components as part of the row, especially with cellMeasurer in rowrenderer?