Skip to content

Commit 1946aee

Browse files
magrinjfacebook-github-bot
authored andcommitted
- Externalise and handle any sort of data blob (#20787)
Summary: Fixes #20770 Pull Request resolved: #20787 Reviewed By: sahrens Differential Revision: D9485598 Pulled By: cpojer fbshipit-source-id: edddebf6b5e1ca396ab1a519baf019c1e5188d44
1 parent f81d77c commit 1946aee

File tree

5 files changed

+1508
-85
lines changed

5 files changed

+1508
-85
lines changed

Libraries/Lists/SectionList.js

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,14 @@ const ScrollView = require('ScrollView');
1515
const VirtualizedSectionList = require('VirtualizedSectionList');
1616

1717
import type {ViewToken} from 'ViewabilityHelper';
18-
import type {Props as VirtualizedSectionListProps} from 'VirtualizedSectionList';
18+
import type {
19+
SectionBase as _SectionBase,
20+
Props as VirtualizedSectionListProps,
21+
} from 'VirtualizedSectionList';
1922

2023
type Item = any;
2124

22-
export type SectionBase<SectionItemT> = {
23-
/**
24-
* The data for rendering items in this section.
25-
*/
26-
data: $ReadOnlyArray<SectionItemT>,
27-
/**
28-
* Optional key to keep track of section re-ordering. If you don't plan on re-ordering sections,
29-
* the array index will be used by default.
30-
*/
31-
key?: string,
32-
33-
// Optional props will override list-wide props just for this section.
34-
renderItem?: ?(info: {
35-
item: SectionItemT,
36-
index: number,
37-
section: SectionBase<SectionItemT>,
38-
separators: {
39-
highlight: () => void,
40-
unhighlight: () => void,
41-
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
42-
},
43-
}) => ?React.Element<any>,
44-
ItemSeparatorComponent?: ?React.ComponentType<any>,
45-
keyExtractor?: (item: SectionItemT) => string,
46-
47-
// TODO: support more optional/override props
48-
// onViewableItemsChanged?: ...
49-
};
25+
export type SectionBase<SectionItemT> = _SectionBase<SectionItemT>;
5026

5127
type RequiredProps<SectionT: SectionBase<any>> = {
5228
/**
@@ -326,10 +302,17 @@ class SectionList<SectionT: SectionBase<any>> extends React.PureComponent<
326302
}
327303

328304
render() {
329-
/* $FlowFixMe(>=0.66.0 site=react_native_fb) This comment suppresses an
330-
* error found when Flow v0.66 was deployed. To see the error delete this
331-
* comment and run Flow. */
332-
return <VirtualizedSectionList {...this.props} ref={this._captureRef} />;
305+
return (
306+
/* $FlowFixMe(>=0.66.0 site=react_native_fb) This comment suppresses an
307+
* error found when Flow v0.66 was deployed. To see the error delete this
308+
* comment and run Flow. */
309+
<VirtualizedSectionList
310+
{...this.props}
311+
ref={this._captureRef}
312+
getItemCount={items => items.length}
313+
getItem={(items, index) => items[index]}
314+
/>
315+
);
333316
}
334317

335318
_wrapperListRef: ?React.ElementRef<typeof VirtualizedSectionList>;

Libraries/Lists/VirtualizedSectionList.js

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,38 +20,38 @@ import type {ViewToken} from 'ViewabilityHelper';
2020
import type {Props as VirtualizedListProps} from 'VirtualizedList';
2121

2222
type Item = any;
23-
type SectionItem = any;
2423

25-
type SectionBase = {
26-
// Must be provided directly on each section.
27-
data: $ReadOnlyArray<SectionItem>,
24+
export type SectionBase<SectionItemT> = {
25+
/**
26+
* The data for rendering items in this section.
27+
*/
28+
data: $ReadOnlyArray<SectionItemT>,
29+
/**
30+
* Optional key to keep track of section re-ordering. If you don't plan on re-ordering sections,
31+
* the array index will be used by default.
32+
*/
2833
key?: string,
2934

3035
// Optional props will override list-wide props just for this section.
31-
renderItem?: ?({
32-
item: SectionItem,
36+
renderItem?: ?(info: {
37+
item: SectionItemT,
3338
index: number,
34-
section: SectionBase,
39+
section: SectionBase<SectionItemT>,
3540
separators: {
3641
highlight: () => void,
3742
unhighlight: () => void,
3843
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
3944
},
4045
}) => ?React.Element<any>,
4146
ItemSeparatorComponent?: ?React.ComponentType<any>,
42-
keyExtractor?: (item: SectionItem, index: ?number) => string,
43-
44-
// TODO: support more optional/override props
45-
// FooterComponent?: ?ReactClass<any>,
46-
// HeaderComponent?: ?ReactClass<any>,
47-
// onViewableItemsChanged?: ({viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void,
47+
keyExtractor?: (item: SectionItemT, index?: ?number) => string,
4848
};
4949

50-
type RequiredProps<SectionT: SectionBase> = {
50+
type RequiredProps<SectionT: SectionBase<any>> = {
5151
sections: $ReadOnlyArray<SectionT>,
5252
};
5353

54-
type OptionalProps<SectionT: SectionBase> = {
54+
type OptionalProps<SectionT: SectionBase<any>> = {
5555
/**
5656
* Rendered after the last item in the last section.
5757
*/
@@ -131,10 +131,9 @@ type State = {childProps: VirtualizedListProps};
131131
* hood. The only operation that might not scale well is concatting the data arrays of all the
132132
* sections when new props are received, which should be plenty fast for up to ~10,000 items.
133133
*/
134-
class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
135-
Props<SectionT>,
136-
State,
137-
> {
134+
class VirtualizedSectionList<
135+
SectionT: SectionBase<any>,
136+
> extends React.PureComponent<Props<SectionT>, State> {
138137
static defaultProps: DefaultProps = {
139138
...VirtualizedList.defaultProps,
140139
data: [],
@@ -147,8 +146,8 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
147146
viewPosition?: number,
148147
}) {
149148
let index = Platform.OS === 'ios' ? params.itemIndex : params.itemIndex + 1;
150-
for (let ii = 0; ii < params.sectionIndex; ii++) {
151-
index += this.props.sections[ii].data.length + 2;
149+
for (let i = 0; i < params.sectionIndex; i++) {
150+
index += this.props.getItemCount(this.props.sections[i].data) + 2;
152151
}
153152
const toIndexParams = {
154153
...params,
@@ -173,10 +172,12 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
173172
_computeState(props: Props<SectionT>): State {
174173
const offset = props.ListHeaderComponent ? 1 : 0;
175174
const stickyHeaderIndices = [];
176-
const itemCount = props.sections.reduce((v, section) => {
177-
stickyHeaderIndices.push(v + offset);
178-
return v + section.data.length + 2; // Add two for the section header and footer.
179-
}, 0);
175+
const itemCount = props.sections
176+
? props.sections.reduce((v, section) => {
177+
stickyHeaderIndices.push(v + offset);
178+
return v + props.getItemCount(section.data) + 2; // Add two for the section header and footer.
179+
}, 0)
180+
: 0;
180181

181182
return {
182183
childProps: {
@@ -185,7 +186,8 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
185186
ItemSeparatorComponent: undefined, // Rendered with renderItem
186187
data: props.sections,
187188
getItemCount: () => itemCount,
188-
getItem,
189+
// $FlowFixMe
190+
getItem: (sections, index) => getItem(props, sections, index),
189191
keyExtractor: this._keyExtractor,
190192
onViewableItemsChanged: props.onViewableItemsChanged
191193
? this._onViewableItemsChanged
@@ -221,42 +223,41 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
221223
trailingSection?: ?SectionT,
222224
} {
223225
let itemIndex = index;
224-
const {sections} = this.props;
225-
for (let ii = 0; ii < sections.length; ii++) {
226-
const section = sections[ii];
227-
const key = section.key || String(ii);
226+
const {getItem, getItemCount, keyExtractor, sections} = this.props;
227+
for (let i = 0; i < sections.length; i++) {
228+
const section = sections[i];
229+
const sectionData = section.data;
230+
const key = section.key || String(i);
228231
itemIndex -= 1; // The section adds an item for the header
229-
if (itemIndex >= section.data.length + 1) {
230-
itemIndex -= section.data.length + 1; // The section adds an item for the footer.
232+
if (itemIndex >= getItemCount(sectionData) + 1) {
233+
itemIndex -= getItemCount(sectionData) + 1; // The section adds an item for the footer.
231234
} else if (itemIndex === -1) {
232235
return {
233236
section,
234237
key: key + ':header',
235238
index: null,
236239
header: true,
237-
trailingSection: sections[ii + 1],
240+
trailingSection: sections[i + 1],
238241
};
239-
} else if (itemIndex === section.data.length) {
242+
} else if (itemIndex === getItemCount(sectionData)) {
240243
return {
241244
section,
242245
key: key + ':footer',
243246
index: null,
244247
header: false,
245-
trailingSection: sections[ii + 1],
248+
trailingSection: sections[i + 1],
246249
};
247250
} else {
248-
const keyExtractor = section.keyExtractor || this.props.keyExtractor;
251+
const extractor = section.keyExtractor || keyExtractor;
249252
return {
250253
section,
251-
key: key + ':' + keyExtractor(section.data[itemIndex], itemIndex),
254+
key:
255+
key + ':' + extractor(getItem(sectionData, itemIndex), itemIndex),
252256
index: itemIndex,
253-
leadingItem: section.data[itemIndex - 1],
254-
leadingSection: sections[ii - 1],
255-
trailingItem:
256-
section.data.length > itemIndex + 1
257-
? section.data[itemIndex + 1]
258-
: undefined,
259-
trailingSection: sections[ii + 1],
257+
leadingItem: getItem(sectionData, itemIndex - 1),
258+
leadingSection: sections[i - 1],
259+
trailingItem: getItem(sectionData, itemIndex + 1),
260+
trailingSection: sections[i + 1],
260261
};
261262
}
262263
}
@@ -358,7 +359,8 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
358359
info.section.ItemSeparatorComponent || this.props.ItemSeparatorComponent;
359360
const {SectionSeparatorComponent} = this.props;
360361
const isLastItemInList = index === this.state.childProps.getItemCount() - 1;
361-
const isLastItemInSection = info.index === info.section.data.length - 1;
362+
const isLastItemInSection =
363+
info.index === this.props.getItemCount(info.section.data) - 1;
362364
if (SectionSeparatorComponent && isLastItemInSection) {
363365
return SectionSeparatorComponent;
364366
}
@@ -523,22 +525,29 @@ class ItemWithSeparator extends React.Component<
523525
}
524526
}
525527

526-
function getItem(sections: ?$ReadOnlyArray<Item>, index: number): ?Item {
528+
function getItem(
529+
props: Props<SectionBase<any>>,
530+
sections: ?$ReadOnlyArray<Item>,
531+
index: number,
532+
): ?Item {
527533
if (!sections) {
528534
return null;
529535
}
530536
let itemIdx = index - 1;
531-
for (let ii = 0; ii < sections.length; ii++) {
532-
if (itemIdx === -1 || itemIdx === sections[ii].data.length) {
537+
for (let i = 0; i < sections.length; i++) {
538+
const section = sections[i];
539+
const sectionData = section.data;
540+
const itemCount = props.getItemCount(sectionData);
541+
if (itemIdx === -1 || itemIdx === itemCount) {
533542
// We intend for there to be overflow by one on both ends of the list.
534543
// This will be for headers and footers. When returning a header or footer
535544
// item the section itself is the item.
536-
return sections[ii];
537-
} else if (itemIdx < sections[ii].data.length) {
545+
return section;
546+
} else if (itemIdx < itemCount) {
538547
// If we are in the bounds of the list's data then return the item.
539-
return sections[ii].data[itemIdx];
548+
return props.getItem(sectionData, itemIdx);
540549
} else {
541-
itemIdx -= sections[ii].data.length + 2; // Add two for the header and footer
550+
itemIdx -= itemCount + 2; // Add two for the header and footer
542551
}
543552
}
544553
return null;

0 commit comments

Comments
 (0)