From f2f6a734a1368bfff5bd52a10f0c870d588598f2 Mon Sep 17 00:00:00 2001 From: Fishery Date: Wed, 17 Jul 2019 16:58:42 -0400 Subject: [PATCH] updates & changes Updated packages, info and version Updated eslint/flow config Merged PR #21, PR #22 and PR #24 --- .eslintrc | 8 --- .eslintrc.js | 33 ++++++++++ .flowconfig | 82 ++++++++++++++++++------- .gitignore | 3 +- package.json | 42 +++++++------ src/MasonryList.js | 146 ++++++++++++++++++++++++++++++--------------- 6 files changed, 217 insertions(+), 97 deletions(-) delete mode 100644 .eslintrc create mode 100644 .eslintrc.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 871f9a2..0000000 --- a/.eslintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": [ - "anf", - ], - "rules": { - "react/prop-types": 0 - } -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..bddd44b --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,33 @@ +module.exports = { + "env": { + "es6": true, + "react-native/react-native": true + }, + "extends": [ + "airbnb", + "eslint:recommended", + "anf" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + 'parser': 'babel-eslint', + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": [ + "react", + "react-native" + ], + "rules": { + "react/forbid-prop-types": [0, { "forbid": ["any", "array", "object"] }], + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], + "react/prop-types": 0, + "no-use-before-define": ["error", { "variables": false }] // Temporarily disable this. See https://github.com/Intellicode/eslint-plugin-react-native/issues/22 + } +}; \ No newline at end of file diff --git a/.flowconfig b/.flowconfig index 4fe3d32..5f2c4a5 100644 --- a/.flowconfig +++ b/.flowconfig @@ -7,53 +7,93 @@ ; Ignore unexpected extra "@providesModule" .*/node_modules/.*/node_modules/fbjs/.* -.*/node_modules/react-native/RNTester/.* ; Ignore duplicate module providers ; For RN Apps installed via npm, "Libraries" folder is inside ; "node_modules/react-native" but in the source repo it is in the root -.*/Libraries/react-native/React.js -.*/Libraries/react-native/ReactNative.js +node_modules/react-native/Libraries/react-native/React.js -/\example/ +; Ignore polyfills +node_modules/react-native/Libraries/polyfills/.* + +; These should not be required directly +; require from fbjs/lib instead: require('fbjs/lib/warning') +node_modules/warning/.* + +; Flow doesn't support platforms +.*/Libraries/Utilities/HMRLoadingView.js + +[untyped] +.*/node_modules/@react-native-community/cli/.*/.* [include] [libs] node_modules/react-native/Libraries/react-native/react-native-interface.js -node_modules/react-native/flow +node_modules/react-native/flow/ [options] emoji=true -module.system=haste +esproposal.optional_chaining=enable +esproposal.nullish_coalescing=enable -experimental.strict_type_args=true +module.file_ext=.js +module.file_ext=.json +module.file_ext=.ios.js -esproposal.decorators=ignore -esproposal.class_static_fields=enable -esproposal.class_instance_fields=enable +module.system=haste +module.system.haste.use_name_reducers=true +# get basename +module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' +# strip .js or .js.flow suffix +module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' +# strip .ios suffix +module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' +module.system.haste.paths.blacklist=.*/__tests__/.* +module.system.haste.paths.blacklist=.*/__mocks__/.* +module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* +module.system.haste.paths.whitelist=/node_modules/react-native/RNTester/.* +module.system.haste.paths.whitelist=/node_modules/react-native/IntegrationTests/.* +module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/react-native/react-native-implementation.js +module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* munge_underscores=true -module.file_ext=.ios.js -module.file_ext=.js -module.file_ext=.jsx -module.file_ext=.json module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' suppress_type=$FlowIssue suppress_type=$FlowFixMe -suppress_type=$FixMe - suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError -unsafe.enable_getters_and_setters=true +[lints] +sketchy-null-number=warn +sketchy-null-mixed=warn +sketchy-number=warn +untyped-type-import=warn +nonstrict-import=warn +deprecated-type=warn +unsafe-getters-setters=warn +inexact-spread=warn +unnecessary-invariant=warn +signature-verification-failure=warn +deprecated-utility=error + +[strict] +deprecated-type +nonstrict-import +sketchy-null +unclear-type +unsafe-getters-setters +untyped-import +untyped-type-import [version] -^0.53.0 +^0.102.0 diff --git a/.gitignore b/.gitignore index 40b878d..504afef 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules/ \ No newline at end of file +node_modules/ +package-lock.json diff --git a/package.json b/package.json index 8cf85a7..9d98f05 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,17 @@ { - "name": "@appandflow/masonry-list", + "name": "@fisherwise/masonry-list", "version": "0.4.0", "main": "./src/MasonryList.js", "private": false, "repository": { "type": "git", - "url": "https://github.com/AppAndFlow/react-native-masonry-list.git" + "url": "https://github.com/fisherwise/react-native-masonry-list.git" }, "bugs": { - "url": "https://github.com/AppAndFlow/react-native-masonry-list.git/issues" + "url": "https://github.com/fisherwise/react-native-masonry-list.git/issues" }, "keywords": [ "react-native", - "appandflow", "masonry", "list" ], @@ -21,6 +20,7 @@ "files": [ "/src" ], + "lint": "eslint src", "lint-staged": { "*.js": [ "yarn prettier", @@ -45,21 +45,25 @@ "react": "*" }, "devDependencies": { - "babel-preset-react-native": "^3.0.1", - "chalk": "^2.1.0", - "eslint": "^4.5.0", - "eslint-config-anf": "^0.5.2", - "eslint-config-prettier": "^2.3.0", - "flow-bin": "^0.53.1", - "glob": "^7.1.2", - "husky": "^0.14.3", - "jest": "^20.0.4", - "jest-expo": "^20.0.0", - "lint-staged": "^4.0.3", - "prettier": "^1.5.3", - "react": "16.0.0-beta.5", - "react-native": "janicduplessis/react-native", - "react-test-renderer": "16.0.0-beta.5" + "babel-preset-react-native": "^4.0.1", + "chalk": "^2.4.2", + "core-js": "^3.1.4", + "eslint": "^6.0.1", + "eslint-config-airbnb": "^17.1.1", + "eslint-config-anf": "^0.6.2", + "eslint-config-prettier": "^6.0.0", + "eslint-plugin-react": "^7.14.2", + "eslint-plugin-react-native": "^3.7.0", + "flow-bin": "^0.102.0", + "glob": "^7.1.4", + "husky": "^3.0.0", + "jest": "^24.8.0", + "jest-expo": "^33.0.2", + "lint-staged": "^9.2.0", + "prettier": "^1.18.2", + "react": "16.8.6", + "react-native": "0.60.3", + "react-test-renderer": "16.8.6" }, "dependencies": {} } diff --git a/src/MasonryList.js b/src/MasonryList.js index ac1ba32..7bea928 100644 --- a/src/MasonryList.js +++ b/src/MasonryList.js @@ -1,6 +1,11 @@ +/* eslint-disable react/default-props-match-prop-types */ +/* eslint-disable react/require-default-props */ +/* eslint-disable no-return-assign */ +/* eslint-disable no-underscore-dangle */ // @flow import * as React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies import { VirtualizedList, View, @@ -44,12 +49,15 @@ const _stateFromProps = ({ numColumns, data, getHeightForItem }) => { export type Props = { data: Array, numColumns: number, - renderItem: ({ item: any, index: number, column: number }) => ?React.Element< - any, - >, + renderItem: ({ + item: any, + index: number, + column: number, + }) => ?React.Element, getHeightForItem: ({ item: any, index: number }) => number, ListHeaderComponent?: ?React.ComponentType, ListEmptyComponent?: ?React.ComponentType, + ListFooterComponent?: ?React.ComponentType, /** * Used to extract a unique key for a given item at the specified index. Key is used for caching * and as the react key to track item re-ordering. The default extractor checks `item.key`, then @@ -77,20 +85,21 @@ export type Props = { */ onRefresh?: ?Function, }; + type State = { columns: Array, }; // This will get cloned and added a bunch of props that are supposed to be on -// ScrollView so we wan't to make sure we don't pass them over (especially +// ScrollView so we want to make sure we don't pass them over (especially // onLayout since it exists on both). -class FakeScrollView extends React.Component<{ style?: any, children?: any }> { +class FakeScrollView extends React.PureComponent<{ + style?: any, + children?: any, +}> { render() { - return ( - - {this.props.children} - - ); + const { style, children } = this.props; + return {children}; } } @@ -102,13 +111,13 @@ export default class MasonryList extends React.Component { if (props.onRefresh && props.refreshing != null) { return ( } + {...props} /> ); } @@ -117,13 +126,12 @@ export default class MasonryList extends React.Component { }; state = _stateFromProps(this.props); + _listRefs: Array = []; + _scrollRef: ?ScrollView; - _endsReached = 0; - componentWillReceiveProps(newProps: Props) { - this.setState(_stateFromProps(newProps)); - } + _endReached = false; getScrollResponder() { if (this._scrollRef && this._scrollRef.getScrollResponder) { @@ -139,19 +147,14 @@ export default class MasonryList extends React.Component { return findNodeHandle(this._scrollRef); } - scrollToOffset({ offset, animated }: any) { - if (this._scrollRef) { - this._scrollRef.scrollTo({ y: offset, animated }); - } - } - - _onLayout = event => { + _onLayout = (event: Object) => { this._listRefs.forEach( list => list && list._onLayout && list._onLayout(event), ); }; - _onContentSizeChange = (width, height) => { + _onContentSizeChange = (width: number, height: number) => { + this._endReached = false; this._listRefs.forEach( list => list && @@ -160,36 +163,44 @@ export default class MasonryList extends React.Component { ); }; - _onScroll = event => { - if (this.props.onScroll) { - this.props.onScroll(event); + _onScroll = (event: Object) => { + const { onScroll } = this.props; + + if (onScroll) { + onScroll(event); } this._listRefs.forEach( list => list && list._onScroll && list._onScroll(event), ); }; - _onScrollBeginDrag = event => { - if (this.props.onScrollBeginDrag) { - this.props.onScrollBeginDrag(event); + _onScrollBeginDrag = (event: Object) => { + const { onScrollBeginDrag } = this.props; + + if (onScrollBeginDrag) { + onScrollBeginDrag(event); } this._listRefs.forEach( list => list && list._onScrollBeginDrag && list._onScrollBeginDrag(event), ); }; - _onScrollEndDrag = event => { - if (this.props.onScrollEndDrag) { - this.props.onScrollEndDrag(event); + _onScrollEndDrag = (event: Object) => { + const { onScrollEndDrag } = this.props; + + if (onScrollEndDrag) { + onScrollEndDrag(event); } this._listRefs.forEach( list => list && list._onScrollEndDrag && list._onScrollEndDrag(event), ); }; - _onMomentumScrollEnd = event => { - if (this.props.onMomentumScrollEnd) { - this.props.onMomentumScrollEnd(event); + _onMomentumScrollEnd = (event: Object) => { + const { onMomentumScrollEnd } = this.props; + + if (onMomentumScrollEnd) { + onMomentumScrollEnd(event); } this._listRefs.forEach( list => @@ -197,8 +208,22 @@ export default class MasonryList extends React.Component { ); }; + _onEndReached = (info: { distanceFromEnd: number }) => { + if (this._endReached) { + return; + } + this._endReached = true; + const { onEndReached } = this.props; + if (onEndReached) { + Promise.resolve(onEndReached(info)).then(() => { + this._endReached = false; + }); + } + }; + _getItemLayout = (columnIndex, rowIndex) => { - const column = this.state.columns[columnIndex]; + const { columns } = this.state; + const column = columns[columnIndex]; let offset = 0; for (let ii = 0; ii < rowIndex; ii += 1) { offset += column.heights[ii]; @@ -208,21 +233,34 @@ export default class MasonryList extends React.Component { _renderScrollComponent = () => ; - _getItemCount = data => data.length; + _getItemCount = (data: any[]) => data.length; - _getItem = (data, index) => data[index]; + _getItem = (data: any[], index: number) => data[index]; _captureScrollRef = ref => (this._scrollRef = ref); + scrollToOffset({ offset, animated }: any) { + if (this._scrollRef) { + this._scrollRef.scrollTo({ y: offset, animated }); + } + } + + // TODO: Update to reflect props change + // eslint-disable-next-line camelcase + UNSAFE_componentWillReceiveProps(newProps: Props) { + this.setState(_stateFromProps(newProps)); + } + render() { const { renderItem, ListHeaderComponent, ListEmptyComponent, + ListFooterComponent, keyExtractor, - onEndReached, ...props } = this.props; + let headerElement; if (ListHeaderComponent) { headerElement = ; @@ -231,33 +269,44 @@ export default class MasonryList extends React.Component { if (ListEmptyComponent) { emptyElement = ; } + let footerElement; + if (ListFooterComponent) { + footerElement = ; + } + + const { columns } = this.state; const content = ( - {this.state.columns.map(col => + {columns.map(col => ( (this._listRefs[col.index] = ref)} key={`$col_${col.index}`} + listKey={`$col_${col.index}`} data={col.data} getItemCount={this._getItemCount} getItem={this._getItem} getItemLayout={(data, index) => - this._getItemLayout(col.index, index)} + this._getItemLayout(col.index, index) + } renderItem={({ item, index }) => - renderItem({ item, index, column: col.index })} + renderItem({ item, index, column: col.index }) + } renderScrollComponent={this._renderScrollComponent} keyExtractor={keyExtractor} - onEndReached={onEndReached} - onEndReachedThreshold={this.props.onEndReachedThreshold} + onEndReached={this._onEndReached} + // onEndReachedThreshold={this.props.onEndReachedThreshold} removeClippedSubviews={false} - />, - )} + /> + ))} ); + const { renderScrollComponent, data } = this.props; + const scrollComponent = React.cloneElement( - this.props.renderScrollComponent(this.props), + renderScrollComponent(this.props), { ref: this._captureScrollRef, removeClippedSubviews: false, @@ -269,7 +318,8 @@ export default class MasonryList extends React.Component { onMomentumScrollEnd: this._onMomentumScrollEnd, }, headerElement, - emptyElement && this.props.data.length === 0 ? emptyElement : content, + emptyElement && data.length === 0 ? emptyElement : content, + footerElement, ); return scrollComponent;