Last active
February 10, 2019 19:18
-
-
Save mrnkr/d7e43159babe3c98124df487f674eb44 to your computer and use it in GitHub Desktop.
React List component which implements infinite scroll automatically with intersection observer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In the last update I added support for sections - said sections can have headers which receive a template function