feat: Add CollectionNode to collection renderer#8523
feat: Add CollectionNode to collection renderer#8523nwidynski wants to merge 8 commits intoadobe:mainfrom
CollectionNode to collection renderer#8523Conversation
|
@devongovett Would love to hear your thoughts on this. For more context, this PR relates to #8317 (comment). Let me know if the docs need updates as well here! |
|
So you are looking for some kind of "inheritance" for collection renderers basically? Like use virtualizer but add an extra wrapper element around items? Can you describe in a bit more detail how your use case (a carousel I guess?) works? One concern is that it could be somewhat easy to mess up the DOM structure expected by an existing renderer, but maybe it's an advanced use case anyway so you know the risks. I was going to say the default collection renderer was pretty simple so it wouldn't be too hard to copy, but the drop indicator stuff is kinda complicated now so maybe this makes sense. Do you usually want to wrap just the item itself or also wrap the drop indicators? Sorry for all the questions just trying to think through the use cases here. |
|
@devongovett Yes, thats right! The primary idea for this PR was to be able to create a component similar to PS: We have already moved on from this implementation to one based on a |
|
Got it, thanks. I think something like that makes sense but I haven't fully thought it through yet. We're in release mode at the moment so responses might be slow. Will discuss it with the team soon. |
devongovett
left a comment
There was a problem hiding this comment.
We discussed this as a team and we think this is a reasonable API addition. Just one question on prop naming in case you have some ideas.
| /** The content that should be rendered before the item. */ | ||
| before?: ReactNode, | ||
| /** The content that should be rendered after the item. */ | ||
| after?: ReactNode |
There was a problem hiding this comment.
Do you think we should name these props so they are more specific to drop indicators? Not sure if we might want to add other things between items in the future, whether we'd want to group them here or allow them to be passed in separately for more control.
There was a problem hiding this comment.
I was questioning the same while working on this. Personally, I do feel like a user of this feature is looking for as much control here as possible. A compromise between the two could maybe be to change the signature to ReactNode[] or ReactElement[] to make destructuring much easier.
There was a problem hiding this comment.
@devongovett Btw, since I saw your reply on X, we might want to offer a wrapper prop here as well to wrap the node. Maybe thats easier than cloning the node just to modify its render function.
There was a problem hiding this comment.
Apologies for the very late reply. Personally, I think keeping the naming as is but expanding the signature to what you've proposed above is what I'd lean towards. The wrapper prop (and the itemRef addition you've mentioned in other comments) seems fine as well but in the hopes of getting this in sooner rather than later we can make those follow ups if you are fine with that.
There was a problem hiding this comment.
Sounds good, I will do the follow ups in another PR tmrw 👍 Feel free to push the changes for the signatures if you intend to merge it today, since im out of office already.
|
@devongovett Have you had the chance to make a decision on naming? Just bumping to get this PR wrapped up. PS: I would also love to support an |
|
Can we tie this up? Please also let me know about the
let Component = forwardRef(({node}: {node: Node<any>}, ref: ForwardedRef<E>) => render(node.props, mergeRefs(node.props.ref, ref), node)); |
| items: parent ? collection.getChildren!(parent.key) : collection, | ||
| dependencies: [renderDropIndicator], | ||
| dependencies: [CollectionNode, parent, renderDropIndicator], |
There was a problem hiding this comment.
Adding parent here doesn't do a whole lot, since collection.getChildren returns a new iterator on every render anyways. Still added it just in case that ever becomes identity stable.
| shouldInvalidate?: (context: any) => boolean, | ||
| /** A function that renders this node to a React Element in the DOM. */ | ||
| render?: (node: Node<any>) => ReactElement | ||
| render?: (node: Node<any>, ref?: ForwardedRef<Element>) => ReactElement |
There was a problem hiding this comment.
Is this necessary? doesn't node have the ref on it?
There was a problem hiding this comment.
I wanted to avoid mutating the node props outside of the builder if that makes sense. Using mergeProps in CollectionBuilder means any ref a parent renderer wants to attach to this node is resolved just-in-time before hitting the render function.
Also, while I appreciate you taking the time, you might want to defer review to next week here, haha. I'm about to replace this PR with a new one that opens up Virtualizer properly to be able to inherit not only the "CollectionNode", but also "CollectionRoot", since that's required to make the carousel work.
I was just kept busy with the RFC today 😅
devongovett
left a comment
There was a problem hiding this comment.
I'm wondering if this is the right API. It sounds like what you want is collection renderer composition, and I agree in principle that would be nice, but that is kind of left up to the implementor at the moment. Virtualizer does not currently "extend" the default renderer (it re-implements rendering drop indicators for example), and there's no way to control the order things are wrapped. Ideally they would compose in the order you nest them in the JSX tree.
<DefaultCollectionRenderer>
<Virtualizer>
<MySpecialWrapper>
<ListBox>In this case, the items should be wrapped so that MySpecialWrapper goes inside the absolutely positioned Virtualizer wrapper. And ideally Virtualizer wouldn't need to re-implement rendering the drop indicators (though they also get wrapped with absolutely positioned elements), it should inherit that behavior from the DefaultCollectionRenderer, and these should be outside the Virtualizer item wrapper. I haven't thought of exactly what the API should be yet, but feels like it should be doable with function composition.
Also not quite sure if it's worth the complexity if we don't have a strong need for it though. An alternative could be to simply export more things so it's easier to create custom renderers (e.g. export functions for rendering drop indicators).
| shouldInvalidate?: (context: any) => boolean, | ||
| /** A function that renders this node to a React Element in the DOM. */ | ||
| render?: (node: Node<any>) => ReactElement | ||
| render?: (node: Node<any>, ref?: ForwardedRef<Element>) => ReactElement |
There was a problem hiding this comment.
Do we need this? Can you get the ref via node.props.ref?
| return item?.render?.(item); | ||
| }, | ||
| // If we don't skip rendering the node here, we end up rendering them twice. | ||
| renderView: () => null, |
There was a problem hiding this comment.
this will negate the caching done by Virtualizer.
There was a problem hiding this comment.
Do you have alternate suggestions on how to approach that? Getting the rendered output within the inherited DefaultCollectionNode is pretty difficult.
| pseudoProps = {before: new Array(beforeIndicator), after: new Array(afterIndicator)}; | ||
| } | ||
|
|
||
| let render = (item: Node<unknown>, itemRef?: ForwardedRef<Element>) => ( |
There was a problem hiding this comment.
This will end up wrapping the original element and not the wrapper from the renderer's CollectionNode implementation. Won't that cause issues with positioning? The virtualizer wrapper adds position: absolute but any before or after content you add inside your custom CollectionNode won't be added inside that and won't get positioned correctly. The problem is you don't control the order the wrapping occurs. I think the order things get wrapped should match the order you nest collection renderers in the JSX tree.
There was a problem hiding this comment.
Yes, agreed also. Its done that way in the coming revision 😅 Sorry about the wasted time here.
|
I fully agree, hence why I asked to defer review to next week 😅 . I have pretty much finished up a rework of this PR to do exactly what you said here with function composition. I'm just putting the finishing touches on, since our RFC conversation took up the majority of today. |
|
May we close this one if another is superseding it? We're auditing to minimise merge conflicts with a really large PR |
|
Yes, we can close this one temporarily. |
This adds an optional
CollectionNodecomponent to the collection renderer interface, which enables customization of a nodes render output without having to re-implement the entire renderer. This can be useful to build wrapper components which modify a nodes pseudo content without breaking an existing custom renderer, e.g.<Virtualizer />.✅ Pull Request Checklist:
📝 Test Instructions:
🧢 Your Project: