Skip to content
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

Ability to pass a render function for an item #85

Closed
jlongster opened this issue Nov 6, 2018 · 31 comments
Closed

Ability to pass a render function for an item #85

jlongster opened this issue Nov 6, 2018 · 31 comments

Comments

@jlongster
Copy link

react-virtualized takes a rowRenderer prop, and FlatList in react native takes a similar renderItem prop. What is the reason you chose to go with an API where you pass a component as a child instead?

Doing so forces you to go through this memoization process if you want to pass props down to the item. I would think this is an extremely common use case, and if you accept a render function instead we can pass down any props we want as normal props, allowing the user to leverage any of React's builtin checks for sCU (PureComponent, React.memo).

Would you be open to expanding the API a bit? Instead of passing a component as a child, we could instead provide two different props: itemComponent and renderItem, the former just passed a component (like now) and the latter passes the data into a function which returns a component.

@jlongster
Copy link
Author

I could provide an inline function component which then returns my full component with whatever props I passed into it, but that's doubling the amount the wrappers and seems unnecessary.

@bvaughn
Copy link
Owner

bvaughn commented Nov 6, 2018

Your timing here is funny. I'm having a similar conversation over on bvaughn/react-error-boundary/issues/26 this morning. 😄

react-virtualized takes a rowRenderer prop, and FlatList in react native takes a similar renderItem prop. What is the reason you chose to go with an API where you pass a component as a child instead?

The name of the prop itself isn't that significant, but I chose children because I thought it seemed a more natural fit– less arbitrary than "renderItem" and less direction-specific than "rowRenderer".

If you dislike this syntax <FixedSizeList {...props}>{Row}</FixedSizeList>, you could use this one instead <FixedSizeList children={Row} {...props} />. Or you could even use an inline function like the FlatList docs show:

<FixedSizeList
  children={( index, style ) => (
    // ...
  )}
  {...props}
/>

(Although I would caution against that if your list item is doing anything complicated, since React wouldn't be able to memoize it.)

The important distinction between react-window and react-virtualized is that the "item renderer" is wrapped in a React element rather than called directly as a function. This provides a couple of benefits:

  • Class components are supported too.
  • It allows React's concurrent mode to split rendering work across frames and defer actually invoking item renderers until it's ready.
  • It frees users up from having to worry about passing along the proper key attribute (without having to clone elements). This is actually one of the most common questions in react-virtualized. People forget to relay the key then wonder why their items re-render unexpectedly.
  • Item renderers automatically support newer APIs like React.memo and "hooks".

Doing so forces you to go through this memoization process if you want to pass props down to the item.

It's interesting that you see it this way. 🙂 I think it actually helps simplify memoization, because it allows you to use a class component that extends React.PureComponent as well as the new React.memo utility. If react-window treated the item renderer as a vanilla function, you would always have to add an extra layer around really performance sensitive code to use React's built-in memoization.

and if you accept a render function instead we can pass down any props we want as normal props, allowing the user to leverage any of React's builtin checks for sCU (PureComponent, React.memo).

To be clear, you could already do this by adding an inline function component wreapper with local scope, but recreating this wrapper on render would change its "type" and this would cause React to unmount and remount (rather than update) your items, which is definitely not what you want. This is a limitation of my API design.

Would you be open to expanding the API a bit? Instead of passing a component as a child, we could instead provide two different props: itemComponent and renderItem, the former just passed a component (like now) and the latter passes the data into a function which returns a component.

My initial reaction is a negative one. I don't want to increase the complexity of the implementation (or the docs). I don't think there's a compelling argument for this, but let's keep talking. Maybe you can change my mind!

@jlongster
Copy link
Author

It is an interesting discussion! I think we're coming at it from different angles, let me try to explain my side more.

If you dislike this syntax <FixedSizeList {...props}>{Row}</FixedSizeList>, you could use this one instead <FixedSizeList children={Row} {...props} />. Or you could even use an inline function like the FlatList docs show:

I wasn't focused on the syntax, but more that other libraries provide a prop which is a function that returns a component, rather than passing a component type directly to it.

As you mentioned, I don't want to create an inline function component since that has other limitations (can't memo, want to split out complicated stuff, etc)

  • Class components are supported too.

With a render function you can return any type of component, no? I don't consider a render function a "function component" - the rowRenderer and renderItem (in FlatList) are simple functions that can return anything. They are more powerful because they are a higher abstraction.

The point about key is a good one, but isn't a big enough deal to convince me.

  • It allows React's concurrent mode to split rendering work across frames and defer actually invoking item renderers until it's ready.

That's true, but I wonder how much of a benefit this really is? Since React components are lazy, it's not like it's actually rendering the components; rendering them could still be async. But returning ~100 components probably isn't a huge deal.

  • Item renderers automatically support newer APIs like React.memo and "hooks".

Not sure what you mean - that the lib can add the memo itself? I feel like most of the time people are going to want to pass down their own props, which means they are back to having to memoize themselves anyway. But now, I need to add a separate memoization library and use that instead of just being able to put React.memo on the component that I return in something like renderItem.

It's interesting that you see it this way. 🙂 I think it actually helps simplify memoization, because it allows you to use a class component that extends React.PureComponent as well as the new React.memo utility. If react-window treated the item renderer as a vanilla function, you would always have to add an extra layer around really performance sensitive code to use React's built-in memoization.

I must be missing something? See the example - if you need to pass props down you now need to memoize the data object with a separate library. With a render function I get exactly what you say - I can use anything I want to, just return whatever component that has PureComponent, memo, etc. Currently since react-window is controlling that I'm limited.

I think my main point is recognizing that you are always going to have to pass down additional data. Right now the Item component only gets index and style from the list, and you could maybe only need to pass the items array itself as the data object which won't change across rerenders - so that would work. But surely in most cases the user wants to pass down more stuff - like a callback, etc. In those cases where back to having to use memoize-one to memoize instead of being able to leverage PureComponent or React.memo.

@jlongster
Copy link
Author

Another option would be to spread the additional props so they are passed to your component like normal props:

<List itemData={{ onSelect: this.onSelect }}>{Item}</List>

// The lib renders Item like `<Item {...itemData} />` so:

function Item({ onSelect }) {
  // ...
}

Now Item can leverage PureComponent or memo for memoization, but you mentioned not liking spreading.

@bvaughn
Copy link
Owner

bvaughn commented Nov 6, 2018

With a render function you can return any type of component, no? I don't consider a render function a "function component" - the rowRenderer and renderItem (in FlatList) are simple functions that can return anything. They are more powerful because they are a higher abstraction.

My experience has been the opposite– that they can be a burden because they require you to add an additional layer around any work to properly minimize it. In the context of a windowed list, this can hurt performance if people end up doing non-trivial work in their wrapper function. I often got asked with react-virtualized "why is my row re-rendering in this case?" and with react-window the solution is much more straight forward: if it's a problem, just use PureComponent or React.memo.

That's true, but I wonder how much of a benefit this really is? Since React components are lazy, it's not like it's actually rendering the components; rendering them could still be async. But returning 100 components probably isn't a huge deal.

You're right! Although this relies on people to put any "heavy lifting" behind their own (memoized) Component and in practice, I don't think that's as obvious as it should be.

Item renderers automatically support newer APIs like React.memo and "hooks".

Not sure what you mean - that the lib can add the memo itself?

I mean that you don't have to worry about the distinction of which functions are React elements and which are functions. You can just use API features like memo or hooks.

Let's say you wrote some code like this:

import { FixedSizeList } from 'react-window';
import ListRow from './path/to/ListRow';
 
export default function List({items, ...rest}) {
  <FixedSizeList itemCount={items.length} itemData={items} {...rest}>
    {ListRow}
  </List>
);

If someone else came along later and wanted to try hooks in the ListRow component, react-window would support this! react-virtualized would not. Same for other APIs like React.memo.

I think it's confusing when the things you think of as your app's "components" are a mix of React elements and functions. There's something to be said about the consistency of "it's all just React elements".

I feel like most of the time people are going to want to pass down their own props, which means they are back to having to memoize themselves anyway. But now, I need to add a separate memoization library and use that instead of just being able to put React.memo on the component that I return in something like renderItem.

This is a valid criticism. If memoization is important, and if you need to pass more than just the collection then you would need to add your own lightweight memoization wrapper around item data. Fortunately the new useMemo hook will make this super convenient 🙂 but it is a current drawback.

However I'm not sure it's as big of one as you are suggesting, because the two types of memoization we're talking about are different. I am mostly concerned with memoization while scrolling, since that is by far the most performance sensitive part of windowed UI. Since the only thing that is re-rendering during a scroll is the list/grid itself, even if you didn't memoize the incoming data prop– your list items would still be memoized such that only the newly added ones would render.

The use case you're describing is when the application re-renders at a higher level. In this case, if all you're passing down is the e.g. items Array then no memoization is needed. If you're passing a complex object, and you don't memoize (with e.g. useMemo or memoize-one) then you're right– list items will re-render, but only the small, visible set of them. This might still negatively impact the perf of your app so it's worth considering, but I think it's less important than the scrolling use case.

Another option would be to spread the additional props so they are passed to your component like normal props

This is an interesting suggestion. Spreading is sometimes convenient, but it has some downsides too, such as props naming conflicts. I'm also not sure how we could generically Flow type that API (which may or may not matter to you if you're not using Flow I guess).

cc @ryanflorence who might find this discussion interesting.

@jlongster
Copy link
Author

My experience has been the opposite– that they can be a burden because they require you to add an additional layer around any work to properly minimize it. In the context of a windowed list, this can hurt performance if people end up doing non-trivial work in their wrapper function. I often got asked with react-virtualized "why is my row re-rendering in this case?" and with react-window the solution is much more straight forward: if it's a problem, just use PureComponent or React.memo.

I think more power tends to be a burden because, yes, you are taking on more for yourself. Personally I prefer it that way, as it's more flexible, but I understand why others might be confused.

If someone else came along later and wanted to try hooks in the ListRow component, react-window would support this! react-virtualized would not. Same for other APIs like React.memo.

I guess my only reply would be: of course :P If people expect a normal function to act like a component, that seems like a very basic misunderstanding of the API. It's a function, and you return a component. In that component you can do whatever you want. That's almost like people expecting to be able to use hooks in a callback method, or use React.memo on it.

This is a valid criticism. If memoization is important, and if you need to pass more than just the collection then you would need to add your own lightweight memoization wrapper around item data. Fortunately the new useMemo hook will make this super convenient 🙂 but it is a current drawback.

Hooks are far from being stabilized, so until then I think it's worth trying to make the API easier to use for others. I tried hard converting this component to hooks actually, but they aren't ready yet.

Even with useMemo, I think it feels a little awkward to depend on it just to pass down props to a child component. I think it's very common these days to allow more full control by accepting a function that allows you to pass whatever you want down. Having to put all of my callbacks in an object that is memoized doesn't feel like very idiomatic React.

I'm sure as a maintainer you have been met with a lot of confusion because of that flexibility, and I get why you'd want think to be more opinionated out of the box. I'm against a large surface area of APIs, but in this case I think it could be appropriate to provide an additional prop which accepted a normal function. I think it would be easier to devs to go from the basic case which has everything builtin, realize they need to pass a bunch of stuff down, and then convert it to a render function which applies props given from this lib, instead of having to install memoize-one and create a memoized object and then access it from this.props.data in the item instead of this.props.

I just realized it also creates an API inconsistency because I use my Item component in a few other places in special cases. Right now it takes props normally, so I can just render it out myself. But if I converted it to this API, I'd have to pass down my extra props through data.

I'm just thinking out loud here. It's OK if I don't end up convincing you. I'm used to the other API, and I guess I could create an inline function component that just renders mine. One small wrapper shouldn't effect perf very much.

However I'm not sure it's as big of one as you are suggesting, because the two types of memoization we're talking about are different. I am mostly concerned with memoization while scrolling, since that is by far the most performance sensitive part of windowed UI. Since the only thing that is re-rendering during a scroll is the list/grid itself, even if you didn't memoize the incoming data prop– your list items would still be memoized such that only the newly added ones would render.

That's a very good point. Personally I haven't hit a perf problem with the list rerendering since it's so fast because my individual items use PureComponent. But maybe there would be slightly better UX if it literally didn't have to render at all.

This is an interesting suggestion. Spreading is sometimes convenient, but it has some downsides too, such as props naming conflicts. I'm also not sure how we could generically Flow type that API (which may or may not matter to you if you're not using Flow I guess).

I don't use Flow, and yeah conflicts are a problem. Another options would be to invert it so that you pass the scroll info in as an object instead of individual props, and spread the user's props out so they are normal. Not sure though.

@jlongster
Copy link
Author

I understand where you are coming from, though. You are after memoization so that you don't have to do anything when scrolling, and I just want to memoize my Item components so when I change my state they don't all rerender.

It's worth noting even if you've memoized it so that scrolling is free, if I use memoize-one to memoize my data object across renders, it's still doing the same work as before. When it rerenders it has to compare the equality of all the props and check if the memoized value is up-to-date, but now it's just memoize-one doing that instead of PureComponent or React.memo.

Actually, I just realized that itemData is a single object across the whole list, not per item. Hm, that's interesting... right now my Item takes props like highlighted and the list component passes a boolean value based on the state like this.state.highlightTransaction === trans.id. I guess you're encouraging devs to do more of this work inside the Item component itself, so that you're doing less work while scrolling?

I can see that. I suppose I could have an "intermediate" Item component that is meant to be rendered in a list, and knows how to take a list of items, index, and other properties taken from itemData and render the same Transaction component that I have now. That would mean less work while scrolling... I'll play with it and see how it feels. It will take me some time to refactor my code though. :)

@jlongster
Copy link
Author

fwiw the main reason I'm looking into react-window is after turning on async mode, I see a lot more blank space while scrolling when using react-virtualized. I don't really know why - I'm guessing it's somehow not flushing out the DOM as much as it was before. Maybe that means scrolling is actually more fluid, but I don't see the list rendered as much. Maybe this is because it is doing too much work while scrolling? Anyway, I'll try this out and see how it goes.

@bvaughn
Copy link
Owner

bvaughn commented Nov 6, 2018

I guess my only reply would be: of course :P If people expect a normal function to act like a component, that seems like a very basic misunderstanding of the API. It's a function, and you return a component. In that component you can do whatever you want. That's almost like people expecting to be able to use hooks in a callback method, or use React.memo on it.

We might just agree to disagree here. 🙂 I've seen people burned by this in other libraries when a render function isn't a real React component. (This sort of thing also regularly causes pain for e.g. Enzyme users after updates.)

and I guess I could create an inline function component that just renders mine. One small wrapper shouldn't effect perf very much.

I would advise against this, since recreating an inline function would change its element type which would cause React to unmount and remount it (and blow away any state in your inner component).

It's worth noting even if you've memoized it so that scrolling is free, if I use memoize-one to memoize my data object across renders, it's still doing the same work as before. When it rerenders it has to compare the equality of all the props and check if the memoized value is up-to-date, but now it's just memoize-one doing that instead of PureComponent or React.memo.

I don't think I agree that it's the "same as before". Strict equality checking a few values is going to be less work than rendering an item, even if the difference is negligable.

fwiw the main reason I'm looking into react-window is after turning on async mode, I see a lot more blank space while scrolling when using react-virtualized. I don't really know why - I'm guessing it's somehow not flushing out the DOM as much as it was before. Maybe that means scrolling is actually more fluid, but I don't see the list rendered as much. Maybe this is because it is doing too much work while scrolling? Anyway, I'll try this out and see how it goes.

I would love to hear how react-window ends up working out for you relative to react-virtualized!

@jlongster
Copy link
Author

We might just agree to disagree here. 🙂 I've seen people burned by this in other libraries when a render function isn't a real React component. (This sort of thing also regularly causes pain for e.g. Enzyme users after updates.)

That's very surprising, since the whole render prop concept is built on the idea that you can just pass a function in and return something. While hooks will eventually replace that, that concept is well-understood from my point of view.

I don't think I agree that it's the "same as before". Strict equality checking a few values is going to be less work than rendering an item, even if the difference is negligable.

I mean the same as if I was allowed to render my own items, and those items were memoized. It'll rerender the list but do the prop equality checking, and that's it. Just like if this lib memoized them itself.

Also, you mention the perf sensitivity of rendering on scroll and the different cases of memoization that we want. But is it really that different? In onScroll you shouldn't need to rerender anything if you determine that what's already on screen is enough, so in that event you don't set any state. But when you do need to render new items, you change state which rerenders the whole list, but the ones already existing are memoized. I think that's what both of want, but we just disagree on where that memoization takes place. I'm saying I want to provide it myself because it feels like less ceremony for the common case of passing props down, but you want to have to baked in so it's less confusing for newcomers.

I might experiment with a wrapper around List that provides the API that I want, would be interesting to see how it maps! (and would make it easier for me to switch)

@jlongster
Copy link
Author

Sorry if it sounds like I'm arguing, I'm commenting without much edits since I don't have much time :)

@bvaughn
Copy link
Owner

bvaughn commented Nov 6, 2018

That's very surprising, since the whole render prop concept is built on the idea that you can just pass a function in and return something.

A lot of people don't understand the subtle differences between MyComponent({ foo, bar }) and <My Component foo={foo} bar={bar} /> and so are surprised when certain APIs (e.g. refs, context, hooks, etc.) don't work in the former.

Also, you mention the perf sensitivity of rendering on scroll and the different cases of memoization that we want. But is it really that different? In onScroll you shouldn't need to rerender anything if you determine that what's already on screen is enough, so in that event you don't set any state.

Check out some of the caching and cache-busting I'm doing in react-virtualized grid component sometimes (for elements and styles) to make this work. By comparison, react-window is much simpler (and smaller, and probably less buggy) because it just lets React manage all of this.

I might experiment with a wrapper around List that provides the API that I want, would be interesting to see how it maps! (and would make it easier for me to switch)

Sounds great 😄 Keep me posted.

Out of curiosity, if I were to spread itemData props onto the item render, so you didn't need to do the extra memoization layer (for advanced cases, which probably aren't that common to begin with so we're potentially over-optimizing here) ––– would you be happier with the react-window API? At that point, it's strictly simpler isn't it?

@jlongster
Copy link
Author

jlongster commented Nov 6, 2018

I've seen references to the cache stuff in react-virtualized, I think I see what you're saying. I love that react-window feels a lot simpler for sure.

I'm not entirely sure if spreading will solve it entirely, since I understand now that it's not a per-item data object. I think I'll still need to change my code so generating the per-item state from the data object happen within the item itself. Spreading made sense to me more when I thought I could generate per-item data objects.

I was going to say that I can't imagine this being over-optimizing, but I think the difference of understanding is that the itemData is a "global" object for the entire list. It's not generated per-item. So making a new data object to pass down per item is done just once when the list rerenders, and that's probably not a big deal. No need to memoize that (it's mostly callbacks for me). What really does need to be memoized is each item, so that changing some of my state only rerenders the items that it effects. This API makes you think about that a little differently: I'll basically do what I do now in renderItem inside an "intermediate" component that is always rerendered when the list's state changes, but that renders out the Item component itself with a bunch of dumb props, and that is memoized. So changing state results in the minimal rendering possible.

@bvaughn
Copy link
Owner

bvaughn commented Nov 6, 2018

I'll basically do what I do now in renderItem inside an "intermediate" component that is always rerendered when the list's state changes, but that renders out the Item component itself with a bunch of dumb props, and that is memoized. So changing state results in the minimal rendering possible.

I think this makes sense. Please keep me posted if this turns out to not work well.

I'm going to close this issue (since it's not actionable) but let's keep the conversation going as you try things out! 😄

@jlongster
Copy link
Author

Thanks for taking the time to respond so thoroughly!

@bvaughn
Copy link
Owner

bvaughn commented Nov 7, 2018

Regarding spreading itemData props onto the item renderer component, that might actually be as trivial as changing the Flow types from:

type RenderComponentProps<T> = {|
  data: T,
  index: number,
  isScrolling?: boolean,
  style: Object,
|};

...to...

type RenderComponentProps<T: Object> = {|
  ...T,
  index: number,
  isScrolling?: boolean,
  style: Object,
|};

cc @TrySound to sanity check this 😄

Maybe it's worth thinking long and hard about the most intuitive API. Is it worth risking possible props naming conflicts (with both current props and, any future ones we might add, since they would become a backwards breaking change) to simplify the memoization story?

I listed some pros and cons here: https://gist.github.com/bvaughn/39ee223c7503a834afc1c2e94df27fc5

@TrySound
Copy link
Contributor

TrySound commented Nov 7, 2018

I'm not sure. This forces us to always pass an object to itemData. And still it doesn't solve the problem with callbacks. I would go with useMemo and useCallback which is gonna be used a lot in the world without classes. At least in expensive places.

Mixing data may be confusing for users. There are props which are may be overridden by react-window. This always had a bad experience for me.

I personally use react-window in small filter lists and they are fast without any memoization. For big table memoize-one is enough until we have hooks.

And yeah, types will work but T need to be constrained with Object.

@bvaughn
Copy link
Owner

bvaughn commented Nov 7, 2018

I'm not sure. This forces us to always pass an object to itemData.

To be clear, I was only asking for confirmation that the Flow type was correct 😉

I linked to a Gist above that mentions the pros and cons of actually making the change. My personal feelings are that the cons outweigh the pros, but I wanted to give it fair consideration.

And yeah, types will work but T need to be contained with Object.

I don't know what this means.

@TrySound
Copy link
Contributor

TrySound commented Nov 7, 2018

@bvaughn Oops, I meant "constrained" like <T: Object>

@bvaughn
Copy link
Owner

bvaughn commented Nov 7, 2018

Oh! Yes that makes total sense. Thank you.

@jlongster
Copy link
Author

@TrySound Even with hooks, this is now what it will look like to pass callbacks down to the item:

const data = useMemo(
  () => ({
    onHover,
    onEdit,
    onSave,
    onDelete,
    onMove,
    onSplit
  }),
  [onHover, onEdit, onSave, onDelete, onMove, onSplit]
);

<List itemData={data} ... />

I still don't feel like it's a very ergonomic API, compared to:

function renderItem({ index, style}) {
  return <Item onHover={onHover} onEdit={onEdit} .../>
}

<List renderItem={renderItem} />

@bvaughn
Copy link
Owner

bvaughn commented Nov 7, 2018

Agreed that's not very ergonomic, but it's only necessary if you're passing multiple props down (which I still suspect is uncommon, but I don't have any actual usage data) and that layer of memoization actually has a meaningful, positive impact on performance. Given those caveats, I'm not sure the gains outweigh the downsides I mentioned on the Gist.

@jlongster
Copy link
Author

Might not be surprising, but I disagree 😀. I think it’s more rare that your rendering static data that doesn’t need to be interacted with. In almost all cases where I’ve rendered a list, I’ve needed to pass callbacks down (on devtools, client projects, and actual). I don’t have data generally from others though - might be useful to ask around?

I also think it’s critical that the whole list doesn’t rerender when changing a row’s state. It sounds like we’re talking about two different use cases: you’re talking about rendering static data that doesn’t change, but I’m talking about interactive stuff. The perf of the latter is just as critical as scrolling peformance whenever I’ve use large lists.

@jlongster
Copy link
Author

I’m not arguing for spreading the props specifically, but anything that makes it more ergonomic to use with dynamic data.

@bvaughn
Copy link
Owner

bvaughn commented Nov 7, 2018

might be useful to ask around?

That's why I tweeted/reddited this thread and gist 😉

The perf of the latter is just as critical as scrolling peformance whenever I’ve use large lists.

I strongly disagree. Both might be critical, but scrolling is necessarily more critical because it's the same thing at a much higher rate.

There are "escape hatches" if it's really important to you though, although they're not very ergonomic. You can always provide your own shouldComponentUpdate or put an intermediate component that just unwraps props.data and passes them along if you want to avoid the case of a list re-rendering when you trigger an action.

😆 We should discuss this over a cup of coffee sometime.

@jlongster
Copy link
Author

jlongster commented Nov 7, 2018

Yeah, too bad I'm on the east coast!

That's why I tweeted/reddited this thread and gist

It didn't ask specifically though about that use case - it's a somewhat nuanced issue so I doubt people read all the way through and would respond whether or not they need to pass additional props down.

I strongly disagree. Both might be critical, but scrolling is necessarily more critical because it's the same thing at a much higher rate.

From a UX perspective, if either the scrolling is janky or you have an editable list and it's janky when moving around, both are just as serious UX issues from my perspective. Yes, in some ways scrolling is more sensitive, but both are just as important UX. I would be very careful about considering this as over-optimization, as I strongly disagree with that characterization.

@bvaughn
Copy link
Owner

bvaughn commented Nov 7, 2018

I guess I should rephrase. Both are important UX interactions, but what I meant was that scrolling is far more performance sensitive and so more important to optimize. (I believe users are far less likely to notice if you exceed your frame budget slightly on an update action vs a scrolling interaction.)

It didn't ask specifically though about that use case - it's a somewhat nuanced issue so I doubt people read all the way through and would respond whether or not they need to pass additional props down.

You want to champion this? You're passionate about it. I've got my hands kind of full at the moment 😄

Edit because my last sentence may have sounded passive-aggressive. Not my intention! Hope you didn't read it that way.

@jlongster
Copy link
Author

In many of my uses of virtualized large lists, I immediately notice when I accidentally break shouldComponentUpdate and all the items start rerendering. Interactions are clearly janky - it's more than just dropping a frame here or there, but there's lag in any of the interactions. It's super important for interactive data.

Yeah sure! I'll tweet about it. It's not a huge deal because I'm pretty sure I can wrap this library and expose the API I want, if nothing changes here. But I'm curious myself if my use cases are more special than I think.

@bvaughn
Copy link
Owner

bvaughn commented Nov 7, 2018

In the past 24 hours, I've chatted with Spencer about React Native's FlatList, Necolas about Twitter's virtualization, and you here– and I'm getting my threads mixed up a bit at this point, but...

If you come up with a concrete suggested change to react-window (or even if you have one now) pitch it here as a comment, let me sit and think on it a bit, and see if we can't come up with a compromise that would address your concerns.

I'm not actually using my windowing components very much these days, so it's easy for me to get hung up on theoretical usage that's out of touch. I appreciate the feedback from people who have used them a lot.

I think your original suggestion has been to provide a plain function render option rather than a component. If that's still your primary suggestion, then what would your proposed API be like given that I definitely want to continue treating item renderers as React elements by default.

@jlongster
Copy link
Author

Thanks for engaging so much, the theoretical usage is just as important because sometimes current practical usage can blind you from a better API. It's been good to hash out the core motivation for our ideas.

Sounds good - I will definitely work on wrapping this with a layer that will make it easier for me to switch my app, and that will help me figure out if there are any concrete changes I can suggest. Thanks!

@AlexGalays
Copy link

It seems this ship has sailed (I saw what's in store for version 2) but just in case, I wanted to echo jlongster's sentiment.

I know React really well and found react-window children API to be really off-putting. In idiomatic react, closures capture any state you may need down the tree. Here, we have to manually pass some memoized itemData to a parent only so it can then pass it down again to its children, which is clearly worse ergonomics and not what the rest of a typical codebase does.

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants