React-window: Version 2 changes

Created on 29 Jul 2019  ·  44Comments  ·  Source: bvaughn/react-window

This is an umbrella issue to share my plans for the upcoming version 2.0 release of react-window.

Feedback is appreciated and will be taken into consideration. I ask that you be understanding if I'm not able to accommodate all requests. I'll try to do my best to weigh feature requests against bundle size, runtime performance, and maintenance concerns.

I expect that upgrading from version 1 to 2 may require substantial code changes, many of which will not be possible to automate with code mods. Because of this, particularly for application code, I would advise against upgrading existing code unless you have a strong reason (e.g. you need support for dynamic size content).

I am also going to go ahead and pin the current docs to the domain react-window-v1.now.sh so they will not be lost when I update for version 2.


Table of contents:
  • Support fewer components
  • Use render props API
  • No more horizontal lists support
  • Only grids support RTL
  • Changes to onScroll callback timing

    * Other changes/deprecations

Fewer components

One way to help manage complexity is to reduce the number of components the library supports. I am currently planning on supporting only the following component types in version 2:

  • SimpleList (previously FixedSizeList)

    • This highly optimized list component should be used when row heights are fixed (and known ahead of time).

  • List (previously DynamicSizeList)

    • This list should be used for dynamically sized content (e.g. chat, newsfeed). It requires the ResizeObserver API (or polyfill).

  • Grid (previously VariableSizeGrid)

    • This component should be used for tabular data (e.g. spreadsheets) that should be virtualized along both vertical and horizontal axis. It supports variable size rows and columns, but does not support automatically measuring and updating their sizes.


Render props

One of the major changes from react-virtualized to react-window was the decision to treat children as React elements (e.g. React.createElement(children, props))) rather than as render props (e.g. children(props)).

There were a couple of motivations for doing this:

  • React provides built-in solutions for memoization (e.g. React.memo, useMemo, shouldComponentUpdate) so I didn't need to implement my own caching abstraction for item renderers.
  • APIs like hooks and suspense "just work" inside of item renderers.
  • Keys can be managed by react-window without requiring the render prop to pass them along (and without requiring cloneElement calls).

Unfortunately there were also some downsides:

  • Inline item renderers incur a high cost. Because their "type" (the function definition) gets recreated each time the parent component renders, React deeply unmounts and remounts their rendered tree. This means that docs need to teach people not to use them even though they're often more convenient.
  • Because inline functions couldn't be used to close over local scope, it was more complicated for item renderers to share state with parents, requiring APIs like itemData and a custom areEqual comparison export.

After taking the above pros and cons into consideration, I've decided to convert to a render props approach for react-window as well. This means that complicated examples like this can be re-written more easily:

const Example = ({ height, items, toggleItemActive, width }) => (
  <List
    height={height}
    itemCount={items.length}
    itemRenderer={({ index, key, style }) => {
      const item = items[index];
      return (
        <div key={key} onClick={() => toggleItemActive(index)} style={style}>
          {item.label} is {item.isActive ? "active" : "inactive"}
        </div>
      );
    }}
    itemSize={35}
    width={width}
  />
);

No more horizontal list support

Previously, list components supported both horizontal and vertical layout modes. In order to simplify implementation and maintenance, and because the overwhelmingly common use case is vertical lists, I will be removing support for layout="horizontal".


RTL support

Grid components will continue to support direction="RTL", but lists will not (since they will only support a vertical layout). This tradeoff is being made to enable lists to be smaller and easier to maintain.


onItemsRendered and onScroll callback changes

List and grid components currently support onItemsRendered and onScroll callback props. These callbacks are called during the commit phase (after the list or grid has completed rendering). This can be useful in that it is always safe to perform a side effect (like analytics logging) in response to these callbacks, but it has a downside as well: any scroll synchronized updates must be done in a second ("cascading") render.

Version 2 will make a change to the onScroll callback to address this. The onScroll callback will be called during the event's dispatch cycle so that any update will be batched (by React) with the list or grid's own update.

The onItemsRendered callback will be replaced with a onItemsDisplayed prop, although it will continue to be called during the commit cycle. This change is being made to enable the list component to more aggressively optimize render performance by pre-rendering at idle priority and make use of experimental APIs like display locking.


Other props changes/deprecations

There are several pending deprecations (with DEV warnings) which will be removed:

  • innerTagName and outerTagName for all list and grid components. (Use innerElementType and outerElementType instead.)
  • overscanCount, overscanColumnsCount, and overscanRowsCount for grid component. (Use overscanColumnCount and overscanRowCount instead.)
  • overscanCount will be removed for list components, in favor of a dynamic overscanning approach.
  • "horizontal" and "vertical" values for direction. (These were moved to layout, but they will be removed entirely in version 2.)
  • The itemData prop (and the corresponding data prop passed to item renderers) will be removed because the change to a render prop API no longer make this necessary.
  • The useIsScrolling prop (and the corresponding isScrolling prop passed to item renderers) will be removed because the changes to pre-rendering and display locking would make this more expensive to implement.
  • Scroll align parameters will change slightly. The previously named "auto" will now be named "minimal". The new default value will be "smart" (rather than "auto").

Note that some of the above deprecated props may not still be relevant given the other planned changes, but I'm listing them here anyway for completeness sake.

👋 help wanted 💬 discussion

Most helpful comment

Horizontal lists are widely used on mobile in our case. We are use them for infinite scrollable carousel like control too. IMO must have thing on mobile.

All 44 comments

Horizontal lists are widely used on mobile in our case. We are use them for infinite scrollable carousel like control too. IMO must have thing on mobile.

Thanks for sharing a use case @istarkov. It's not that I don't think there are _any_ valid use cases for horizontal windowing. I just think they are far less common. (Even in the case of carousels, many use left/right arrow navigation that doesn't require "scroll" event based windowing.) I think it might still be the better route (for me) to drop support for v2 to help offset some of the complexity I'm planning on introducing with pre-rendering, dynamic sizing, etc.

I'm really excited for this leaner approach that will hopefully lead to simpler, more performant, and simpler code.

Great. But I have few concerns about dropping variable size list. You are planning to replace variable size list with DynamicList but the issue with dynamic list that you can't scroll to item, which might be pretty useful feature for some components and such requirement could came up over time, so you can't predict if react-window fits your needs.
The second issue I see is dynamic list performance, if you can provide the same performance for dynamic list as for variable size list it might be ok to drop it even if you won't support scrollToItem, but if not, developers could be locked in weird situations where Dynamic list is slow and has no scroll to item support, List has only fixed items height.

You are planning to replace variable size list with DynamicList but the issue with dynamic list that you can't scroll to item

I don't think this is an inherent limitation, just one of the current implementation. I have ideas for how I could offer that function. Whether I find time to tackle it is another question :smile:

The second issue I see is dynamic list performance, if you can provide the same performance for dynamic list as for variable size list it might be ok to drop it even if you won't support scrollToItem, but if not, developers could be locked in weird situations where Dynamic list is slow and has no scroll to item support, List has only fixed items height.

Hard to say if it will perform as well. Seems like dynamic list will have to do more work so it's probable that it won't perform quite as well. Maybe the difference will be significant, maybe not. I want to be able to support some things I don't currently, and I think I need to cut scope to add new features. If you're currently happy with variable list though, there's no reason to upgrade to v2 (at least any time soon).

@bvaughn This looks like documentation to me!
Have you been working on this since last summer? ;-)

On a more serious note - I think render props make sense in this context, even though I'm not a fan.

No, I wrote this this morning 😝 I'm exhausted.

I think render props make sense in this context, even though I'm not a fan.

Can you elaborate why you're not a fan?

Could horizontal list/grid support make sense as an opt in feature like AutoSizer (react-virtualized-auto-sizer) ?

I've used both react-virtualized and react-window for horizontal lists, and find the react-window API a lot simpler. Though I def understand the need for the deprecation in hopes for a simpler API.

Grid support is still planned. Opt in horizontal support, no not really. I don't think that would make sense.

@bvaughn I generally enjoy render props and they will solve the problems you've outlined, but I I think they encourage an inline function which may or may not contain a bunch of nested logic. 🤔 Any fault here falls on the user though. 🙂

can you realize masonry grid?

@nikitapilgrim No. I would suggest just using the Masonry component from react-virtualized

https://github.com/bvaughn/react-virtualized/blob/master/docs/Masonry.md

thanks, but its work only with react-virtualized?

@nikitapilgrim Yup. 👍
React-virtualized supports a bunch of features, that react-window does not.

The scope of react-window is drastically reduced, to focus on making the package smaller and faster. 😉

If you need features from react-virtualized, I suggest you stick with that - it's still a great a library!

I stumbled upon react-window because I was looking for a solution to this react-virtualized babel 7 issue. I am using the react-virtualized Masonry components but can't anymore after upgrading babel.

This isn't the place to report issues with react-virtualized.

Agreed @bvaughn. However yourself and @babangsund replied to @nikitapilgrim above that a Masonry component won't be introduced in react-window and instead to use react-virtualized. However, if a user is on the latest [email protected] and [email protected] (such as with create-react-app), react-virtualized won't necessarily work.

I figure that is useful info for anyone else going down the same rabbit hole.
¯\ _ (ツ) _ /¯

hey i want to use dynamic size list where can i download version 2 or i will have to use react virtualise ?
image

this is how my list is looking with variable size list

This is not a generic support issue. Please keep comments on topic.

Feedback is appreciated

I'm using react-window on a trello-like product and here's some thoughts:

Render props

This seems like a good change because of the simplicity and to avoid the mentioned downside. Also, the api would be more similar to react-native's FlatList. I remember reading the discussion at https://github.com/bvaughn/react-window/issues/85.

Fewer components
No more horizontal list support

I highly vote against the removal of horizontal support and VariableSizeList. As you can imagine, these are the main two that I use. Are their implementation really too much of a burden to maintain? If not, I hope you can reconsider and keep them.

I don't have any strong opinions related to the other changes. The overscanCount removal does worry me a little but I've not faced use cases where a custom value was required yet, so it's probably fine.

Hey @bvaughn! Thanks for this package! I'm looking to incorporate version 2 in my project at work. Do you have a ballpark on when this might be released?

Thanks for this library, and sharing your plan for future release it's very helpful to help us mitigate technical debt.

Is there a "simple" way to have WindowScroller behavior using the recent react-window and later on version 2 ?

If it's not possible would this be out of the perimeter of this version 2? or could this be added later ?

My context is that my layout includes a footer, and using modern approach like DynamicSizeList would introduce a second scroll bar (one for the page and one for the list of products)

Great package. Eagerly awaiting dynamic height lists by default -- at the moment I wrote a bunch of custom logic for passing rendered row heights back up to the parent list. Hopefully with this new version I can remove that logic. Thanks for your work on this!

@ltkn @mrdanimal

There is already a way of using window scroller:
https://github.com/bvaughn/react-window/issues/30#issuecomment-428868071

How to do Scroll infinite in both directions, left or right!!!

If we have WindowScroller without depends on react-virtualized that will be great. Because the goal behind react-window made it much efficient and less code comparing to react-virtualized So, I don't want to use WindowScroller from react-virtualized. Instead looking for a separate package like react-virtualized-auto-sizer.

Thanks in advance.

@prabusamvel hm..
What's the advantage of a separate package?

Been playing around with the new DynamicSizeList. Very cool! I think a great enhancement would be to include 'ttb' and 'btt' (top-to-bottom, bottom-to-top) in the direction type. There is currently no clean way to implement a list that scrolls upwards, but every other direction is supported.

@toddmacintyre I can't find this version 2. Can any one guide me?

@muhammedmagdi npm install react-window@next

It would be brilliant if version 2 could support the DOM structures required by the spec here - https://www.w3.org/TR/wai-aria-1.1/#grid as #217 is still an issue for us. As far as I can tell your proposal for version 2 right now doesn't.

@mjurkowski @bvaughn yarn add react-window@next installs 1.6.0-alpha.1. Also when trying to run the dynamic list sandbox example https://react-window-next.now.sh/#/examples/list/dynamic-size it throws Error importing GitHub repository: Could not find package.json

How about passing the index in Grid as well - while at it?:)

Part of our application requires a grid with variable width column headers. We've achieved that by using a horizontal variable sized list coupled with a grid and synchronising the scrolling of the two.

Removing horizontal lists will prevent us creating that UI (or at least we'll have to re-write it to use 2 grids where the column header grid has a single row.

Next product looks amazing. But I need both horizontal and vertical, so FYI I created my own: https://www.npmjs.com/package/react-infinite-grid-scroller. Uses grid layout, react hooks, IntersectionObserver, and requestIdleCallback.

Feedback welcome.

I have the feeling that supporting sticky elements would be a great addition to react-window. It is a pretty common use case and would be straight forward to implement for both lists and grids.

The API could look like this:

<Grid
    // First 2 columns are sticky
    stickyLeft={2}
    // Last column is not sticky (default value)
    stickyRight={0}
    // First and last rows are sticky
    stickyTop={1}
    stickyBottom={1}
/>

The two key elements to make the stickiness work are:

  1. only unmount cells that are sticky if they are out of the window in the axis they are not sticky
  2. use position: sticky instead of absolute, and use margin-x instead of left and top for the axis that is not sticky

The cost of such a great feature seems reasonable:

  • The footprint is minimal
  • No major impact on performance (just rendering a few extra rows / columns)
  • Browser support is great, and those that do not support position: sticky just see a normal version

If you think this use case is too specific to be supported as is, a great compromise would be to support only point 1. Being able to specify whether or not a cell should be rendered would allow to implement stickiness easily.

I would be glad to help if needed.

does not support automatically measuring and updating their sizes.

I feel like this is a pretty big down side. For almost every grid I've written I need to have 3 or 4 fixed width columns (for a status label, some meta data, a call to action etc) and 1 dynamic width column that takes up the rest of the available space.

I appreciate that "does not support" doesn't necessarily mean this won't be possible with the addition of additional code and components like Autosizer - it would just be nice to have those use cases well considered and solutions documented for those that need a dynamic column but don't want to go to react-virtualized for all the baggage it brings with it.

it would just be nice to have those use cases well considered and solutions documented for those that need a dynamic column

Totally understood- but practically speaking, that would require a lot of effort, and this library has been a labor of love (not a paid endeavor). Unfortunately I haven't even had time to finish my scoped down v2 efforts, let alone anything more aggressive. 😞

it would just be nice to have those use cases well considered and solutions documented for those that need a dynamic column

Totally understood- but practically speaking, that would require a lot of effort, and this library has been a labor of love (not a paid endeavor). Unfortunately I haven't even had time to finish my _scoped down_ v2 efforts, let alone anything more aggressive. 😞

Yup. I get that. Hopefully those kind of efforts can be community led.

Would be nice- but in my experience, that never happens :smile:

I have been unable to use WindowScroller with DynamicSizedList, probably because of the just-in-time rendering making scrollTo not work very well. Will this be possible with the new version?

I have accepted the fact that I don't have the time or energy to finish this effort. If anyone would like to step in and finish the branch I started, I'd welcome your help. (Please also see issue #6 for details on the List and Grid should be implemented to support just-in-time measurements.)

Hi @bvaughn,
I have created a discussion on my fork with some random notes.
Let me know if there's anything that should not be on the list or that is not correct. I'll complete the list when I'll make progress.

Looks like good stuff

Was this page helpful?
0 / 5 - 0 ratings