From 264d67c424841660874bcf11ad686aea57fe7a44 Mon Sep 17 00:00:00 2001 From: Louis Lagrange Date: Thu, 4 May 2017 00:08:14 -0700 Subject: [PATCH] Add ListEmptyComponent prop Summary: Hey there :) Please let me know if the name `ListEmptyComponent` should be changed. I also thought about `ListNoItemsComponent`. Or maybe `ListPlaceholderComponent`? - [x] Explain the **motivation** for making this change. - [x] Provide a **test plan** demonstrating that the code is solid. - [x] Match the **code formatting** of the rest of the codebase. - [x] Target the `master` branch, NOT a "stable" branch. In a FlatList, I wanted to show some placeholder when my data is empty (while keeping eventual Header/Footer/RefreshControl). A way around this issue would be to do something like adding a `ListHeaderComponent` that checks if the list is empty, like so: ```js ListHeaderComponent={() => (!data.length ? No data found : null)} ``` But I felt it was not easily readable as soon as you have an actual header. This PR adds a `ListEmptyComponent` that is rendered when the list is empty. I added tests for VirtualizedList, FlatList and SectionList and ran `yarn test -- -u`. I then checked that the snapshots changed like I wanted. I also tested this against one of my project, though I had to manually add my changes because the project is on RN 0.43. Here are the docs screenshots: - [VirtualizedList](https://cloud.githubusercontent.com/assets/82368/25566000/0ebf2b82-2dd2-11e7-8b80-d8c505f1f2d6.png) - [FlatList](https://cloud.githubusercontent.com/assets/82368/25566005/2842ab42-2dd2-11e7-81b4-32c74c2b4fc3.png) - [SectionList](https://cloud.githubusercontent.com/assets/82368/25566010/368aec1e-2dd2-11e7-9425-3bb5e5803513.png) Thanks for your work! Closes https://github.com/facebook/react-native/pull/13718 Differential Revision: D4993711 Pulled By: sahrens fbshipit-source-id: 055b40f709067071e40308bdf5a37cedaa223dc5 --- Libraries/Lists/FlatList.js | 5 + Libraries/Lists/SectionList.js | 11 ++- Libraries/Lists/VirtualizedList.js | 34 ++++++- Libraries/Lists/__tests__/FlatList-test.js | 1 + Libraries/Lists/__tests__/SectionList-test.js | 1 + .../Lists/__tests__/VirtualizedList-test.js | 29 ++++++ .../__snapshots__/FlatList-test.js.snap | 1 + .../__snapshots__/SectionList-test.js.snap | 1 + .../VirtualizedList-test.js.snap | 91 +++++++++++++++++++ 9 files changed, 169 insertions(+), 5 deletions(-) diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js index 28932e5bc..3639cc361 100644 --- a/Libraries/Lists/FlatList.js +++ b/Libraries/Lists/FlatList.js @@ -72,6 +72,11 @@ type OptionalProps = { * `separators.updateProps`. */ ItemSeparatorComponent?: ?ReactClass, + /** + * Rendered when the list is empty. Can be a React Component Class, a render function, or + * a rendered element. + */ + ListEmptyComponent?: ?(ReactClass | React.Element), /** * Rendered at the bottom of all the items. Can be a React Component Class, a render function, or * a rendered element. diff --git a/Libraries/Lists/SectionList.js b/Libraries/Lists/SectionList.js index f45936b93..3e9fcae79 100644 --- a/Libraries/Lists/SectionList.js +++ b/Libraries/Lists/SectionList.js @@ -87,11 +87,18 @@ type OptionalProps> = { */ ItemSeparatorComponent?: ?ReactClass, /** - * Rendered at the very beginning of the list. + * Rendered at the very beginning of the list. Can be a React Component Class, a render function, or + * a rendered element. */ ListHeaderComponent?: ?(ReactClass | React.Element), /** - * Rendered at the very end of the list. + * Rendered when the list is empty. Can be a React Component Class, a render function, or + * a rendered element. + */ + ListEmptyComponent?: ?(ReactClass | React.Element), + /** + * Rendered at the very end of the list. Can be a React Component Class, a render function, or + * a rendered element. */ ListFooterComponent?: ?(ReactClass | React.Element), /** diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index 1a9f33438..21ce56dc0 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -82,6 +82,21 @@ type OptionalProps = { */ initialScrollIndex?: ?number, keyExtractor: (item: Item, index: number) => string, + /** + * Rendered when the list is empty. Can be a React Component Class, a render function, or + * a rendered element. + */ + ListEmptyComponent?: ?(ReactClass | React.Element), + /** + * Rendered at the bottom of all the items. Can be a React Component Class, a render function, or + * a rendered element. + */ + ListFooterComponent?: ?(ReactClass | React.Element), + /** + * Rendered at the top of all the items. Can be a React Component Class, a render function, or + * a rendered element. + */ + ListHeaderComponent?: ?(ReactClass | React.Element), /** * The maximum number of items to render in each incremental render batch. The more rendered at * once, the better the fill rate, but responsiveness my suffer because rendering content may @@ -394,14 +409,14 @@ class VirtualizedList extends React.PureComponent { }; render() { - const {ListFooterComponent, ListHeaderComponent} = this.props; + const {ListEmptyComponent, ListFooterComponent, ListHeaderComponent} = this.props; const {data, disableVirtualization, horizontal} = this.props; const cells = []; const stickyIndicesFromProps = new Set(this.props.stickyHeaderIndices); const stickyHeaderIndices = []; if (ListHeaderComponent) { const element = React.isValidElement(ListHeaderComponent) - ? ListHeaderComponent + ? ListHeaderComponent // $FlowFixMe : ; cells.push( @@ -476,10 +491,19 @@ class VirtualizedList extends React.PureComponent { ); } + } else if (ListEmptyComponent) { + const element = React.isValidElement(ListEmptyComponent) + ? ListEmptyComponent // $FlowFixMe + : ; + cells.push( + + {element} + + ); } if (ListFooterComponent) { const element = React.isValidElement(ListFooterComponent) - ? ListFooterComponent + ? ListFooterComponent // $FlowFixMe : ; cells.push( @@ -585,6 +609,10 @@ class VirtualizedList extends React.PureComponent { this._maybeCallOnEndReached(); }; + _onLayoutEmpty = (e) => { + this.props.onLayout && this.props.onLayout(e); + }; + _onLayoutFooter = (e) => { this._footerLength = this._selectLength(e.nativeEvent.layout); }; diff --git a/Libraries/Lists/__tests__/FlatList-test.js b/Libraries/Lists/__tests__/FlatList-test.js index e0d6fe56c..d5bed75dc 100644 --- a/Libraries/Lists/__tests__/FlatList-test.js +++ b/Libraries/Lists/__tests__/FlatList-test.js @@ -48,6 +48,7 @@ describe('FlatList', () => { const component = ReactTestRenderer.create( } + ListEmptyComponent={() => } ListFooterComponent={() =>