Skip to content

Commit 63d3ea1

Browse files
sahrensfacebook-github-bot
authored andcommitted
Improve flow typing
Summary: - Properly inherit flow types from base components, including `defaultProps` - template-ify the `Item` type as it flows from the `data` prop into `ItemComponent` Note that for `SectionList` is is harder to do the `Item` typing because each section in the `sections` array can have a different `Item` type, plus all the optional overrides...not sure how to tackle that. Reviewed By: yungsters Differential Revision: D4557523 fbshipit-source-id: a0c5279fcdaabe6aab5fe11743a99c3715a44032
1 parent c529a06 commit 63d3ea1

9 files changed

Lines changed: 318 additions & 77 deletions

File tree

.flowconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ suppress_type=$FixMe
4444
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-9]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)
4545
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-9]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+
4646
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
47+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
4748

4849
unsafe.enable_getters_and_setters=true
4950

Examples/UIExplorer/js/FlatListExample.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class FlatListExample extends React.PureComponent {
108108
key={(this.state.horizontal ? 'h' : 'v') + (this.state.fixedHeight ? 'f' : 'd')}
109109
legacyImplementation={false}
110110
numColumns={1}
111-
onRefresh={() => alert('onRefresh: nothing to refresh :P')}
111+
onRefresh={this._onRefresh}
112112
onViewableItemsChanged={this._onViewableItemsChanged}
113113
ref={this._captureRef}
114114
refreshing={false}
@@ -121,6 +121,7 @@ class FlatListExample extends React.PureComponent {
121121
_getItemLayout = (data: any, index: number) => {
122122
return getItemLayout(data, index, this.state.horizontal);
123123
};
124+
_onRefresh = () => alert('onRefresh: nothing to refresh :P');
124125
_renderItemComponent = ({item}) => {
125126
return (
126127
<ItemComponent
@@ -154,7 +155,7 @@ class FlatListExample extends React.PureComponent {
154155
_pressItem = (key: number) => {
155156
pressItem(this, key);
156157
};
157-
_listRef: FlatList;
158+
_listRef: FlatList<*>;
158159
}
159160

160161

Libraries/Experimental/FlatList.js

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,23 @@ const invariant = require('invariant');
4141

4242
import type {StyleObj} from 'StyleSheetTypes';
4343
import type {Viewable} from 'ViewabilityHelper';
44+
import type {Props as VirtualizedListProps} from 'VirtualizedList';
4445

4546
type Item = any;
4647

47-
type RequiredProps = {
48+
type RequiredProps<ItemT> = {
4849
/**
4950
* Note this can be a normal class component, or a functional component, such as a render method
5051
* on your main component.
5152
*/
52-
ItemComponent: ReactClass<{item: Item, index: number}>,
53+
ItemComponent: ReactClass<{item: ItemT, index: number}>,
5354
/**
5455
* For simplicity, data is just a plain array. If you want to use something else, like an
5556
* immutable list, use the underlying `VirtualizedList` directly.
5657
*/
57-
data: ?Array<Item>,
58+
data: ?Array<ItemT>,
5859
};
59-
type OptionalProps = {
60+
type OptionalProps<ItemT> = {
6061
/**
6162
* Rendered at the bottom of all the items.
6263
*/
@@ -79,7 +80,7 @@ type OptionalProps = {
7980
* Remember to include separator length (height or width) in your offset calculation if you
8081
* specify `SeparatorComponent`.
8182
*/
82-
getItemLayout?: (data: ?Array<Item>, index: number) =>
83+
getItemLayout?: (data: ?Array<ItemT>, index: number) =>
8384
{length: number, offset: number, index: number},
8485
/**
8586
* If true, renders items next to each other horizontally instead of stacked vertically.
@@ -90,7 +91,7 @@ type OptionalProps = {
9091
* and as the react key to track item re-ordering. The default extractor checks item.key, then
9192
* falls back to using the index, like react does.
9293
*/
93-
keyExtractor: (item: Item, index: number) => string,
94+
keyExtractor: (item: ItemT, index: number) => string,
9495
/**
9596
* Multiple columns can only be rendered with horizontal={false} and will zig-zag like a flexWrap
9697
* layout. Items should all be the same height - masonry layouts are not supported.
@@ -111,6 +112,7 @@ type OptionalProps = {
111112
* `viewablePercentThreshold` prop.
112113
*/
113114
onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
115+
legacyImplementation?: ?boolean,
114116
/**
115117
* Set this true while waiting for new data from a refresh.
116118
*/
@@ -123,11 +125,19 @@ type OptionalProps = {
123125
* Optional optimization to minimize re-rendering items.
124126
*/
125127
shouldItemUpdate: (
126-
prevProps: {item: Item, index: number},
127-
nextProps: {item: Item, index: number}
128+
prevProps: {item: ItemT, index: number},
129+
nextProps: {item: ItemT, index: number}
128130
) => boolean,
129131
};
130-
type Props = RequiredProps & OptionalProps; // plus props from the underlying implementation
132+
type Props<ItemT> = RequiredProps<ItemT> & OptionalProps<ItemT> & VirtualizedListProps;
133+
134+
const defaultProps = {
135+
...VirtualizedList.defaultProps,
136+
getItem: undefined,
137+
getItemCount: undefined,
138+
numColumns: 1,
139+
};
140+
type DefaultProps = typeof defaultProps;
131141

132142
/**
133143
* A performant interface for rendering simple, flat lists, supporting the most handy features:
@@ -148,13 +158,9 @@ type Props = RequiredProps & OptionalProps; // plus props from the underlying im
148158
* ItemComponent={({item}) => <Text>{item.key}</Text>}
149159
* />
150160
*/
151-
class FlatList extends React.PureComponent {
152-
static defaultProps = {
153-
keyExtractor: VirtualizedList.defaultProps.keyExtractor,
154-
numColumns: 1,
155-
shouldItemUpdate: VirtualizedList.defaultProps.shouldItemUpdate,
156-
};
157-
props: Props;
161+
class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, void> {
162+
static defaultProps: DefaultProps = defaultProps;
163+
props: Props<ItemT>;
158164
/**
159165
* Scrolls to the end of the content. May be janky without getItemLayout prop.
160166
*/
@@ -191,7 +197,7 @@ class FlatList extends React.PureComponent {
191197
this._checkProps(this.props);
192198
}
193199

194-
componentWillReceiveProps(nextProps: Props) {
200+
componentWillReceiveProps(nextProps: Props<ItemT>) {
195201
this._checkProps(nextProps);
196202
}
197203

@@ -200,7 +206,7 @@ class FlatList extends React.PureComponent {
200206

201207
_captureRef = (ref) => { this._listRef = ref; };
202208

203-
_checkProps(props: Props) {
209+
_checkProps(props: Props<ItemT>) {
204210
const {
205211
getItem,
206212
getItemCount,
@@ -229,7 +235,7 @@ class FlatList extends React.PureComponent {
229235
}
230236
}
231237

232-
_getItem = (data: Array<Item>, index: number): Item | Array<Item> => {
238+
_getItem = (data: Array<ItemT>, index: number): ItemT | Array<ItemT> => {
233239
const {numColumns} = this.props;
234240
if (numColumns > 1) {
235241
const ret = [];
@@ -243,13 +249,19 @@ class FlatList extends React.PureComponent {
243249
}
244250
};
245251

246-
_getItemCount = (data: Array<Item>): number => {
252+
_getItemCount = (data: Array<ItemT>): number => {
247253
return Math.floor(data.length / this.props.numColumns);
248254
};
249255

250-
_keyExtractor = (items: Item | Array<Item>, index: number): string => {
256+
_keyExtractor = (items: ItemT | Array<ItemT>, index: number): string => {
251257
const {keyExtractor, numColumns} = this.props;
252258
if (numColumns > 1) {
259+
invariant(
260+
Array.isArray(items),
261+
'FlatList: Encountered internal consistency error, expected each item to consist of an ' +
262+
'array with 1-%s columns; instead, received a single item.',
263+
numColumns,
264+
);
253265
return items.map((it, kk) => keyExtractor(it, index * numColumns + kk)).join(':');
254266
} else {
255267
return keyExtractor(items, index);

Libraries/Experimental/MetroListView.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type NormalProps = {
4949

5050
// Provide either `items` or `sections`
5151
items?: ?Array<Item>, // By default, an Item is assumed to be {key: string}
52-
sections?: ?Array<{key: string, items: Array<Item>}>,
52+
sections?: ?Array<{key: string, data: Array<Item>}>,
5353

5454
/**
5555
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
@@ -146,7 +146,7 @@ class MetroListView extends React.Component {
146146
const sections = {};
147147
props.sections.forEach((sectionIn, ii) => {
148148
const sectionID = 's' + ii;
149-
sections[sectionID] = sectionIn.itemData;
149+
sections[sectionID] = sectionIn.data;
150150
sectionHeaderData[sectionID] = sectionIn;
151151
});
152152
return {

Libraries/Experimental/SectionList.js

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,43 +37,43 @@ const React = require('React');
3737
const VirtualizedSectionList = require('VirtualizedSectionList');
3838

3939
import type {Viewable} from 'ViewabilityHelper';
40+
import type {Props as VirtualizedSectionListProps} from 'VirtualizedSectionList';
4041

4142
type Item = any;
42-
type SectionItem = any;
4343

44-
type SectionBase = {
44+
type SectionBase<SectionItemT> = {
4545
// Must be provided directly on each section.
46-
data: Array<SectionItem>,
46+
data: Array<SectionItemT>,
4747
key: string,
4848

4949
// Optional props will override list-wide props just for this section.
50-
ItemComponent?: ?ReactClass<{item: SectionItem, index: number}>,
50+
ItemComponent?: ?ReactClass<{item: SectionItemT, index: number}>,
5151
SeparatorComponent?: ?ReactClass<*>,
52-
keyExtractor?: (item: SectionItem) => string,
52+
keyExtractor?: (item: SectionItemT) => string,
5353

5454
// TODO: support more optional/override props
5555
// FooterComponent?: ?ReactClass<*>,
5656
// HeaderComponent?: ?ReactClass<*>,
5757
// onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
5858

5959
// TODO: support recursive sections
60-
// SectionHeaderComponent?: ?ReactClass<{section: SectionBase}>,
60+
// SectionHeaderComponent?: ?ReactClass<{section: SectionBase<*>}>,
6161
// sections?: ?Array<Section>;
6262
};
6363

64-
type RequiredProps<SectionT: SectionBase> = {
64+
type RequiredProps<SectionT: SectionBase<*>> = {
6565
sections: Array<SectionT>,
6666
};
6767

68-
type OptionalProps<SectionT: SectionBase> = {
68+
type OptionalProps<SectionT: SectionBase<*>> = {
6969
/**
7070
* Rendered after the last item in the last section.
7171
*/
7272
FooterComponent?: ?ReactClass<*>,
7373
/**
7474
* Default renderer for every item in every section.
7575
*/
76-
ItemComponent?: ?ReactClass<{item: Item, index: number}>,
76+
ItemComponent: ReactClass<{item: Item, index: number}>,
7777
/**
7878
* Rendered at the top of each section. In the future, a sticky option will be added.
7979
*/
@@ -93,8 +93,8 @@ type OptionalProps<SectionT: SectionBase> = {
9393
* stored outside of the recursive `ItemComponent` instance tree.
9494
*/
9595
enableVirtualization?: ?boolean,
96-
keyExtractor?: (item: Item) => string,
97-
onEndReached?: ({distanceFromEnd: number}) => void,
96+
keyExtractor: (item: Item, index: number) => string,
97+
onEndReached?: ?({distanceFromEnd: number}) => void,
9898
/**
9999
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
100100
* sure to also set the `refreshing` prop correctly.
@@ -104,21 +104,25 @@ type OptionalProps<SectionT: SectionBase> = {
104104
* Called when the viewability of rows changes, as defined by the
105105
* `viewablePercentThreshold` prop.
106106
*/
107-
onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
107+
onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
108108
/**
109109
* Set this true while waiting for new data from a refresh.
110110
*/
111-
refreshing?: boolean,
111+
refreshing?: ?boolean,
112112
/**
113113
* This is an optional optimization to minimize re-rendering items.
114114
*/
115-
shouldItemUpdate?: (
115+
shouldItemUpdate: (
116116
prevProps: {item: Item, index: number},
117117
nextProps: {item: Item, index: number}
118118
) => boolean,
119119
};
120120

121-
type Props<SectionT> = RequiredProps<SectionT> & OptionalProps<SectionT>;
121+
type Props<SectionT> = RequiredProps<SectionT>
122+
& OptionalProps<SectionT>
123+
& VirtualizedSectionListProps<SectionT>;
124+
125+
type DefaultProps = typeof VirtualizedSectionList.defaultProps;
122126

123127
/**
124128
* A performant interface for rendering sectioned lists, supporting the most handy features:
@@ -132,8 +136,11 @@ type Props<SectionT> = RequiredProps<SectionT> & OptionalProps<SectionT>;
132136
*
133137
* If you don't need section support and want a simpler interface, use FlatList.
134138
*/
135-
class SectionList<SectionT: SectionBase> extends React.Component<void, Props<SectionT>, void> {
139+
class SectionList<SectionT: SectionBase<*>>
140+
extends React.PureComponent<DefaultProps, Props<SectionT>, *>
141+
{
136142
props: Props<SectionT>;
143+
static defaultProps: DefaultProps = VirtualizedSectionList.defaultProps;
137144

138145
render() {
139146
if (this.props.legacyImplementation) {

Libraries/Experimental/VirtualizedList.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ type RequiredProps = {
6868
* The default accessor functions assume this is an Array<{key: string}> but you can override
6969
* getItem, getItemCount, and keyExtractor to handle any type of index-based data.
7070
*/
71-
data: any,
71+
data?: any,
7272
};
7373
type OptionalProps = {
7474
FooterComponent?: ?ReactClass<*>,
@@ -89,12 +89,12 @@ type OptionalProps = {
8989
getItemCount: (items: any) => number,
9090
getItemLayout?: (items: any, index: number) =>
9191
{length: number, offset: number, index: number}, // e.g. height, y
92-
horizontal: boolean,
92+
horizontal?: ?boolean,
9393
initialNumToRender: number,
9494
keyExtractor: (item: Item, index: number) => string,
9595
maxToRenderPerBatch: number,
96-
onEndReached: ({distanceFromEnd: number}) => void,
97-
onEndReachedThreshold: number, // units of visible length
96+
onEndReached?: ?({distanceFromEnd: number}) => void,
97+
onEndReachedThreshold?: ?number, // units of visible length
9898
onLayout?: ?Function,
9999
/**
100100
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
@@ -105,11 +105,11 @@ type OptionalProps = {
105105
* Called when the viewability of rows changes, as defined by the
106106
* `viewablePercentThreshold` prop.
107107
*/
108-
onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
108+
onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
109109
/**
110110
* Set this true while waiting for new data from a refresh.
111111
*/
112-
refreshing?: boolean,
112+
refreshing?: ?boolean,
113113
removeClippedSubviews?: boolean,
114114
renderScrollComponent: (props: Object) => React.Element<*>,
115115
shouldItemUpdate: (
@@ -126,11 +126,11 @@ type OptionalProps = {
126126
viewablePercentThreshold: number,
127127
windowSize: number, // units of visible length
128128
};
129-
type Props = RequiredProps & OptionalProps;
129+
export type Props = RequiredProps & OptionalProps;
130130

131131
let _usedIndexForKey = false;
132132

133-
class VirtualizedList extends React.PureComponent {
133+
class VirtualizedList extends React.PureComponent<OptionalProps, Props, *> {
134134
props: Props;
135135

136136
// scrollToEnd may be janky without getItemLayout prop
@@ -182,7 +182,7 @@ class VirtualizedList extends React.PureComponent {
182182
);
183183
}
184184

185-
static defaultProps: OptionalProps = {
185+
static defaultProps = {
186186
disableVirtualization: false,
187187
getItem: (data: any, index: number) => data[index],
188188
getItemCount: (data: any) => data ? data.length : 0,
@@ -390,7 +390,12 @@ class VirtualizedList extends React.PureComponent {
390390

391391
_onCellLayout = (e, cellKey, index) => {
392392
const layout = e.nativeEvent.layout;
393-
const next = {offset: this._selectOffset(layout), length: this._selectLength(layout), index, inLayout: true};
393+
const next = {
394+
offset: this._selectOffset(layout),
395+
length: this._selectLength(layout),
396+
index,
397+
inLayout: true,
398+
};
394399
const curr = this._frames[cellKey];
395400
if (!curr ||
396401
next.offset !== curr.offset ||

0 commit comments

Comments
 (0)