Skip to content

Instantly share code, notes, and snippets.

@mrnkr
Last active February 10, 2019 19:18
Show Gist options
  • Save mrnkr/d7e43159babe3c98124df487f674eb44 to your computer and use it in GitHub Desktop.
Save mrnkr/d7e43159babe3c98124df487f674eb44 to your computer and use it in GitHub Desktop.
React List component which implements infinite scroll automatically with intersection observer
import React, { Component } from 'react';
import get from 'lodash/get';
import EmptyListPlaceholder from './list/EmptyListPlaceholder';
import Loading from './list/Loading';
import './list/List.scss';
interface Props<T> {
className?: string;
grid?: boolean;
sectioned?: boolean;
sectionFilter?: string;
currentPage?: number;
items: T[];
loading?: boolean;
onPageChange?: (page: number) => void;
smallItems?: boolean;
sectionHeader?: (section: string) => JSX.Element;
template: (item: T) => JSX.Element;
}
export default class List<T> extends Component<Props<T>> {
private intersection = new IntersectionObserver(entries => {
entries.forEach(entry => {
const { currentPage = 1, onPageChange = () => null } = this.props;
if (!entry.isIntersecting)
return;
onPageChange(currentPage + 1);
this.intersection.unobserve(entry.target);
})
});
public componentWillUnmount() {
this.intersection.disconnect();
}
public render() {
const { className, items, loading = false, sectioned = false, sectionFilter = '', grid = false, smallItems = false, template, sectionHeader = () => null } = this.props;
return items.length > 0 ? (
<div className={`column ${className}`}>
{
groupBy(items, sectionFilter).map(section =>
<div
key={get(section[0], sectionFilter, 'default')}
className={`${grid ? 'my-grid' : 'my-list'} ${grid && smallItems ? 'sm' : ''}`}
>
{
sectionHeader(get(section[0], sectionFilter, 'default'))
}
{
section.map((item, index) =>
React.cloneElement(template(item),
index === items.length - 2 ?
{ elementRef: this.observeIntersection } :
{}
)
)
}
</div>
)
}
{
loading ?
<Loading /> :
null
}
</div>
) : (
<EmptyListPlaceholder message="Nada que ver aquí" />
);
}
private observeIntersection = (el: HTMLDivElement) =>
el ? this.intersection.observe(el) : null;
}
function groupBy<T>(arr: T[], field: string = ''): T[][] {
const sections: { [key: string]: T[] } = {};
arr.forEach(item =>
sections[get(item, field, 'default')] = [...(sections[get(item, field, 'default')] || []), item]);
return Object
.keys(sections)
.map(key => sections[key]);
}
@mrnkr
Copy link
Author

mrnkr commented Feb 10, 2019

In the last update I added support for sections - said sections can have headers which receive a template function

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