From 070d6beb83811a15c573bd125ac5d9ba237198f6 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Tue, 28 Apr 2020 20:18:52 +0200 Subject: [PATCH] Improved accordion performance --- .../Animations/AnimatedAccordion.js | 46 ++++++++++++------- src/components/Lists/Clubs/ClubListHeader.js | 31 +++++++++++-- .../Lists/PlanexGroups/GroupListAccordion.js | 22 ++------- src/screens/Amicale/Clubs/ClubListScreen.js | 34 ++++---------- src/screens/Amicale/LoginScreen.js | 4 +- 5 files changed, 72 insertions(+), 65 deletions(-) diff --git a/src/components/Animations/AnimatedAccordion.js b/src/components/Animations/AnimatedAccordion.js index 1441624..9c4fc79 100644 --- a/src/components/Animations/AnimatedAccordion.js +++ b/src/components/Animations/AnimatedAccordion.js @@ -12,8 +12,7 @@ type Props = { title: string, subtitle?: string, left?: (props: { [keys: string]: any }) => React.Node, - startOpen: boolean, - keepOpen: boolean, + opened: boolean, unmountWhenCollapsed: boolean, children?: React.Node, } @@ -24,36 +23,51 @@ type State = { const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon); -class AnimatedAccordion extends React.PureComponent { +class AnimatedAccordion extends React.Component { static defaultProps = { - startOpen: false, - keepOpen: false, + opened: false, unmountWhenCollapsed: false, } chevronRef: { current: null | AnimatedListIcon }; + chevronIcon: string; + animStart: string; + animEnd: string; + state = { - expanded: false, + expanded: this.props.opened, } constructor(props) { super(props); this.chevronRef = React.createRef(); + this.setupChevron(); } - componentDidMount() { - if (this.props.startOpen) - this.toggleAccordion(); + setupChevron() { + if (this.state.expanded) { + this.chevronIcon = "chevron-up"; + this.animStart = "180deg"; + this.animEnd = "0deg"; + } else { + this.chevronIcon = "chevron-down"; + this.animStart = "0deg"; + this.animEnd = "180deg"; + } } toggleAccordion = () => { - if (!this.props.keepOpen) { - if (this.chevronRef.current != null) - this.chevronRef.current.transitionTo({rotate: this.state.expanded ? '0deg' : '180deg'}); - this.setState({expanded: !this.state.expanded}) - } + if (this.chevronRef.current != null) + this.chevronRef.current.transitionTo({rotate: this.state.expanded ? this.animStart : this.animEnd}); + this.setState({expanded: !this.state.expanded}) }; + shouldComponentUpdate(nextProps: Props) { + this.state.expanded = nextProps.opened; + this.setupChevron(); + return true; + } + render() { const colors = this.props.theme.colors; return ( @@ -67,13 +81,13 @@ class AnimatedAccordion extends React.PureComponent { right={(props) => } left={this.props.left} /> - + {!this.props.unmountWhenCollapsed || (this.props.unmountWhenCollapsed && this.state.expanded) ? this.props.children : null} diff --git a/src/components/Lists/Clubs/ClubListHeader.js b/src/components/Lists/Clubs/ClubListHeader.js index 7028742..dbe4f62 100644 --- a/src/components/Lists/Clubs/ClubListHeader.js +++ b/src/components/Lists/Clubs/ClubListHeader.js @@ -1,22 +1,43 @@ // @flow import * as React from 'react'; -import {Card, List, Text} from 'react-native-paper'; +import {Card, Chip, List, Text} from 'react-native-paper'; import {StyleSheet, View} from "react-native"; import i18n from 'i18n-js'; import AnimatedAccordion from "../../Animations/AnimatedAccordion"; +import {isItemInCategoryFilter} from "../../../utils/Search"; +import type {category} from "../../../screens/Amicale/Clubs/ClubListScreen"; type Props = { - categoryRender: Function, - categories: Array, + categories: Array, + onChipSelect: (id: number) => void, + selectedCategories: Array, } class ClubListHeader extends React.Component { + shouldComponentUpdate(nextProps: Props) { + return nextProps.selectedCategories.length !== this.props.selectedCategories.length; + } + + getChipRender = (category: category, key: string) => { + const onPress = () => this.props.onChipSelect(category.id); + return + {category.name} + ; + }; + + getCategoriesRender() { let final = []; for (let i = 0; i < this.props.categories.length; i++) { - final.push(this.props.categoryRender(this.props.categories[i], this.props.categories[i].id)); + final.push(this.getChipRender(this.props.categories[i], this.props.categories[i].id.toString())); } return final; } @@ -27,7 +48,7 @@ class ClubListHeader extends React.Component { } - startOpen={true} + opened={true} > {i18n.t("clubs.categoriesFilterMessage")} diff --git a/src/components/Lists/PlanexGroups/GroupListAccordion.js b/src/components/Lists/PlanexGroups/GroupListAccordion.js index 76d37c9..6dbfecc 100644 --- a/src/components/Lists/PlanexGroups/GroupListAccordion.js +++ b/src/components/Lists/PlanexGroups/GroupListAccordion.js @@ -19,27 +19,16 @@ type Props = { theme: CustomTheme, } -type State = { - expanded: boolean, -} - const LIST_ITEM_HEIGHT = 64; -class GroupListAccordion extends React.Component { +class GroupListAccordion extends React.Component { constructor(props) { super(props); - this.state = { - expanded: props.item.id === "0", - } } - shouldComponentUpdate(nextProps: Props, nextSate: State) { - if (nextProps.currentSearchString !== this.props.currentSearchString) - this.state.expanded = nextProps.currentSearchString.length > 0; - + shouldComponentUpdate(nextProps: Props) { return (nextProps.currentSearchString !== this.props.currentSearchString) - || (nextSate.expanded !== this.state.expanded) || (nextProps.favoriteNumber !== this.props.favoriteNumber) || (nextProps.item.content.length !== this.props.item.content.length); } @@ -61,8 +50,6 @@ class GroupListAccordion extends React.Component { return null; } - itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index}); - render() { const item = this.props.item; return ( @@ -82,6 +69,7 @@ class GroupListAccordion extends React.Component { /> : null} unmountWhenCollapsed={true}// Only render list if expanded for increased performance + opened={this.props.item.id === 0 || this.props.currentSearchString.length > 0} > {/*$FlowFixMe*/} { keyExtractor={this.keyExtractor} listKey={item.id.toString()} // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration - getItemLayout={this.itemLayout} // Broken with search - removeClippedSubviews={true} + // getItemLayout={this.itemLayout} // Broken with search + // removeClippedSubviews={true} /> diff --git a/src/screens/Amicale/Clubs/ClubListScreen.js b/src/screens/Amicale/Clubs/ClubListScreen.js index ccdd167..0418197 100644 --- a/src/screens/Amicale/Clubs/ClubListScreen.js +++ b/src/screens/Amicale/Clubs/ClubListScreen.js @@ -2,7 +2,7 @@ import * as React from 'react'; import {Animated, Platform} from "react-native"; -import {Chip, Searchbar} from 'react-native-paper'; +import {Searchbar} from 'react-native-paper'; import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen"; import i18n from "i18n-js"; import ClubListItem from "../../../components/Lists/Clubs/ClubListItem"; @@ -36,7 +36,7 @@ type Props = { } type State = { - currentlySelectedCategories: Array, + currentlySelectedCategories: Array, currentSearchString: string, } @@ -99,13 +99,11 @@ class ClubListScreen extends React.Component { this.updateFilteredData(str, null); }; - keyExtractor = (item: club) => { - return item.id.toString(); - }; + keyExtractor = (item: club) => item.id.toString(); itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index}); - getScreen = (data: Array<{categories: Array, clubs: Array} | null>) => { + getScreen = (data: Array<{ categories: Array, clubs: Array } | null>) => { let categoryList = []; let clubList = []; if (data[0] != null) { @@ -131,11 +129,9 @@ class ClubListScreen extends React.Component { ) }; - onChipSelect(id: string) { - this.updateFilteredData(null, id); - } + onChipSelect = (id: number) => this.updateFilteredData(null, id); - updateFilteredData(filterStr: string | null, categoryId: string | null) { + updateFilteredData(filterStr: string | null, categoryId: number | null) { let newCategoriesState = [...this.state.currentlySelectedCategories]; let newStrState = this.state.currentSearchString; if (filterStr !== null) @@ -154,23 +150,11 @@ class ClubListScreen extends React.Component { }) } - getChipRender = (category: category, key: string) => { - const onPress = this.onChipSelect.bind(this, category.id); - return - {category.name} - ; - }; - getListHeader() { return ; } @@ -189,7 +173,7 @@ class ClubListScreen extends React.Component { return shouldRender; } - getRenderItem = ({item}: {item: club}) => { + getRenderItem = ({item}: { item: club }) => { const onPress = this.onListItemPress.bind(this, item); if (this.shouldRenderItem(item)) { return ( diff --git a/src/screens/Amicale/LoginScreen.js b/src/screens/Amicale/LoginScreen.js index fcc2404..28d49d5 100644 --- a/src/screens/Amicale/LoginScreen.js +++ b/src/screens/Amicale/LoginScreen.js @@ -37,8 +37,8 @@ const emailRegex = /^.+@.+\..+$/; class LoginScreen extends React.Component { state = { - email: '', - password: '', + email: 'vergnet@etud.insa-toulouse.fr', + password: 'LHSdf32รน43', isEmailValidated: false, isPasswordValidated: false, loading: false,