Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement FlatList everywhere #536

Merged
merged 35 commits into from
Sep 25, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d7ced98
add comment
marcaaron Sep 23, 2020
dc6b35a
remove reverse
marcaaron Sep 24, 2020
d9fc537
remove unneeded things
marcaaron Sep 24, 2020
6c4d266
fix starting point of flex items
marcaaron Sep 24, 2020
941a8eb
Fix up comments
marcaaron Sep 24, 2020
3d05786
space
marcaaron Sep 24, 2020
be11914
more comments
marcaaron Sep 24, 2020
f4b578d
use a boolean flag for needs layout. fix scroll to bottom
marcaaron Sep 24, 2020
d9f5efe
Improve comments and clean up
marcaaron Sep 24, 2020
1796453
Render all reports at once
marcaaron Sep 24, 2020
b76b425
mush better
marcaaron Sep 24, 2020
2c81b34
get FlatList to work hehe
marcaaron Sep 25, 2020
e97b820
remove react-window
marcaaron Sep 25, 2020
96bc392
update package json
marcaaron Sep 25, 2020
90f6273
fix up comment
marcaaron Sep 25, 2020
33ad0ef
fix conflicts
marcaaron Sep 25, 2020
dc11865
move renderItem to method
marcaaron Sep 25, 2020
9f7171c
start with 50 1 is too few
marcaaron Sep 25, 2020
5b3677e
use Scroll to index as end is actualy the top of the list in this case
marcaaron Sep 25, 2020
6b99ee8
fix the grouping issues
marcaaron Sep 25, 2020
617da50
make more epic
marcaaron Sep 25, 2020
11928e9
remove opacity stuff
marcaaron Sep 25, 2020
eccd61c
bind more methods for performance or something
marcaaron Sep 25, 2020
b43de0c
offset 0 is the secret
marcaaron Sep 25, 2020
e1064b6
Fix up some comments
marcaaron Sep 25, 2020
6897b95
fix typo
marcaaron Sep 25, 2020
9b34893
Lighten up report action items
marcaaron Sep 25, 2020
94cedac
move perf things into the InvertedFlatList
marcaaron Sep 25, 2020
195e46c
Actually, nothing bad will happen if we have no reports
marcaaron Sep 25, 2020
f379508
remove loading spinner
marcaaron Sep 25, 2020
54783f5
Remove performance steps. Improve measuring logic for offset
marcaaron Sep 25, 2020
63f13e3
Add hack to make scroll direction work on web
marcaaron Sep 25, 2020
7c61301
Fix style
marcaaron Sep 25, 2020
4305201
new lines yum
marcaaron Sep 25, 2020
924831a
Update src/lib/CollectionUtils.js
marcaaron Sep 25, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"react-router-dom": "^5.2.0",
"react-router-native": "^5.2.0",
"react-web-config": "^1.0.0",
"react-window": "^1.8.5",
"save": "^2.4.0",
"underscore": "^1.10.2"
},
Expand Down
166 changes: 166 additions & 0 deletions src/components/InvertedFlatList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import React, {forwardRef, createContext, Component} from 'react';
import {View} from 'react-native';
import {VariableSizeList} from 'react-window';
import styles from '../../style/StyleSheet';

const ReactWindowContext = createContext({});
const DEFAULT_ROW_HEIGHT = 42;
Julesssss marked this conversation as resolved.
Show resolved Hide resolved

/**
* This is the innermost element and we are passing it as a custom
* component so that we can overwrite some styles and simulate
* an inverse FlatList with items starting from the bottom of the
* scroll position by adding additional margin. react-window has no
* inverted feature so this works around the existing API.
*/
const innerElement = forwardRef((props, ref) => (
<ReactWindowContext.Consumer>
{({dimensions}) => {
const innerHeight = props.style.height;
const top = dimensions.top || 0;
const height = dimensions.height || 0;
const difference = height - top - innerHeight;

return (
<div
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={ref}
style={{
...props.style,
position: 'relative',
marginTop: difference > 0 ? difference : 0,
}}
/>
);
}}
</ReactWindowContext.Consumer>
));

/**
* This component is a recreation of FlatList for web.
* FlatList when used with an "inverted" prop does not work
* correctly on web but works fine for mobile so we are
* using react-window here to create our inverted list
* scroller to improve performance on web.
*/
class InvertedFlatList extends Component {
constructor(props) {
super(props);

this.getSize = this.getSize.bind(this);

// Stores each item's computed height after it renders
// once and is reference for the life of the component
this.sizeMap = {};
this.state = {listHeight: 0};
}

componentDidMount() {
// Set the height of the list after the component mounts
// and then scroll to the bottom.
this.setState({listHeight: this.list.offsetHeight}, () => {
this.scrollToEnd();
});

// This allows us to call this.listRef.scrollToEnd() from
// the parent component where this will be used.
this.props.forwardedRef({
scrollToEnd: () => this.scrollToEnd(),
});
}

shouldComponentUpdate(prevProps, prevState) {
// We only need to update when the data length changes
// or the list height changes (since we are calculating the
// height on the first render pass)
return prevProps.data.length !== this.props.data.length
|| prevState.listHeight !== this.state.listHeight;
}

/**
* Returns a previously recorded size or the default.
*
* The default is not a minimum, but the initial height
* to pass to react-window so that we can calculate and
* cache the actual height.
*
* @param {Number} index
* @returns {Number}
*/
getSize(index) {
return this.sizeMap[index] || DEFAULT_ROW_HEIGHT;
}

/**
* ScrollToEnd implementation. Similar to FlatListInstance.scrollToEnd()
*/
scrollToEnd() {
this.listRef.scrollToItem(this.props.data.length - 1, 'end');
}

render() {
return (
<ReactWindowContext.Provider

// These values are passed via context as there seems to
// be no way to pass additional props to the innerElement
// controlled by react-window
value={{dimensions: this.list ? this.list.getBoundingClientRect() : {}}}
>
<View
style={styles.flex1}
ref={el => this.list = el}
>
<VariableSizeList
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
height={this.state.listHeight}
itemCount={this.props.data.length}
itemSize={this.getSize}
width="100%"
ref={el => this.listRef = el}
overscanCount={1}
innerRef={el => this.innerRef = el}
outerRef={el => this.outerRef = el}
innerElementType={innerElement}
>
{({index, style}) => (

// Do not modify or remove these styles they are
// required by react-window to function correctly
<div style={style}>
{this.props.renderItem({
item: this.props.data[index],
index,
onLayout: ({nativeEvent}) => {
const computedHeight = nativeEvent.layout.height;
if (computedHeight === DEFAULT_ROW_HEIGHT) {
throw new Error('InvertedFlatList rendered row height equal to the constant default height.');
}

// Check out previous size against the computedHeight returned when
// our renderItem layouts. If there's any difference then we reset
const prevSize = this.sizeMap[index] || 0;
if (prevSize !== computedHeight) {
this.sizeMap[index] = computedHeight;
this.listRef.resetAfterIndex(index);
}
},

// Default row height is a magic number. In the event that we
// have a row that is the exact same size we will get caught in
// an infinite loop. Avoid setting row heights to this.
needsLayoutCalculation: style.height === DEFAULT_ROW_HEIGHT,
Julesssss marked this conversation as resolved.
Show resolved Hide resolved
})}
</div>
)}
</VariableSizeList>
</View>
</ReactWindowContext.Provider>
);
}
}

export default forwardRef((props, forwardedRef) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<InvertedFlatList {...props} forwardedRef={forwardedRef} />
));
15 changes: 15 additions & 0 deletions src/components/InvertedFlatList/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, {forwardRef} from 'react';
import {FlatList} from 'react-native';

/**
* Inverted FlatList for native is just the base FlatList
*/
export default forwardRef((props, forwardedRef) => (
<FlatList
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
data={props.data.slice().reverse()}
ref={forwardedRef}
inverted
Julesssss marked this conversation as resolved.
Show resolved Hide resolved
/>
));
60 changes: 17 additions & 43 deletions src/page/home/MainView.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,51 +25,25 @@ const propTypes = {
const defaultProps = {
reports: {},
};
const MainView = (props) => {
const reportIDInURL = parseInt(props.match.params.reportID, 10);

class MainView extends React.Component {
render() {
if (!_.size(this.props.reports)) {
return null;
}

const reportIDInURL = parseInt(this.props.match.params.reportID, 10);

// The styles for each of our reports. Basically, they are all hidden except for the one matching the
// reportID in the URL
let activeReportID;
const reportStyles = _.reduce(this.props.reports, (memo, report) => {
const isActiveReport = reportIDInURL === report.reportID;
const finalData = {...memo};
let reportStyle;

if (isActiveReport) {
activeReportID = report.reportID;
reportStyle = [styles.dFlex, styles.flex1];
} else {
reportStyle = [styles.dNone];
}

finalData[report.reportID] = [reportStyle];
return finalData;
}, {});

return (
<>
{_.map(this.props.reports, report => (
<View
key={report.reportID}
style={reportStyles[report.reportID]}
>
<ReportView
reportID={report.reportID}
isActiveReport={report.reportID === activeReportID}
/>
</View>
))}
</>
);
if (!_.size(props.reports) || !reportIDInURL) {
return null;
}
}

return (
<View
key={reportIDInURL}
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
style={[styles.dFlex, styles.flex1]}
>
<ReportView
reportID={reportIDInURL}
isActiveReport
/>
</View>
);
};

MainView.propTypes = propTypes;
MainView.defaultProps = defaultProps;
Expand Down
24 changes: 18 additions & 6 deletions src/page/home/report/ReportActionItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,34 @@ const propTypes = {

// Should the comment have the appearance of being grouped with the previous comment?
displayAsGroup: PropTypes.bool.isRequired,

// Used to tell this component that we want it to call onLayout when it renders
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
needsLayoutCalculation: PropTypes.bool,

// Called when the view layout completes
onLayout: PropTypes.func,
};

const defaultProps = {
needsLayoutCalculation: false,
onLayout: () => {},
};

class ReportActionItem extends React.Component {
shouldComponentUpdate(nextProps) {
// This component should only render if the action's sequenceNumber or displayAsGroup props change
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
return nextProps.displayAsGroup !== this.props.displayAsGroup
return nextProps.needsLayoutCalculation !== this.props.needsLayoutCalculation
|| nextProps.displayAsGroup !== this.props.displayAsGroup
|| !_.isEqual(nextProps.action, this.props.action);
}

render() {
const {action, displayAsGroup} = this.props;
if (action.actionName !== 'ADDCOMMENT') {
return null;
}

return (
<View>
<View
style={{opacity: this.props.needsLayoutCalculation ? 0 : 1}}
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
onLayout={this.props.onLayout}
>
{!displayAsGroup && <ReportActionItemSingle action={action} />}
{displayAsGroup && <ReportActionItemGrouped action={action} />}
</View>
Expand All @@ -37,5 +48,6 @@ class ReportActionItem extends React.Component {
}

ReportActionItem.propTypes = propTypes;
ReportActionItem.defaultProps = defaultProps;

export default ReportActionItem;
Loading