Skip to content

Commit

Permalink
Improve flow typing
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sahrens authored and facebook-github-bot committed Feb 17, 2017
1 parent c529a06 commit 63d3ea1
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 77 deletions.
1 change: 1 addition & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-9]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)
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]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError

unsafe.enable_getters_and_setters=true

Expand Down
5 changes: 3 additions & 2 deletions Examples/UIExplorer/js/FlatListExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class FlatListExample extends React.PureComponent {
key={(this.state.horizontal ? 'h' : 'v') + (this.state.fixedHeight ? 'f' : 'd')}
legacyImplementation={false}
numColumns={1}
onRefresh={() => alert('onRefresh: nothing to refresh :P')}
onRefresh={this._onRefresh}
onViewableItemsChanged={this._onViewableItemsChanged}
ref={this._captureRef}
refreshing={false}
Expand All @@ -121,6 +121,7 @@ class FlatListExample extends React.PureComponent {
_getItemLayout = (data: any, index: number) => {
return getItemLayout(data, index, this.state.horizontal);
};
_onRefresh = () => alert('onRefresh: nothing to refresh :P');
_renderItemComponent = ({item}) => {
return (
<ItemComponent
Expand Down Expand Up @@ -154,7 +155,7 @@ class FlatListExample extends React.PureComponent {
_pressItem = (key: number) => {
pressItem(this, key);
};
_listRef: FlatList;
_listRef: FlatList<*>;
}


Expand Down
54 changes: 33 additions & 21 deletions Libraries/Experimental/FlatList.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,23 @@ const invariant = require('invariant');

import type {StyleObj} from 'StyleSheetTypes';
import type {Viewable} from 'ViewabilityHelper';
import type {Props as VirtualizedListProps} from 'VirtualizedList';

type Item = any;

type RequiredProps = {
type RequiredProps<ItemT> = {
/**
* Note this can be a normal class component, or a functional component, such as a render method
* on your main component.
*/
ItemComponent: ReactClass<{item: Item, index: number}>,
ItemComponent: ReactClass<{item: ItemT, index: number}>,
/**
* For simplicity, data is just a plain array. If you want to use something else, like an
* immutable list, use the underlying `VirtualizedList` directly.
*/
data: ?Array<Item>,
data: ?Array<ItemT>,
};
type OptionalProps = {
type OptionalProps<ItemT> = {
/**
* Rendered at the bottom of all the items.
*/
Expand All @@ -79,7 +80,7 @@ type OptionalProps = {
* Remember to include separator length (height or width) in your offset calculation if you
* specify `SeparatorComponent`.
*/
getItemLayout?: (data: ?Array<Item>, index: number) =>
getItemLayout?: (data: ?Array<ItemT>, index: number) =>
{length: number, offset: number, index: number},
/**
* If true, renders items next to each other horizontally instead of stacked vertically.
Expand All @@ -90,7 +91,7 @@ type OptionalProps = {
* and as the react key to track item re-ordering. The default extractor checks item.key, then
* falls back to using the index, like react does.
*/
keyExtractor: (item: Item, index: number) => string,
keyExtractor: (item: ItemT, index: number) => string,
/**
* Multiple columns can only be rendered with horizontal={false} and will zig-zag like a flexWrap
* layout. Items should all be the same height - masonry layouts are not supported.
Expand All @@ -111,6 +112,7 @@ type OptionalProps = {
* `viewablePercentThreshold` prop.
*/
onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
legacyImplementation?: ?boolean,
/**
* Set this true while waiting for new data from a refresh.
*/
Expand All @@ -123,11 +125,19 @@ type OptionalProps = {
* Optional optimization to minimize re-rendering items.
*/
shouldItemUpdate: (
prevProps: {item: Item, index: number},
nextProps: {item: Item, index: number}
prevProps: {item: ItemT, index: number},
nextProps: {item: ItemT, index: number}
) => boolean,
};
type Props = RequiredProps & OptionalProps; // plus props from the underlying implementation
type Props<ItemT> = RequiredProps<ItemT> & OptionalProps<ItemT> & VirtualizedListProps;

const defaultProps = {
...VirtualizedList.defaultProps,
getItem: undefined,
getItemCount: undefined,
numColumns: 1,
};
type DefaultProps = typeof defaultProps;

/**
* A performant interface for rendering simple, flat lists, supporting the most handy features:
Expand All @@ -148,13 +158,9 @@ type Props = RequiredProps & OptionalProps; // plus props from the underlying im
* ItemComponent={({item}) => <Text>{item.key}</Text>}
* />
*/
class FlatList extends React.PureComponent {
static defaultProps = {
keyExtractor: VirtualizedList.defaultProps.keyExtractor,
numColumns: 1,
shouldItemUpdate: VirtualizedList.defaultProps.shouldItemUpdate,
};
props: Props;
class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, void> {
static defaultProps: DefaultProps = defaultProps;
props: Props<ItemT>;
/**
* Scrolls to the end of the content. May be janky without getItemLayout prop.
*/
Expand Down Expand Up @@ -191,7 +197,7 @@ class FlatList extends React.PureComponent {
this._checkProps(this.props);
}

componentWillReceiveProps(nextProps: Props) {
componentWillReceiveProps(nextProps: Props<ItemT>) {
this._checkProps(nextProps);
}

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

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

_checkProps(props: Props) {
_checkProps(props: Props<ItemT>) {
const {
getItem,
getItemCount,
Expand Down Expand Up @@ -229,7 +235,7 @@ class FlatList extends React.PureComponent {
}
}

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

_getItemCount = (data: Array<Item>): number => {
_getItemCount = (data: Array<ItemT>): number => {
return Math.floor(data.length / this.props.numColumns);
};

_keyExtractor = (items: Item | Array<Item>, index: number): string => {
_keyExtractor = (items: ItemT | Array<ItemT>, index: number): string => {
const {keyExtractor, numColumns} = this.props;
if (numColumns > 1) {
invariant(
Array.isArray(items),
'FlatList: Encountered internal consistency error, expected each item to consist of an ' +
'array with 1-%s columns; instead, received a single item.',
numColumns,
);
return items.map((it, kk) => keyExtractor(it, index * numColumns + kk)).join(':');
} else {
return keyExtractor(items, index);
Expand Down
4 changes: 2 additions & 2 deletions Libraries/Experimental/MetroListView.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type NormalProps = {

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

/**
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
Expand Down Expand Up @@ -146,7 +146,7 @@ class MetroListView extends React.Component {
const sections = {};
props.sections.forEach((sectionIn, ii) => {
const sectionID = 's' + ii;
sections[sectionID] = sectionIn.itemData;
sections[sectionID] = sectionIn.data;
sectionHeaderData[sectionID] = sectionIn;
});
return {
Expand Down
39 changes: 23 additions & 16 deletions Libraries/Experimental/SectionList.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,43 +37,43 @@ const React = require('React');
const VirtualizedSectionList = require('VirtualizedSectionList');

import type {Viewable} from 'ViewabilityHelper';
import type {Props as VirtualizedSectionListProps} from 'VirtualizedSectionList';

type Item = any;
type SectionItem = any;

type SectionBase = {
type SectionBase<SectionItemT> = {
// Must be provided directly on each section.
data: Array<SectionItem>,
data: Array<SectionItemT>,
key: string,

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

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

// TODO: support recursive sections
// SectionHeaderComponent?: ?ReactClass<{section: SectionBase}>,
// SectionHeaderComponent?: ?ReactClass<{section: SectionBase<*>}>,
// sections?: ?Array<Section>;
};

type RequiredProps<SectionT: SectionBase> = {
type RequiredProps<SectionT: SectionBase<*>> = {
sections: Array<SectionT>,
};

type OptionalProps<SectionT: SectionBase> = {
type OptionalProps<SectionT: SectionBase<*>> = {
/**
* Rendered after the last item in the last section.
*/
FooterComponent?: ?ReactClass<*>,
/**
* Default renderer for every item in every section.
*/
ItemComponent?: ?ReactClass<{item: Item, index: number}>,
ItemComponent: ReactClass<{item: Item, index: number}>,
/**
* Rendered at the top of each section. In the future, a sticky option will be added.
*/
Expand All @@ -93,8 +93,8 @@ type OptionalProps<SectionT: SectionBase> = {
* stored outside of the recursive `ItemComponent` instance tree.
*/
enableVirtualization?: ?boolean,
keyExtractor?: (item: Item) => string,
onEndReached?: ({distanceFromEnd: number}) => void,
keyExtractor: (item: Item, index: number) => string,
onEndReached?: ?({distanceFromEnd: number}) => void,
/**
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
* sure to also set the `refreshing` prop correctly.
Expand All @@ -104,21 +104,25 @@ type OptionalProps<SectionT: SectionBase> = {
* Called when the viewability of rows changes, as defined by the
* `viewablePercentThreshold` prop.
*/
onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
/**
* Set this true while waiting for new data from a refresh.
*/
refreshing?: boolean,
refreshing?: ?boolean,
/**
* This is an optional optimization to minimize re-rendering items.
*/
shouldItemUpdate?: (
shouldItemUpdate: (
prevProps: {item: Item, index: number},
nextProps: {item: Item, index: number}
) => boolean,
};

type Props<SectionT> = RequiredProps<SectionT> & OptionalProps<SectionT>;
type Props<SectionT> = RequiredProps<SectionT>
& OptionalProps<SectionT>
& VirtualizedSectionListProps<SectionT>;

type DefaultProps = typeof VirtualizedSectionList.defaultProps;

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

render() {
if (this.props.legacyImplementation) {
Expand Down
25 changes: 15 additions & 10 deletions Libraries/Experimental/VirtualizedList.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type RequiredProps = {
* The default accessor functions assume this is an Array<{key: string}> but you can override
* getItem, getItemCount, and keyExtractor to handle any type of index-based data.
*/
data: any,
data?: any,
};
type OptionalProps = {
FooterComponent?: ?ReactClass<*>,
Expand All @@ -89,12 +89,12 @@ type OptionalProps = {
getItemCount: (items: any) => number,
getItemLayout?: (items: any, index: number) =>
{length: number, offset: number, index: number}, // e.g. height, y
horizontal: boolean,
horizontal?: ?boolean,
initialNumToRender: number,
keyExtractor: (item: Item, index: number) => string,
maxToRenderPerBatch: number,
onEndReached: ({distanceFromEnd: number}) => void,
onEndReachedThreshold: number, // units of visible length
onEndReached?: ?({distanceFromEnd: number}) => void,
onEndReachedThreshold?: ?number, // units of visible length
onLayout?: ?Function,
/**
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
Expand All @@ -105,11 +105,11 @@ type OptionalProps = {
* Called when the viewability of rows changes, as defined by the
* `viewablePercentThreshold` prop.
*/
onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
/**
* Set this true while waiting for new data from a refresh.
*/
refreshing?: boolean,
refreshing?: ?boolean,
removeClippedSubviews?: boolean,
renderScrollComponent: (props: Object) => React.Element<*>,
shouldItemUpdate: (
Expand All @@ -126,11 +126,11 @@ type OptionalProps = {
viewablePercentThreshold: number,
windowSize: number, // units of visible length
};
type Props = RequiredProps & OptionalProps;
export type Props = RequiredProps & OptionalProps;

let _usedIndexForKey = false;

class VirtualizedList extends React.PureComponent {
class VirtualizedList extends React.PureComponent<OptionalProps, Props, *> {
props: Props;

// scrollToEnd may be janky without getItemLayout prop
Expand Down Expand Up @@ -182,7 +182,7 @@ class VirtualizedList extends React.PureComponent {
);
}

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

_onCellLayout = (e, cellKey, index) => {
const layout = e.nativeEvent.layout;
const next = {offset: this._selectOffset(layout), length: this._selectLength(layout), index, inLayout: true};
const next = {
offset: this._selectOffset(layout),
length: this._selectLength(layout),
index,
inLayout: true,
};
const curr = this._frames[cellKey];
if (!curr ||
next.offset !== curr.offset ||
Expand Down
Loading

1 comment on commit 63d3ea1

@hramos
Copy link
Contributor

@hramos hramos commented on 63d3ea1 Feb 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit is breaking open source tests in Circle and will be reverted in #12526.

Please sign in to comment.