Add small performance improvement to album list
This commit is contained in:
@@ -7,6 +7,7 @@ module.exports = {
|
|||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
'plugin:@typescript-eslint/eslint-recommended',
|
'plugin:@typescript-eslint/eslint-recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
// "plugin:@typescript-eslint/recommended"
|
// "plugin:@typescript-eslint/recommended"
|
||||||
],
|
],
|
||||||
globals: {
|
globals: {
|
||||||
@@ -23,7 +24,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
'react',
|
'react',
|
||||||
'@typescript-eslint'
|
'@typescript-eslint',
|
||||||
|
'react-hooks'
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
indent: [
|
indent: [
|
||||||
|
|||||||
9
index.ts
9
index.ts
@@ -1,3 +1,12 @@
|
|||||||
|
// import React from 'react';
|
||||||
|
|
||||||
|
// // if (process.env.NODE_ENV === 'development') {
|
||||||
|
// // const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
||||||
|
// // whyDidYouRender(React, {
|
||||||
|
// // trackAllPureComponents: true,
|
||||||
|
// // });
|
||||||
|
// // }
|
||||||
|
|
||||||
import 'react-native-gesture-handler';
|
import 'react-native-gesture-handler';
|
||||||
import { AppRegistry } from 'react-native';
|
import { AppRegistry } from 'react-native';
|
||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
|
|||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -1638,6 +1638,14 @@
|
|||||||
"eslint-plugin-react-hooks": "^3.0.0",
|
"eslint-plugin-react-hooks": "^3.0.0",
|
||||||
"eslint-plugin-react-native": "3.8.1",
|
"eslint-plugin-react-native": "3.8.1",
|
||||||
"prettier": "^2.0.2"
|
"prettier": "^2.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint-plugin-react-hooks": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-EjxTHxjLKIBWFgDJdhKKzLh5q+vjTFrqNZX36uIxWS4OfyXe5DawqPj3U5qeJ1ngLwatjzQnmR0Lz0J0YH3kxw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@react-native-community/eslint-plugin": {
|
"@react-native-community/eslint-plugin": {
|
||||||
@@ -3943,9 +3951,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-plugin-react-hooks": {
|
"eslint-plugin-react-hooks": {
|
||||||
"version": "3.0.0",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.5.tgz",
|
||||||
"integrity": "sha512-EjxTHxjLKIBWFgDJdhKKzLh5q+vjTFrqNZX36uIxWS4OfyXe5DawqPj3U5qeJ1ngLwatjzQnmR0Lz0J0YH3kxw==",
|
"integrity": "sha512-3YLSjoArsE2rUwL8li4Yxx1SUg3DQWp+78N3bcJQGWVZckcp+yeQGsap/MSq05+thJk57o+Ww4PtZukXGL02TQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"eslint-plugin-react-native": {
|
"eslint-plugin-react-native": {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"babel-jest": "^24.9.0",
|
"babel-jest": "^24.9.0",
|
||||||
"babel-plugin-module-resolver": "^4.0.0",
|
"babel-plugin-module-resolver": "^4.0.0",
|
||||||
"eslint": "^6.5.1",
|
"eslint": "^6.5.1",
|
||||||
|
"eslint-plugin-react-hooks": "^4.0.5",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"metro-react-native-babel-preset": "^0.58.0",
|
"metro-react-native-babel-preset": "^0.58.0",
|
||||||
"react-test-renderer": "16.11.0",
|
"react-test-renderer": "16.11.0",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import AlbumImage, { AlbumItem } from './components/AlbumImage';
|
|||||||
import { selectAlbumsByAlphabet, SectionedId } from 'store/music/selectors';
|
import { selectAlbumsByAlphabet, SectionedId } from 'store/music/selectors';
|
||||||
import AlphabetScroller from 'components/AlphabetScroller';
|
import AlphabetScroller from 'components/AlphabetScroller';
|
||||||
import { EntityId } from '@reduxjs/toolkit';
|
import { EntityId } from '@reduxjs/toolkit';
|
||||||
|
import styled from 'styled-components/native';
|
||||||
|
|
||||||
interface VirtualizedItemInfo {
|
interface VirtualizedItemInfo {
|
||||||
section: SectionedId,
|
section: SectionedId,
|
||||||
@@ -33,18 +34,31 @@ type VirtualizedSectionList = { _subExtractor: (index: number) => VirtualizedIte
|
|||||||
|
|
||||||
function generateSection({ section }: { section: SectionedId }) {
|
function generateSection({ section }: { section: SectionedId }) {
|
||||||
return (
|
return (
|
||||||
<SectionHeading label={section.label} />
|
<SectionHeading label={section.label} key={section.label} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SectionHeading extends PureComponent<{ label: string }> {
|
const SectionContainer = styled.View`
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border-bottom-color: #eee;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
height: 50px;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SectionText = styled.Text`
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
`;
|
||||||
|
|
||||||
|
class SectionHeading extends PureComponent<{ label: string }> {
|
||||||
render() {
|
render() {
|
||||||
const { label } = this.props;
|
const { label } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ backgroundColor: '#f6f6f6', borderBottomColor: '#eee', borderBottomWidth: 1, height: 50, justifyContent: 'center' }}>
|
<SectionContainer>
|
||||||
<Text style={{ fontSize: 24, fontWeight: 'bold'}}>{label}</Text>
|
<SectionText>{label}</SectionText>
|
||||||
</View>
|
</SectionContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,6 +71,10 @@ interface GeneratedAlbumItemProps {
|
|||||||
onPress: (id: string) => void;
|
onPress: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HalfOpacity = styled.Text`
|
||||||
|
opacity: 0.5;
|
||||||
|
`;
|
||||||
|
|
||||||
class GeneratedAlbumItem extends PureComponent<GeneratedAlbumItemProps> {
|
class GeneratedAlbumItem extends PureComponent<GeneratedAlbumItemProps> {
|
||||||
render() {
|
render() {
|
||||||
const { id, imageUrl, name, artist, onPress } = this.props;
|
const { id, imageUrl, name, artist, onPress } = this.props;
|
||||||
@@ -66,25 +84,13 @@ class GeneratedAlbumItem extends PureComponent<GeneratedAlbumItemProps> {
|
|||||||
<AlbumItem>
|
<AlbumItem>
|
||||||
<AlbumImage source={{ uri: imageUrl }} />
|
<AlbumImage source={{ uri: imageUrl }} />
|
||||||
<Text numberOfLines={1}>{name}</Text>
|
<Text numberOfLines={1}>{name}</Text>
|
||||||
<Text numberOfLines={1} style={{ opacity: 0.5 }}>{artist}</Text>
|
<HalfOpacity numberOfLines={1}>{artist}</HalfOpacity>
|
||||||
</AlbumItem>
|
</AlbumItem>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const getItemLayout: any = sectionListGetItemLayout({
|
|
||||||
// getItemHeight: (rowData, sectionIndex, rowIndex) => {
|
|
||||||
// console.log(sectionIndex, rowIndex, rowData);
|
|
||||||
// if (sectionIndex === 0) { return 0; }
|
|
||||||
// else if (rowIndex % 2 > 0) { return 0; }
|
|
||||||
// return 220;
|
|
||||||
// },
|
|
||||||
// getSectionHeaderHeight: () => 50,
|
|
||||||
// // getSeparatorHeight: () => 1 / PixelRatio.get(),
|
|
||||||
// // listHeaderHeight: 0,
|
|
||||||
// });
|
|
||||||
|
|
||||||
const Albums: React.FC = () => {
|
const Albums: React.FC = () => {
|
||||||
// Retrieve data from store
|
// Retrieve data from store
|
||||||
const { entities: albums } = useTypedSelector((state) => state.music.albums);
|
const { entities: albums } = useTypedSelector((state) => state.music.albums);
|
||||||
@@ -120,6 +126,7 @@ const Albums: React.FC = () => {
|
|||||||
// current item.
|
// current item.
|
||||||
const previousRows = data?.filter((row, i) => i < sectionIndex)
|
const previousRows = data?.filter((row, i) => i < sectionIndex)
|
||||||
.reduce((sum, row) => sum + Math.ceil(row.data.length / 2), 0) || 0;
|
.reduce((sum, row) => sum + Math.ceil(row.data.length / 2), 0) || 0;
|
||||||
|
|
||||||
|
|
||||||
// We must also calcuate the offset, total distance from the top of the
|
// We must also calcuate the offset, total distance from the top of the
|
||||||
// screen. First off, we'll account for each sectionIndex that is shown up
|
// screen. First off, we'll account for each sectionIndex that is shown up
|
||||||
@@ -130,8 +137,6 @@ const Albums: React.FC = () => {
|
|||||||
const itemOffset = 220 * (previousRows + currentRows);
|
const itemOffset = 220 * (previousRows + currentRows);
|
||||||
const offset = headingOffset + itemOffset;
|
const offset = headingOffset + itemOffset;
|
||||||
|
|
||||||
// console.log(index, sectionIndex, itemIndex, previousRows, currentRows, offset);
|
|
||||||
|
|
||||||
return { index, length, offset };
|
return { index, length, offset };
|
||||||
}, [listRef]);
|
}, [listRef]);
|
||||||
|
|
||||||
@@ -141,14 +146,14 @@ const Albums: React.FC = () => {
|
|||||||
const selectLetter = useCallback((sectionIndex: number) => {
|
const selectLetter = useCallback((sectionIndex: number) => {
|
||||||
listRef.current?.scrollToLocation({ sectionIndex, itemIndex: 0, animated: false, });
|
listRef.current?.scrollToLocation({ sectionIndex, itemIndex: 0, animated: false, });
|
||||||
}, [listRef]);
|
}, [listRef]);
|
||||||
const generateItem = ({ item, index, section }: { item: EntityId, index: number, section: SectionedId }) => {
|
const generateItem = useCallback(({ item, index, section }: { item: EntityId, index: number, section: SectionedId }) => {
|
||||||
if (index % 2 === 1) {
|
if (index % 2 === 1) {
|
||||||
return null;
|
return <View key={item} />;
|
||||||
}
|
}
|
||||||
const nextItem = section.data[index + 1];
|
const nextItem = section.data[index + 1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flexDirection: 'row' }}>
|
<View style={{ flexDirection: 'row' }} key={item}>
|
||||||
<GeneratedAlbumItem
|
<GeneratedAlbumItem
|
||||||
id={item}
|
id={item}
|
||||||
imageUrl={getImage(item as string)}
|
imageUrl={getImage(item as string)}
|
||||||
@@ -167,7 +172,7 @@ const Albums: React.FC = () => {
|
|||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
}, [albums, getImage, selectAlbum]);
|
||||||
|
|
||||||
// Retrieve data on mount
|
// Retrieve data on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -175,7 +180,7 @@ const Albums: React.FC = () => {
|
|||||||
if (!lastRefreshed || differenceInDays(lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
|
if (!lastRefreshed || differenceInDays(lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
|
||||||
retrieveData();
|
retrieveData();
|
||||||
}
|
}
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
@@ -186,8 +191,8 @@ const Albums: React.FC = () => {
|
|||||||
refreshing={isLoading}
|
refreshing={isLoading}
|
||||||
onRefresh={retrieveData}
|
onRefresh={retrieveData}
|
||||||
getItemLayout={getItemLayout}
|
getItemLayout={getItemLayout}
|
||||||
keyExtractor={(d) => d as string}
|
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
|
keyExtractor={(item, index) => `${item}_${index}`}
|
||||||
onScrollToIndexFailed={console.log}
|
onScrollToIndexFailed={console.log}
|
||||||
renderSectionHeader={generateSection}
|
renderSectionHeader={generateSection}
|
||||||
renderItem={generateItem}
|
renderItem={generateItem}
|
||||||
|
|||||||
Reference in New Issue
Block a user