-
Notifications
You must be signed in to change notification settings - Fork 25.1k
[VirtualizedSectionList] - Externalise and handle any sort of data blob #20787
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
90baadb
cc92082
66c365a
c00db10
0762ef6
b1fe9b9
880c4dd
0168f7a
a25a136
dfe4994
5437f8b
edebcef
4ff6035
32a5e4c
1f00f8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,35 +20,11 @@ import type {ViewToken} from 'ViewabilityHelper'; | |
| import type {Props as VirtualizedListProps} from 'VirtualizedList'; | ||
|
|
||
| type Item = any; | ||
| type SectionItem = any; | ||
|
|
||
| type SectionBase = { | ||
| // Must be provided directly on each section. | ||
| data: $ReadOnlyArray<SectionItem>, | ||
| key?: string, | ||
|
|
||
| // Optional props will override list-wide props just for this section. | ||
| renderItem?: ?({ | ||
| item: SectionItem, | ||
| index: number, | ||
| section: SectionBase, | ||
| separators: { | ||
| highlight: () => void, | ||
| unhighlight: () => void, | ||
| updateProps: (select: 'leading' | 'trailing', newProps: Object) => void, | ||
| }, | ||
| }) => ?React.Element<any>, | ||
| ItemSeparatorComponent?: ?React.ComponentType<any>, | ||
| keyExtractor?: (item: SectionItem, index: ?number) => string, | ||
|
|
||
| // TODO: support more optional/override props | ||
| // FooterComponent?: ?ReactClass<any>, | ||
| // HeaderComponent?: ?ReactClass<any>, | ||
| // onViewableItemsChanged?: ({viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void, | ||
| }; | ||
| type SectionBase = Object; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a regression. Could you work on putting back the proper Flow Type and running flow on the react-native repo to verify it?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| type RequiredProps<SectionT: SectionBase> = { | ||
| sections: $ReadOnlyArray<SectionT>, | ||
| sections: $ReadOnlyArray<SectionT> | $ReadOnly<SectionT>, | ||
| }; | ||
|
|
||
| type OptionalProps<SectionT: SectionBase> = { | ||
|
|
@@ -116,7 +92,8 @@ type OptionalProps<SectionT: SectionBase> = { | |
| */ | ||
| refreshing?: ?boolean, | ||
| }; | ||
|
|
||
| /* $FlowFixMe - this Props seems to be missing a bunch of stuff. Remove this | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we fix this instead?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not really an expert with complex flow typing, I can try to remove this, but I'm not sure if I'm going to fix something of fix it in a good way :/ |
||
| * comment to see the errors */ | ||
| export type Props<SectionT> = RequiredProps<SectionT> & | ||
| OptionalProps<SectionT> & | ||
| VirtualizedListProps; | ||
|
|
@@ -148,7 +125,10 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent< | |
| }) { | ||
| let index = Platform.OS === 'ios' ? params.itemIndex : params.itemIndex + 1; | ||
| for (let ii = 0; ii < params.sectionIndex; ii++) { | ||
| index += this.props.sections[ii].data.length + 2; | ||
| const section = this.props.getItem(this.props.sections, ii); | ||
| const sectionData = this.props.getItem(section, 'data'); | ||
| const dataLength = this.props.getItemCount(sectionData); | ||
| index += dataLength + 2; | ||
| } | ||
| const toIndexParams = { | ||
| ...params, | ||
|
|
@@ -173,10 +153,14 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent< | |
| _computeState(props: Props<SectionT>): State { | ||
| const offset = props.ListHeaderComponent ? 1 : 0; | ||
| const stickyHeaderIndices = []; | ||
| const itemCount = props.sections.reduce((v, section) => { | ||
| stickyHeaderIndices.push(v + offset); | ||
| return v + section.data.length + 2; // Add two for the section header and footer. | ||
| }, 0); | ||
| const itemCount = props.sections | ||
| ? props.sections.reduce((v, section) => { | ||
| const sectionData = props.getItem(section, 'data'); | ||
|
|
||
| stickyHeaderIndices.push(v + offset); | ||
| return v + props.getItemCount(sectionData) + 2; // Add two for the section header and footer. | ||
| }, 0) | ||
| : 0; | ||
|
|
||
| return { | ||
| childProps: { | ||
|
|
@@ -185,7 +169,7 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent< | |
| ItemSeparatorComponent: undefined, // Rendered with renderItem | ||
| data: props.sections, | ||
| getItemCount: () => itemCount, | ||
| getItem, | ||
| getItem: (sections, index) => getItem(props, sections, index), | ||
| keyExtractor: this._keyExtractor, | ||
| onViewableItemsChanged: props.onViewableItemsChanged | ||
| ? this._onViewableItemsChanged | ||
|
|
@@ -221,42 +205,44 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent< | |
| trailingSection?: ?SectionT, | ||
| } { | ||
| let itemIndex = index; | ||
| const {sections} = this.props; | ||
| for (let ii = 0; ii < sections.length; ii++) { | ||
| const section = sections[ii]; | ||
| const key = section.key || String(ii); | ||
| const defaultKeyExtractor = this.props.keyExtractor; | ||
| for (let ii = 0; ii < this.props.getItemCount(this.props.sections); ii++) { | ||
| const section = this.props.getItem(this.props.sections, ii); | ||
| const sectionData = this.props.getItem(section, 'data'); | ||
| const key = this.props.getItem(section, 'key') || String(ii); | ||
| itemIndex -= 1; // The section adds an item for the header | ||
| if (itemIndex >= section.data.length + 1) { | ||
| itemIndex -= section.data.length + 1; // The section adds an item for the footer. | ||
| if (itemIndex >= this.props.getItemCount(sectionData) + 1) { | ||
| itemIndex -= this.props.getItemCount(sectionData) + 1; // The section adds an item for the footer. | ||
| } else if (itemIndex === -1) { | ||
| return { | ||
| section, | ||
| key: key + ':header', | ||
| index: null, | ||
| header: true, | ||
| trailingSection: sections[ii + 1], | ||
| trailingSection: this.props.getItem(this.props.sections, ii + 1), | ||
| }; | ||
| } else if (itemIndex === section.data.length) { | ||
| } else if (itemIndex === this.props.getItemCount(sectionData)) { | ||
| return { | ||
| section, | ||
| key: key + ':footer', | ||
| index: null, | ||
| header: false, | ||
| trailingSection: sections[ii + 1], | ||
| trailingSection: this.props.getItem(this.props.sections, ii + 1), | ||
|
magrinj marked this conversation as resolved.
|
||
| }; | ||
| } else { | ||
|
magrinj marked this conversation as resolved.
|
||
| const keyExtractor = section.keyExtractor || this.props.keyExtractor; | ||
| const keyExtractor = | ||
| this.props.getItem(section, 'keyExtractor') || defaultKeyExtractor; | ||
| return { | ||
| section, | ||
| key: key + ':' + keyExtractor(section.data[itemIndex], itemIndex), | ||
| key: | ||
| key + | ||
| ':' + | ||
| keyExtractor(this.props.getItem(sectionData, itemIndex), itemIndex), | ||
| index: itemIndex, | ||
| leadingItem: section.data[itemIndex - 1], | ||
| leadingSection: sections[ii - 1], | ||
| trailingItem: | ||
| section.data.length > itemIndex + 1 | ||
| ? section.data[itemIndex + 1] | ||
| : undefined, | ||
| trailingSection: sections[ii + 1], | ||
| leadingItem: this.props.getItem(sectionData, itemIndex - 1), | ||
| leadingSection: this.props.getItem(this.props.sections, ii - 1), | ||
| trailingItem: this.props.getItem(sectionData, itemIndex + 1), | ||
| trailingSection: this.props.getItem(this.props.sections, ii + 1), | ||
| }; | ||
| } | ||
| } | ||
|
|
@@ -268,7 +254,9 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent< | |
| if (!info) { | ||
| return null; | ||
| } | ||
| const keyExtractor = info.section.keyExtractor || this.props.keyExtractor; | ||
| const keyExtractor = | ||
| this.props.getItem(info.section, 'keyExtractor') || | ||
| this.props.keyExtractor; | ||
| return { | ||
| ...viewable, | ||
| index: info.index, | ||
|
magrinj marked this conversation as resolved.
|
||
|
|
@@ -313,7 +301,8 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent< | |
| return renderSectionFooter ? renderSectionFooter({section}) : null; | ||
| } | ||
| } else { | ||
| const renderItem = info.section.renderItem || this.props.renderItem; | ||
| const renderItem = | ||
| this.props.getItem(info.section, 'renderItem') || this.props.renderItem; | ||
| const SeparatorComponent = this._getSeparatorComponent(index, info); | ||
| invariant(renderItem, 'no renderItem!'); | ||
| return ( | ||
|
|
@@ -355,10 +344,13 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent< | |
| return null; | ||
| } | ||
| const ItemSeparatorComponent = | ||
| info.section.ItemSeparatorComponent || this.props.ItemSeparatorComponent; | ||
| this.props.getItem(info.section, 'ItemSeparatorComponent') || | ||
| this.props.ItemSeparatorComponent; | ||
| const {SectionSeparatorComponent} = this.props; | ||
| const isLastItemInList = index === this.state.childProps.getItemCount() - 1; | ||
| const isLastItemInSection = info.index === info.section.data.length - 1; | ||
| const sectionData = this.props.getItem(info.section, 'data'); | ||
| const isLastItemInSection = | ||
| info.index === this.props.getItemCount(sectionData) - 1; | ||
| if (SectionSeparatorComponent && isLastItemInSection) { | ||
| return SectionSeparatorComponent; | ||
| } | ||
|
|
@@ -445,7 +437,7 @@ class ItemWithSeparator extends React.Component< | |
| }, | ||
| updateProps: (select: 'leading' | 'trailing', newProps: Object) => { | ||
| const {LeadingSeparatorComponent, cellKey, prevCellKey} = this.props; | ||
| if (select === 'leading' && LeadingSeparatorComponent != null) { | ||
| if (select === 'leading' && LeadingSeparatorComponent !== null) { | ||
| this.setState(state => ({ | ||
| leadingSeparatorProps: {...state.leadingSeparatorProps, ...newProps}, | ||
| })); | ||
|
|
@@ -523,22 +515,28 @@ class ItemWithSeparator extends React.Component< | |
| } | ||
| } | ||
|
|
||
| function getItem(sections: ?$ReadOnlyArray<Item>, index: number): ?Item { | ||
| function getItem( | ||
| props: Props<SectionBase>, | ||
| sections: ?$ReadOnlyArray<Item>, | ||
| index: number, | ||
| ): ?Item { | ||
| if (!sections) { | ||
| return null; | ||
| } | ||
| let itemIdx = index - 1; | ||
| for (let ii = 0; ii < sections.length; ii++) { | ||
| if (itemIdx === -1 || itemIdx === sections[ii].data.length) { | ||
| for (let ii = 0; ii < props.getItemCount(sections); ii++) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Working on this at FB. I think this is supposed to stay
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cpojer Thanks ! In fact it can be something else, by exemple if you use an immutable list, to get his length the method is |
||
| const section = props.getItem(props.sections, ii); | ||
| const sectionData = props.getItem(section, 'data'); | ||
| if (itemIdx === -1 || itemIdx === props.getItemCount(sectionData)) { | ||
| // We intend for there to be overflow by one on both ends of the list. | ||
| // This will be for headers and footers. When returning a header or footer | ||
| // item the section itself is the item. | ||
| return sections[ii]; | ||
| } else if (itemIdx < sections[ii].data.length) { | ||
| return section; | ||
| } else if (itemIdx < props.getItemCount(sectionData)) { | ||
| // If we are in the bounds of the list's data then return the item. | ||
| return sections[ii].data[itemIdx]; | ||
| return props.getItem(sectionData, itemIdx); | ||
| } else { | ||
| itemIdx -= sections[ii].data.length + 2; // Add two for the header and footer | ||
| itemIdx -= props.getItemCount(sectionData) + 2; // Add two for the header and footer | ||
| } | ||
| } | ||
| return null; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.