Browse Source

Convert planex group components to functional

Arnaud Vergnet 6 months ago
parent
commit
52651ecf85

+ 60
- 74
src/components/Lists/PlanexGroups/GroupListAccordion.tsx View File

@@ -18,8 +18,8 @@
18 18
  */
19 19
 
20 20
 import * as React from 'react';
21
-import { List, withTheme } from 'react-native-paper';
22
-import { FlatList, StyleSheet, View } from 'react-native';
21
+import { List, useTheme } from 'react-native-paper';
22
+import { FlatList, StyleSheet } from 'react-native';
23 23
 import GroupListItem from './GroupListItem';
24 24
 import AnimatedAccordion from '../../Animations/AnimatedAccordion';
25 25
 import type {
@@ -34,7 +34,6 @@ type PropsType = {
34 34
   onGroupPress: (data: PlanexGroupType) => void;
35 35
   onFavoritePress: (data: PlanexGroupType) => void;
36 36
   currentSearchString: string;
37
-  theme: ReactNativePaper.Theme;
38 37
 };
39 38
 
40 39
 const LIST_ITEM_HEIGHT = 64;
@@ -49,36 +48,22 @@ const styles = StyleSheet.create({
49 48
   },
50 49
 });
51 50
 
52
-class GroupListAccordion extends React.Component<PropsType> {
53
-  shouldComponentUpdate(nextProps: PropsType): boolean {
54
-    const { props } = this;
55
-    return (
56
-      nextProps.currentSearchString !== props.currentSearchString ||
57
-      nextProps.favorites.length !== props.favorites.length ||
58
-      nextProps.item.content.length !== props.item.content.length
59
-    );
60
-  }
51
+function GroupListAccordion(props: PropsType) {
52
+  const theme = useTheme();
61 53
 
62
-  getRenderItem = ({ item }: { item: PlanexGroupType }) => {
63
-    const { props } = this;
64
-    const onPress = () => {
65
-      props.onGroupPress(item);
66
-    };
67
-    const onStarPress = () => {
68
-      props.onFavoritePress(item);
69
-    };
54
+  const getRenderItem = ({ item }: { item: PlanexGroupType }) => {
70 55
     return (
71 56
       <GroupListItem
72 57
         height={LIST_ITEM_HEIGHT}
73 58
         item={item}
74
-        favorites={props.favorites}
75
-        onPress={onPress}
76
-        onStarPress={onStarPress}
59
+        isFav={props.favorites.some((f) => f.id === item.id)}
60
+        onPress={() => props.onGroupPress(item)}
61
+        onStarPress={() => props.onFavoritePress(item)}
77 62
       />
78 63
     );
79 64
   };
80 65
 
81
-  itemLayout = (
66
+  const itemLayout = (
82 67
     _data: Array<PlanexGroupType> | null | undefined,
83 68
     index: number
84 69
   ): { length: number; offset: number; index: number } => ({
@@ -87,57 +72,58 @@ class GroupListAccordion extends React.Component<PropsType> {
87 72
     index,
88 73
   });
89 74
 
90
-  keyExtractor = (item: PlanexGroupType): string => item.id.toString();
75
+  const keyExtractor = (item: PlanexGroupType): string => item.id.toString();
91 76
 
92
-  render() {
93
-    const { props } = this;
94
-    const { item } = this.props;
95
-    var isFavorite = item.id === 0;
96
-    var isEmptyFavorite = isFavorite && props.favorites.length === 0;
97
-    return (
98
-      <View>
99
-        <AnimatedAccordion
100
-          title={
101
-            isEmptyFavorite
102
-              ? i18n.t('screens.planex.favorites.empty.title')
103
-              : item.name.replace(REPLACE_REGEX, ' ')
104
-          }
105
-          subtitle={
106
-            isEmptyFavorite
107
-              ? i18n.t('screens.planex.favorites.empty.subtitle')
108
-              : undefined
109
-          }
110
-          style={styles.container}
111
-          left={(iconProps) =>
112
-            isFavorite ? (
113
-              <List.Icon
114
-                style={iconProps.style}
115
-                icon={'star'}
116
-                color={props.theme.colors.tetrisScore}
117
-              />
118
-            ) : undefined
119
-          }
120
-          unmountWhenCollapsed={!isFavorite} // Only render list if expanded for increased performance
121
-          opened={
122
-            props.currentSearchString.length >= MIN_SEARCH_SIZE_EXPAND ||
123
-            (isFavorite && !isEmptyFavorite)
124
-          }
125
-          enabled={!isEmptyFavorite}
126
-        >
127
-          <FlatList
128
-            data={props.item.content}
129
-            extraData={props.currentSearchString + props.favorites.length}
130
-            renderItem={this.getRenderItem}
131
-            keyExtractor={this.keyExtractor}
132
-            listKey={item.id.toString()}
133
-            // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
134
-            getItemLayout={this.itemLayout}
135
-            removeClippedSubviews
77
+  var isFavorite = props.item.id === 0;
78
+  var isEmptyFavorite = isFavorite && props.favorites.length === 0;
79
+
80
+  return (
81
+    <AnimatedAccordion
82
+      title={
83
+        isEmptyFavorite
84
+          ? i18n.t('screens.planex.favorites.empty.title')
85
+          : props.item.name.replace(REPLACE_REGEX, ' ')
86
+      }
87
+      subtitle={
88
+        isEmptyFavorite
89
+          ? i18n.t('screens.planex.favorites.empty.subtitle')
90
+          : undefined
91
+      }
92
+      style={styles.container}
93
+      left={(iconProps) =>
94
+        isFavorite ? (
95
+          <List.Icon
96
+            style={iconProps.style}
97
+            icon={'star'}
98
+            color={theme.colors.tetrisScore}
136 99
           />
137
-        </AnimatedAccordion>
138
-      </View>
139
-    );
140
-  }
100
+        ) : undefined
101
+      }
102
+      unmountWhenCollapsed={!isFavorite} // Only render list if expanded for increased performance
103
+      opened={
104
+        props.currentSearchString.length >= MIN_SEARCH_SIZE_EXPAND ||
105
+        (isFavorite && !isEmptyFavorite)
106
+      }
107
+      enabled={!isEmptyFavorite}
108
+    >
109
+      <FlatList
110
+        data={props.item.content}
111
+        extraData={props.currentSearchString + props.favorites.length}
112
+        renderItem={getRenderItem}
113
+        keyExtractor={keyExtractor}
114
+        listKey={props.item.id.toString()}
115
+        // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
116
+        getItemLayout={itemLayout}
117
+        removeClippedSubviews={true}
118
+      />
119
+    </AnimatedAccordion>
120
+  );
141 121
 }
142 122
 
143
-export default withTheme(GroupListAccordion);
123
+const propsEqual = (pp: PropsType, np: PropsType) =>
124
+  pp.currentSearchString === np.currentSearchString &&
125
+  pp.favorites.length === np.favorites.length &&
126
+  pp.item.content.length === np.item.content.length &&
127
+  pp.onFavoritePress === np.onFavoritePress;
128
+
129
+export default React.memo(GroupListAccordion, propsEqual);

+ 48
- 86
src/components/Lists/PlanexGroups/GroupListItem.tsx View File

@@ -17,20 +17,19 @@
17 17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18 18
  */
19 19
 
20
-import * as React from 'react';
21
-import { List, TouchableRipple, withTheme } from 'react-native-paper';
20
+import React, { useRef } from 'react';
21
+import { List, TouchableRipple, useTheme } from 'react-native-paper';
22 22
 import * as Animatable from 'react-native-animatable';
23 23
 import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
24 24
 import type { PlanexGroupType } from '../../../screens/Planex/GroupSelectionScreen';
25 25
 import { StyleSheet, View } from 'react-native';
26 26
 import { getPrettierPlanexGroupName } from '../../../utils/Utils';
27 27
 
28
-type PropsType = {
29
-  theme: ReactNativePaper.Theme;
28
+type Props = {
30 29
   onPress: () => void;
31 30
   onStarPress: () => void;
32 31
   item: PlanexGroupType;
33
-  favorites: Array<PlanexGroupType>;
32
+  isFav: boolean;
34 33
   height: number;
35 34
 };
36 35
 
@@ -49,88 +48,51 @@ const styles = StyleSheet.create({
49 48
   },
50 49
 });
51 50
 
52
-class GroupListItem extends React.Component<PropsType> {
53
-  isFav: boolean;
54
-
55
-  starRef: { current: null | (Animatable.View & View) };
56
-
57
-  constructor(props: PropsType) {
58
-    super(props);
59
-    this.starRef = React.createRef();
60
-    this.isFav = this.isGroupInFavorites(props.favorites);
61
-  }
62
-
63
-  shouldComponentUpdate(nextProps: PropsType): boolean {
64
-    const { favorites } = this.props;
65
-    const favChanged = favorites.length !== nextProps.favorites.length;
66
-    let newFavState = this.isFav;
67
-    if (favChanged) {
68
-      newFavState = this.isGroupInFavorites(nextProps.favorites);
69
-    }
70
-    const shouldUpdate = this.isFav !== newFavState;
71
-    this.isFav = newFavState;
72
-    return shouldUpdate;
73
-  }
74
-
75
-  onStarPress = () => {
76
-    const { props } = this;
77
-    const ref = this.starRef;
78
-    if (ref.current && ref.current.rubberBand && ref.current.swing) {
79
-      if (this.isFav) {
80
-        ref.current.rubberBand();
81
-      } else {
82
-        ref.current.swing();
83
-      }
84
-    }
85
-    props.onStarPress();
86
-  };
51
+function GroupListItem(props: Props) {
52
+  const theme = useTheme();
87 53
 
88
-  isGroupInFavorites(favorites: Array<PlanexGroupType>): boolean {
89
-    const { item } = this.props;
90
-    for (let i = 0; i < favorites.length; i += 1) {
91
-      if (favorites[i].id === item.id) {
92
-        return true;
93
-      }
94
-    }
95
-    return false;
96
-  }
54
+  const starRef = useRef<Animatable.View & View>(null);
97 55
 
98
-  render() {
99
-    const { props } = this;
100
-    const { colors } = props.theme;
101
-    return (
102
-      <List.Item
103
-        title={getPrettierPlanexGroupName(props.item.name)}
104
-        onPress={props.onPress}
105
-        left={(iconProps) => (
106
-          <List.Icon
107
-            color={iconProps.color}
108
-            style={iconProps.style}
109
-            icon={'chevron-right'}
110
-          />
111
-        )}
112
-        right={(iconProps) => (
113
-          <Animatable.View ref={this.starRef} useNativeDriver>
114
-            <TouchableRipple
115
-              onPress={this.onStarPress}
116
-              style={styles.iconContainer}
117
-            >
118
-              <MaterialCommunityIcons
119
-                size={30}
120
-                style={styles.icon}
121
-                name="star"
122
-                color={this.isFav ? colors.tetrisScore : iconProps.color}
123
-              />
124
-            </TouchableRipple>
125
-          </Animatable.View>
126
-        )}
127
-        style={{
128
-          height: props.height,
129
-          ...styles.item,
130
-        }}
131
-      />
132
-    );
133
-  }
56
+  return (
57
+    <List.Item
58
+      title={getPrettierPlanexGroupName(props.item.name)}
59
+      onPress={props.onPress}
60
+      left={(iconProps) => (
61
+        <List.Icon
62
+          color={iconProps.color}
63
+          style={iconProps.style}
64
+          icon={'chevron-right'}
65
+        />
66
+      )}
67
+      right={(iconProps) => (
68
+        <Animatable.View
69
+          ref={starRef}
70
+          useNativeDriver={true}
71
+          animation={props.isFav ? 'rubberBand' : undefined}
72
+        >
73
+          <TouchableRipple
74
+            onPress={props.onStarPress}
75
+            style={styles.iconContainer}
76
+          >
77
+            <MaterialCommunityIcons
78
+              size={30}
79
+              style={styles.icon}
80
+              name="star"
81
+              color={props.isFav ? theme.colors.tetrisScore : iconProps.color}
82
+            />
83
+          </TouchableRipple>
84
+        </Animatable.View>
85
+      )}
86
+      style={{
87
+        height: props.height,
88
+        ...styles.item,
89
+      }}
90
+    />
91
+  );
134 92
 }
135 93
 
136
-export default withTheme(GroupListItem);
94
+export default React.memo(
95
+  GroupListItem,
96
+  (pp: Props, np: Props) =>
97
+    pp.isFav === np.isFav && pp.onStarPress === np.onStarPress
98
+);

+ 29
- 57
src/screens/Planex/GroupSelectionScreen.tsx View File

@@ -17,7 +17,12 @@
17 17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18 18
  */
19 19
 
20
-import React, { useEffect, useLayoutEffect, useState } from 'react';
20
+import React, {
21
+  useCallback,
22
+  useEffect,
23
+  useLayoutEffect,
24
+  useState,
25
+} from 'react';
21 26
 import { Platform } from 'react-native';
22 27
 import i18n from 'i18n-js';
23 28
 import { Searchbar } from 'react-native-paper';
@@ -142,39 +147,31 @@ function GroupSelectionScreen() {
142 147
    *
143 148
    * @param item The item to add/remove from favorites
144 149
    */
145
-  const onListFavoritePress = (item: PlanexGroupType) => {
146
-    updateGroupFavorites(item);
147
-  };
148
-
149
-  /**
150
-   * Checks if the given group is in the favorites list
151
-   *
152
-   * @param group The group to check
153
-   * @returns {boolean}
154
-   */
155
-  const isGroupInFavorites = (group: PlanexGroupType): boolean => {
156
-    let isFav = false;
157
-    favoriteGroups.forEach((favGroup: PlanexGroupType) => {
158
-      if (group.id === favGroup.id) {
159
-        isFav = true;
150
+  const onListFavoritePress = useCallback(
151
+    (group: PlanexGroupType) => {
152
+      const removeGroupFromFavorites = (g: PlanexGroupType) => {
153
+        setFavoriteGroups(favoriteGroups.filter((f) => f.id !== g.id));
154
+      };
155
+
156
+      const addGroupToFavorites = (g: PlanexGroupType) => {
157
+        setFavoriteGroups([...favoriteGroups, g].sort(sortName));
158
+      };
159
+
160
+      if (favoriteGroups.some((f) => f.id === group.id)) {
161
+        removeGroupFromFavorites(group);
162
+      } else {
163
+        addGroupToFavorites(group);
160 164
       }
161
-    });
162
-    return isFav;
163
-  };
165
+    },
166
+    [favoriteGroups]
167
+  );
164 168
 
165
-  /**
166
-   * Adds or removes the given group to the favorites list, depending on whether it is already in it or not.
167
-   * Favorites are then saved in user preferences
168
-   *
169
-   * @param group The group to add/remove to favorites
170
-   */
171
-  const updateGroupFavorites = (group: PlanexGroupType) => {
172
-    if (isGroupInFavorites(group)) {
173
-      removeGroupFromFavorites(group);
174
-    } else {
175
-      addGroupToFavorites(group);
176
-    }
177
-  };
169
+  useEffect(() => {
170
+    AsyncStorageManager.set(
171
+      AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
172
+      favoriteGroups
173
+    );
174
+  }, [favoriteGroups]);
178 175
 
179 176
   /**
180 177
    * Generates the dataset to be used in the FlatList.
@@ -220,31 +217,6 @@ function GroupSelectionScreen() {
220 217
     return data;
221 218
   };
222 219
 
223
-  /**
224
-   * Removes the given group from the favorites
225
-   *
226
-   * @param group The group to remove from the array
227
-   */
228
-  const removeGroupFromFavorites = (group: PlanexGroupType) => {
229
-    setFavoriteGroups(favoriteGroups.filter((g) => g.id !== group.id));
230
-  };
231
-
232
-  useEffect(() => {
233
-    AsyncStorageManager.set(
234
-      AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
235
-      favoriteGroups
236
-    );
237
-  }, [favoriteGroups]);
238
-
239
-  /**
240
-   * Adds the given group to favorites
241
-   *
242
-   * @param group The group to add to the array
243
-   */
244
-  const addGroupToFavorites = (group: PlanexGroupType) => {
245
-    setFavoriteGroups([...favoriteGroups, group].sort(sortName));
246
-  };
247
-
248 220
   return (
249 221
     <WebSectionList
250 222
       request={() => readData<PlanexGroupsType>(Urls.planex.groups)}

Loading…
Cancel
Save