Skip to content

Instantly share code, notes, and snippets.

@williamtran29
Last active September 15, 2020 21:13
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save williamtran29/a4559a878f0b55b10a8f8a71b330ecf8 to your computer and use it in GitHub Desktop.
Save williamtran29/a4559a878f0b55b10a8f8a71b330ecf8 to your computer and use it in GitHub Desktop.
[React Native ListView] Scroll to a section by a sectionId, Scroll to a row by a rowId, Receive callbacks when a section changes, Pinned Section and more...
import React, {
Component,
PropTypes
} from 'react'
import {
ListView
} from 'react-native'
// arbitrarily large distance to pre-render all sections for measurements
const RENDER_AHEAD_DISTANCE = 1000000
const scrollOffset = y => ({
y,
x: 0,
animated: true
})
class SectionListView extends Component {
static DataSource = ListView.DataSource
static propTypes = {
...ListView.propTypes,
onSectionChanged: PropTypes.func
}
static defaultProps = {
...ListView.defaultProps,
// arbitrarily large distance to pre-render all sections for measurements
scrollRenderAheadDistance: RENDER_AHEAD_DISTANCE,
scrollEventThrottle: 1
}
constructor(props) {
super(props)
// this data should never trigger a render pass, so it stays out of state.
this.sections = []
this.rowOffsets = {}
this.currentSection = null
}
onSectionHeaderLayout = (sectionId, event) => {
const offset = event.nativeEvent.layout.y
let sections = this.sections.filter(section => section.section !== sectionId)
sections.push({
offset,
section: sectionId
})
sections = sections.sort((a, b) => {
if (a.offset < b.offset) {
return -1
} else if (a.offset > b.offset) {
return 1
}
return 0
})
this.sections = sections
}
onRowLayout = (sectionId, rowId, event) => {
if (!this.rowOffsets[sectionId]) {
this.rowOffsets[sectionId] = {}
}
this.rowOffsets[sectionId][rowId] = event.nativeEvent.layout.y
}
onScroll = (event) => {
if (this.props.onScroll) {
this.props.onScroll(event)
}
if (!this.props.onSectionChanged) {
return
}
const offset = event.nativeEvent.contentOffset.y
let section
this.sections.forEach((currentSection) => {
if (currentSection.offset <= offset) {
section = currentSection.section
}
})
if (section && this.currentSection !== section) {
this.currentSection = section
if (this.props.onSectionChanged) {
this.props.onSectionChanged(section)
}
}
}
scrollToSection = (sectionId) => {
if (this.listView) {
const section = this.sections.find(item => item.section === sectionId)
if (section) {
this.listView.getScrollResponder().scrollTo(scrollOffset(section.offset))
}
}
}
scrollToRow = (sectionId, rowId) => {
if (this.listView && this.rowOffsets[sectionId] && this.rowOffsets[sectionId][rowId]) {
this.listView.getScrollResponder().scrollTo(scrollOffset(this.rowOffsets[sectionId][rowId]))
}
}
renderSectionHeader = (sectionData, sectionId) => {
return React.cloneElement(this.props.renderSectionHeader(sectionData, sectionId), {
onLayout: event => this.onSectionHeaderLayout(sectionId, event)
})
}
renderRow = (data, sectionId, rowId) => {
return React.cloneElement(this.props.renderRow(data, sectionId, rowId), {
onLayout: event => this.onRowLayout(sectionId, rowId, event)
})
}
render() {
return (
<ListView
{...this.props}
ref={component => this.listView = component}
onScroll={this.onScroll}
renderRow={this.renderRow}
renderSectionHeader={this.renderSectionHeader}
/>
)
}
}
export default SectionListView
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment