I am using react-custom-scrollbar
and would like to integrate it with FixedSizeList
.
I have checked the solution on this issue on react-virtualized
: https://github.com/bvaughn/react-virtualized/issues/692#issuecomment-339393521
But the code is throwing error: Uncaught TypeError: Cannot read property 'handleScrollEvent' of undefined
on scroll, in this function:
handleScroll = ({ target }) => {
const { scrollTop, scrollLeft } = target;
const { Grid: grid } = this.List;
grid.handleScrollEvent({ scrollTop, scrollLeft });
}
I have added ref={ instance => { this.List = instance; } }
on fixedSixe<List
component.
This library and react-virtualized
are completely different implementations. General techniques you see on react-virtualized
issues may often provide useful hints as to how to approach something, but specific implementation details like this are not applicable. (In react-virtualized
, List
decorates a Grid
component. In react-window
they are separate components, for better performance and size.)
I don't know if something like react-custom-scrollbar
would work with react-window
since I've never tried it. It's not something I'm very interested in supporting to be honest, since I think custom scrollbars are generally a bad idea because of how they impact performance. So I'm going to close this issue.
But we can continue to chat on it if you have follow up questions 😄
I'll figure out something about this, I am interested in using react-window
because it's lightweight and solves my issue. Even I don't like to use custom scrollbar, but it's not in my hand, we need custom design for scrollbar.
I'll use react-virtualized
, in worst-case.
Thanks :)
@Rahul-Sagore did you end up getting custom scrollbars working with react-window
?
No, did not get time for that. Need to check and figure out.
Not sure if @bvaughn approves this, but it seems to work: https://codesandbox.io/s/00nw2w1jv
Better approach would be to pass Scrollbars
as outerElementType
const CustomScrollbars = ({ onScroll, forwardedRef, style, children }) => {
const refSetter = useCallback(scrollbarsRef => {
if (scrollbarsRef) {
forwardedRef(scrollbarsRef.view);
} else {
forwardedRef(null);
}
}, []);
return (
<Scrollbars
ref={refSetter}
style={{ ...style, overflow: "hidden" }}
onScroll={onScroll}
>
{children}
</Scrollbars>
);
};
const CustomScrollbarsVirtualList = React.forwardRef((props, ref) => (
<CustomScrollbars {...props} forwardedRef={ref} />
));
// ...
<FixedSizeList
outerElementType={CustomScrollbarsVirtualList}
{...rest}
/>
I love how many advanced things are possible using outerElementType
or innerElementType
I am trying to detect the scroll position at the bottom of react-window.
Is it possible? Do you have any ideas for that? I'd like to have codesandbox example.
You should be able to use the available ref props to ask the list for its scrollHeight
@rufoot.
I have tried to use scrollHeight
in the props. But I think there isn't scrollHeight
property in that.
@bvaughn Do you have any idea about that?
scrollToRow200Auto = () => {
this.listRef.current.scrollToItem(200);
console.log("current position = ", this.listRef.current.props.scrollHeight)
};
current position = undefined
@rufoot something like this https://codesandbox.io/s/4zjwwq98j4 ?
https://codesandbox.io/embed/jzo2lool2y
When you scroll down at the bottom of react-window, then you can see one alert.
@piecyk I have tried your solution with outerElementType
and it is super laggy, have you battletested it?:D
I have tried it with react-scrollbars-custom
. Its is less laggy but still laggy. Have someone had any issues with lags using custom scrollbars?
@jancama2 didn't battletested it 😂Can you share Code Sandbox when it gets laggy?
@rufoot something like this https://codesandbox.io/s/4zjwwq98j4 ?
I am using your implementation but I get an error saying forwardedRef is not a function.
The problem in my app that I am trying to solve is that I have a large list of items and when I click on one, the app will redirect to another route so I need to keep track of the scroll position (probably in redux) and then set the scroll position after the route change.
@piecyk have you tried combining it with the InfiniteLoader from react-window-infinite-loader
.
I'm using react-scrollbars-custom
and it seems to be breaking the infinite loader :(
My code:
<InfiniteLoader isItemLoaded={isItemLoaded} itemCount={items.total} loadMoreItems={loadMoreItems}>
{({
onItemsRendered,
ref,
}: {
onItemsRendered: (props: ListOnItemsRenderedProps) => any;
ref: React.Ref<any>;
}) => (
<List
height={TABLE_HEIGHT}
width={width}
itemCount={items.total}
itemSize={ROW_HEIGHT}
onItemsRendered={onItemsRendered}
itemData={{ items }}
ref={ref}
outerElementType={Scrollbar}
>
{Row}
</List>
)}
</InfiniteLoader>
Update: I don't think that it's related to the infinite scroll component. I can't get it to work with the custom scrollbar.
I'd appreciate if someone can share the implementation for react-scrollbars-custom with me
@ranihorev do you need to use that package? The code pen @piecyk shared above uses react-custom-scrollbars and works well (except when creating a ref, which for some reason is null in my code)
Ok I need to ask again. @piecyk I am using your code sample from above (where you replied to @simjes ). instead of using a fixed sized list though, I am using the auto sizer to wrap a VariableSizeList and specify the outerElementType. Something like this:
<TreeRoot
onDragOver={e => this.onDragOver(e)}
>
<AutoSizer>
{({ height, width }) => (
<List
treeRef={this.props.treeRef}
width={width}
height={height}
itemSize={index => rowHeights[index]}
itemCount={nodeTree.length}
itemData={{
nodeTree,
expandedNodes,
nodesFetching,
nodesFetchingFailed,
selectedNode,
selectedNodeId,
resetRowHeight: this.resetRowHeight,
}}
outerElementType={TreeScrollbar}
outerRef={this.props.outerRef}
onScroll={({ scrollOffset, scrollUpdateWasRequested }) => {
if (scrollUpdateWasRequested) {
console.log('scrollOffset: ', scrollOffset);
}
}}
>
{Node}
</List>
)}
</AutoSizer>
</TreeRoot>
I implement the CustomScrollbars and CustomScrollbarsVirtualList the same as you have however the ref is always null. I have tried creating the ref in the component I am using the AutoSizer as well as in it's parent but it's always null. I would really appreciate some help understanding why. As I mentioned above, I need to keep track of the scroll position and be able to set it on a route change in my app so the list doesn't jump back to the top.
@ChristopherHButler I'm already using that package multiple times in my code, so I'd rather keep using the same package. I solution is pretty similar however it doesn't fetch new data ptoperly
@ChristopherHButler really hard to say without any Code Sandbox example of the issue, can you share something?
Basic the idea here is to set the ref from react-custom-scrollbars so the react-window
AutoSizer or VariableSizeList will work with this approach, as in this example
https://codesandbox.io/s/react-window-custom-scrollbars-t4352
@ranihorev can you also share Code Sandbox example?
@piecyk I create a simple (and broken) example:
https://codesandbox.io/s/bvaughnreact-window-fixed-size-list-vertical-64kzh?fontsize=14
I also tried other variations (e.g. setting the ref to be the scroller wrapper) but no success...
Thanks!
@ranihorev this approach would be the same regardless of custom scroll implementation. They need to set reference to the same element that is responsible for scroll and handle that scroll event.
react-scrollbars-custom
provides nicer api using render props pattern so you can do something like https://codesandbox.io/s/bvaughnreact-window-react-scrollbars-custom-pjyxs
Didn't do any profiling, hope this helps 👍
@piecyk I am working with your demo here: https://codesandbox.io/s/4zjwwq98j4. I want to use the onScrollStop to dispatch an action in redux so I can set the scroll position (an idea from this thread: https://github.com/malte-wessel/react-custom-scrollbars/issues/146 but onScrollStart and onScrollStop do not seem to work in my code or in your demo. Do you know why this is? Is there any way to pass props from the List to the Scrollbars?
@ChristopherHButler I think your best option is to use context to pass the props, something like
https://codesandbox.io/s/bvaughnreact-window-fixed-size-list-vertical-v2-usi1m
--- Edited
If you unmount the whole list when route changes you can dispatch the action then, Last scrollOffset can be stored on ref and updated on every scroll change using the onScroll: function
from VariableSizeList then you don't need to pass props to Scrollbars
@piecyk that's awesome, thanks!
btw, it seems that all you need is the onScroll
function (that I missed) and there is no need to forward a ref at all.
https://codesandbox.io/s/bvaughnreact-window-react-scrollbars-custom-99dn1
@piecyk sorry I should have mentioned my implementation of the VariableSizeList is wrapped in an AutoSizer and the component which renders it is a class based component so I'm not sure I can use context. Maybe this is why I cannot get onScrollStop to work?
Quick update:
I added component state and set it onScroll.
`
class BaseTree extends Component {
...
state = { scrollPosition: 0, };
...
listRef = React.createRef();
outerRef = React.createRef();
...
render {
const { nodes, nodesFetching, nodesFetchingFailed, expandedNodes, selectedNode } = this.props;
return (
{({ height, width }) => (
ref={this.listRef}
className="List"
outerRef={this.outerRef}
outerElementType={TreeScrollbar}
width={width}
height={height}
onScroll={({ scrollOffset }) => this.setState({ scrollPosition: scrollOffset })}
itemSize={index => rowHeights[index]}
itemCount={nodeTree.length}
itemData={{
nodeTree,
expandedNodes,
nodesFetching,
nodesFetchingFailed,
selectedNode,
selectedNodeId,
resetRowHeight: this.resetRowHeight,
}}
>
{Node}
)}
);
}}
const mapStateToProps = state => ({
scrollPosition: selectors.getTreeScrollPosition(state),
});
const mapDispatchToProps = dispatch => ({
setTreeScrollPosition: position => dispatch(actions.setTreeScrollPosition({ position })),
});
export default connect(mapStateToProps, mapDispatchToProps)(BaseTree);
`
then on componentWillUnmout I dispatch the action to set the position in redux like this:
componentWillUnmount() {
this.props.setTreeScrollPosition(this.state.scrollPosition);
}
my only issue now is being able to set the scroll position when the component re-mounts. I tried accessing the scrollTop method on this.listRef like this:
this.listRef.current.scrollTop()
but I get an error that it is not a function. I'm not sure which property (this.listRef or this.outerRef) I can use to set the scroll position or in which method. I was thinking I could set it in the BaseTree's componentDidMount as follows:
componentDidMount() {
const { scrollPosition } = this.props;
if (this.listRef.current) {
// console.log('initializing scroll position to : ', scrollPosition);
this.listRef.current.scrollTop(scrollPosition);
}
}
Any help to get this working would be greatly appreciated!
edit: I'm very sorry but my code does not seem to be formatting correctly in this editor so I apologize for that :(
@ranihorev forward ref
to react-window is need for functions like scrollTo, scrollToItem
to work
@ChristopherHButler context should work as exactly for this they are build, from docs
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
But yeah as mention before if you unmount the whole component on route change there is no need to pass the props. Would not store scrollPosition
on state as you don't need it while scrolling is happening, less re-renders, store it on ref.
onScroll={({ scrollOffset }) => this.setState({ scrollPosition: scrollOffset })}
// to
lastScrollOffsetRef = React.createRef(0);
<List
onScroll={({ scrollOffset }) => {
this.lastScrollOffsetRef.current = scrollOffset
}}
// ...rest
/>
// then in
componentWillUnmount() {
this.props.setTreeScrollPosition(this.lastScrollOffsetRef.current);
}
Regarding outerRef
, it's not need in your case. listRef.current.scrollTo
is the correct method you want to use. Hmm your idea to call the method in componentDidMount
is correct and should work,
https://codesandbox.io/s/bvaughnreact-window-fixed-size-list-vertical-80zo7
looks like some timing problem while setting refs, maybe AutoSizer delays render of the List that makes the this.listRef.current to be undefined,
hacky option is to, break from react life cycle with setTimeout, and call the ref on next tick
componentDidMount() {
window.setTimeout(() => {
if (this.listRef.current) {
this.listRef.current.scrollTo(this.props.scrollPosition);
}
}, 0);
}
@piecyk thanks a lot for the help, I really appreciate it :)
Thanks @piecyk That seems to work. There is a slight flicker on mounting after the route change but I can live with it :) I also tried another hack which was to use the className to pass the scrollPosition and set scrollTop in the refSetter of CustomScrollbars:
if (scrollbarsRef) {
forwardedRef(scrollbarsRef.view);
// HACK
scrollbarsRef.scrollTop(className);
}
It seems to work well.
Thanks again for all your help, it's greatly appreciate!! 🙌🏻
Unfortunately, all the examples in this issue do not work correctly (sometimes the mouse stops responding to scroll).
But this example work perfect!
@ranihorev this approach would be the same regardless of custom scroll implementation. They need to set reference to the same element that is responsible for scroll and handle that scroll event.
react-scrollbars-custom
provides nicer api using render props pattern so you can do something like https://codesandbox.io/s/bvaughnreact-window-react-scrollbars-custom-pjyxs
I have tried it with
react-scrollbars-custom
. Its is less laggy but still laggy. Have someone had any issues with lags using custom scrollbars?
Did you get it done? What's the final solution?
If this helps anyone, I've managed to make it work with OverlayScrollbars. You just need to pass the scroll event to the corresponding element. This is how:
const Overflow = ({ children, onScroll }) => {
const ofRef = useRef(null);
useEffect(() => {
const el = ofRef.current.osInstance().getElements().viewport;
if (onScroll) el.addEventListener('scroll', onScroll);
return () => {
if (onScroll) el.removeEventListener('scroll', onScroll);
};
}, [onScroll]);
return (
<OverlayScrollbarsComponent
options={options}
ref={ofRef}
>
{children}
</OverlayScrollbarsComponent>
);
};
And then, in your virtualized component:
<FixedSizeGrid
{...props}
outerElementType={Overflow}
>
I'm using this with FixedSizeGrid
but it should work the same for lists.
Hope it helps.
Most helpful comment
Better approach would be to pass
Scrollbars
asouterElementType
example https://codesandbox.io/s/vmr1l0p463