Skip to content

Instantly share code, notes, and snippets.

@vincentriemer
Created December 3, 2018 15:09
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vincentriemer/f278149c2e4b79248e0f555ab4e0b21e to your computer and use it in GitHub Desktop.
Save vincentriemer/f278149c2e4b79248e0f555ab4e0b21e to your computer and use it in GitHub Desktop.
React wrapper around the <virtual-scroller /> web component
// Global import to register <virtual-scroller /> custom element
// This should be replaced with a layered import once it is introduced into browsers
import "virtual-scroller/src/virtual-scroller";
import React, {
useRef,
useEffect,
forwardRef,
useImperativeMethods
} from "react";
import ReactDOM from "react-dom";
export enum Layout {
Vertical = "vertical",
Horizontal = "horizontal",
VerticalGrid = "vertical-grid",
HorizontalGrid = "horizontal-grid"
}
type Props = {
itemSource: any[];
renderRow: (item: any, itemIndex: number) => React.ReactElement<any>;
layout?: Layout;
tag?: keyof HTMLElementTagNameMap;
className?: string;
style?: React.CSSProperties;
onRangeChange?: (e: any) => void;
onScroll?: (e: any) => void;
};
function useDirectEventListener<T extends EventTarget, R extends EventListener>(
eventName: string,
elemRef: React.MutableRefObject<T>,
handler?: R
) {
useEffect(
() => {
const elem = elemRef.current;
if (elem && handler) {
elem.addEventListener(eventName, handler);
return () => {
elem.removeEventListener(eventName, handler);
};
}
},
[handler]
);
}
export const VirtualScroller = forwardRef(
(
{
layout = Layout.Vertical,
tag = "div",
onRangeChange,
onScroll,
itemSource,
renderRow,
className,
style
}: Props,
ref
) => {
const scrollerRef = useRef<any>(null);
useEffect(
() => {
const scroller = scrollerRef.current;
if (scroller) {
scroller.createElement = (item: any, itemIndex: number) => {
const child = document.createElement(tag);
ReactDOM.render(renderRow(item, itemIndex), child);
return child;
};
scroller.updateElement = (
child: Element,
item: any,
itemIndex: number
) => {
ReactDOM.render(renderRow(item, itemIndex), child);
};
}
},
[renderRow, tag]
);
useEffect(
() => {
const scroller = scrollerRef.current;
if (scroller) {
scroller.itemSource = itemSource;
}
},
[itemSource]
);
useDirectEventListener("rangechange", scrollerRef, onRangeChange);
useDirectEventListener("scroll", scrollerRef, onScroll);
useImperativeMethods(ref, () => ({
scrollToIndex: (index: number, options?: { position: string }) => {
const scroller = scrollerRef.current;
if (scroller) {
scroller.scrollToIndex(index, options);
}
}
}));
return (
<virtual-scroller
ref={scrollerRef}
className={className}
style={style}
layout={layout}
/>
);
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment