自定义 React Native 列表组件未正确处理分页
Custom React Native list component is not handling pagination correctly
我有一个名为 TableList
的自定义列表组件。当用户单击箭头时,它应该递增到下一个数据集。但是,当第一次单击任一方向时,组件只会更新页码,不会更新数据。
例如:在第一页时,点击next
递增到第二页,但数据保持不变。再次单击,增加页码和数据。它将继续正确分页,直到用户开始单击 previous
。然后它会在第一次点击时给出与之前相同的行为,并在后续点击中正常继续。
主要分页功能:
function changePage(direction) {
if (
(currentPage === 0 && direction === 'previous') ||
currentPage * pageSize > data
) {
return null;
}
if (direction === 'previous' && currentPage > 0) {
const newPage = currentPage - 1;
setDataSlice(data.slice(newPage * pageSize, currentPage * pageSize));
setCurrentPage(newPage);
}
if (direction === 'next') {
const newPage = currentPage + 1;
setDataSlice(data.slice(currentPage * pageSize, newPage * pageSize));
setCurrentPage(newPage);
}
}
完整代码:
import {faArrowRight, faArrowLeft} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome';
import {func, number, string, shape} from 'prop-types';
import React, {useState} from 'react';
import {View, Text, FlatList} from 'react-native';
import EtherButton from '../components/EtherButton';
import {useTheme} from '../context/ThemeContext';
PageMenu.propTypes = {
onNext: func.isRequired,
onPrev: func.isRequired,
page: number.isRequired,
};
function PageMenu({onNext, onPrev, page}) {
const {style, values} = useTheme(getThemedStyles);
return (
<View style={style.pageMenuContainer}>
<EtherButton style={style.arrowButton} onPress={onPrev}>
<FontAwesomeIcon icon={faArrowLeft} color={values.BGSECOND} size={30} />
</EtherButton>
<View>
<Text style={style.counter}>{page}</Text>
</View>
<EtherButton style={style.arrowButton} onPress={onNext}>
<FontAwesomeIcon
icon={faArrowRight}
color={values.BGSECOND}
size={30}
/>
</EtherButton>
</View>
);
}
export default function TableList({
data,
pageSize,
style: overrides,
pageView = true,
}) {
const {style} = useTheme(getThemedStyles);
const [loadCount, setLoadCount] = useState(pageSize);
const [currentPage, setCurrentPage] = useState(0);
const [dataSlice, setDataSlice] = useState(data.slice(0, pageSize));
ListItem.proptypes = {
itemData: shape({
date: string.isRequired,
name: string.isRequired,
orderNumber: string.isRequired,
total: number.isRequired,
}),
};
function ListItem({orderData}) {
const [mouseHover, setMouseHover] = useState(false);
const textHighlight = [
style.valueText,
mouseHover ? style.textHighlighted : null,
];
return (
<View
onMouseOver={() => setMouseHover(true)}
onMouseLeave={() => setMouseHover(false)}
style={[style.listRow, mouseHover ? style.rowHighlighted : null]}
>
<View style={style.amountCell}>
<Text style={textHighlight}>{orderData.total} </Text>
</View>
<View style={style.nameCell}>
<Text style={textHighlight}>{orderData.name}</Text>
</View>
<View style={style.tokenCell}>
<Text style={textHighlight}>{orderData.orderNumber}</Text>
</View>
<View style={style.dateCell}>
<Text style={textHighlight}>
{new Date(orderData.date).toLocaleString()}
</Text>
</View>
</View>
);
}
function loadMore() {
const newCount = loadCount + pageSize;
setLoadCount(newCount);
setDataSlice(data.slice(0, newCount));
}
function changePage(direction) {
if (
(currentPage === 0 && direction === 'previous') ||
currentPage * pageSize > data
) {
return null;
}
if (direction === 'previous' && currentPage > 0) {
const newPage = currentPage - 1;
setDataSlice(data.slice(newPage * pageSize, currentPage * pageSize));
setCurrentPage(newPage);
}
if (direction === 'next') {
const newPage = currentPage + 1;
setDataSlice(data.slice(currentPage * pageSize, newPage * pageSize));
setCurrentPage(newPage);
}
}
return (
<View style={[style.mainContainer, overrides]}>
<View style={style.topRow}>
<View style={style.amountCell}>
<Text style={style.headerText}>Price</Text>
</View>
<View style={style.nameCell}>
<Text style={style.headerText}>Description</Text>
</View>
<View style={style.tokenCell}>
<Text style={style.headerText}>Order ID</Text>
</View>
<View style={style.dateCell}>
<Text style={style.headerText}>Date</Text>
</View>
</View>
<FlatList
data={dataSlice}
key={dataSlice}
renderItem={({item}) => <ListItem orderData={item} />}
keyExtractor={(item) => item.orderNumber}
style={style.flatList}
/>
<Text style={style.timeZone}>
Time shown in {new Date().toString().match(/([A-Z]+[-][0-9]+.*)/)[0]}
</Text>
<View style={style.bottomBar}>
{pageView ? (
<PageMenu
onPrev={() => changePage('previous')}
onNext={() => changePage('next')}
page={currentPage + 1}
/>
) : (
<EtherButton onPress={loadMore} style={style.loadMoreButton}>
<Text style={style.buttonText}>Load More</Text>
</EtherButton>
)}
</View>
</View>
);
}
const getThemedStyles = (theme, fontSize) => ({
mainContainer: {
backgroundColor: theme.BGFIRST,
borderColor: theme.FIRST,
borderWidth: 2,
borderRadius: 5,
overflowX: 'auto',
},
topRow: {
backgroundColor: theme.SECOND,
height: 40,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
borderTopLeftRadius: 2,
borderTopRightRadius: 2,
},
headerText: {
fontFamily: 'NotoSans_Regular',
fontSize: fontSize.body,
color: theme.LIGHT,
alignSelf: 'center',
},
flatList: {
paddingVertical: 20,
},
pageMenuContainer: {
flexDirection: 'row',
alignItems: 'center',
},
counter: {
fontFamily: 'NotoSans_Regular',
fontSize: fontSize.body,
backgroundColor: theme.BGSECOND,
color: theme.DARK,
padding: 5,
borderRadius: 5,
borderColor: theme.FIRST,
borderWidth: 2,
},
arrowButton: {
borderColor: theme.SECOND,
},
bottomBar: {
alignItems: 'center',
backgroundColor: theme.SECOND,
},
loadMoreButton: {
justifyContent: 'center',
alignItems: 'center',
height: 40,
marginVertical: 5,
width: '15%',
borderRadius: 5,
borderColor: theme.FIRST,
borderWidth: 2,
backgroundColor: theme.BGSECOND,
},
loadMoreText: {
fontFamily: 'NotoSans_Regular',
fontSize: fontSize.subheader,
},
buttonText: {
fontFamily: 'NotoSans_Bold',
fontSize: fontSize.legal,
color: theme.FIRST,
textAlign: 'center',
},
// Table
listRow: {
alignItems: 'center',
flexDirection: 'row',
height: 33,
justifyContent: 'space-around',
},
rowHighlighted: {
backgroundColor: theme.SECOND,
},
valueText: {
alignSelf: 'center',
fontFamily: 'NotoSans_Regular',
fontSize: fontSize.legal,
},
textHighlighted: {
color: theme.LIGHT,
},
amountCell: {
width: 80,
minWidth: 60,
},
nameCell: {
width: 220,
minWidth: 210,
},
tokenCell: {
width: 200,
minWidth: 150,
},
dateCell: {
width: 140,
minWidth: 115,
},
//
timeZone: {
alignSelf: 'center',
fontFamily: 'NotoSans_Bold',
fontSize: fontSize.legal,
color: theme.DARK,
marginBottom: 20,
},
});
您能否尝试将 changePage 放入 useCallback 并提供 dataSlice 作为依赖项?在我看来这像是数据泄露。
无关:如果您使用的是 keyExtractor,则不应使用 key。我会从 FlatList 中删除 key 道具。
我很尴尬花了这么长时间才弄明白,但我找到了答案。我基于数据设置了错误的变量。更改 if (direction === "next")
部分中的 setState 修复了它。这是我将其更改为:
setDataSlice(data.slice(newPage * pageSize, (newPage + 1) * pageSize));
我有一个名为 TableList
的自定义列表组件。当用户单击箭头时,它应该递增到下一个数据集。但是,当第一次单击任一方向时,组件只会更新页码,不会更新数据。
例如:在第一页时,点击next
递增到第二页,但数据保持不变。再次单击,增加页码和数据。它将继续正确分页,直到用户开始单击 previous
。然后它会在第一次点击时给出与之前相同的行为,并在后续点击中正常继续。
主要分页功能:
function changePage(direction) {
if (
(currentPage === 0 && direction === 'previous') ||
currentPage * pageSize > data
) {
return null;
}
if (direction === 'previous' && currentPage > 0) {
const newPage = currentPage - 1;
setDataSlice(data.slice(newPage * pageSize, currentPage * pageSize));
setCurrentPage(newPage);
}
if (direction === 'next') {
const newPage = currentPage + 1;
setDataSlice(data.slice(currentPage * pageSize, newPage * pageSize));
setCurrentPage(newPage);
}
}
完整代码:
import {faArrowRight, faArrowLeft} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome';
import {func, number, string, shape} from 'prop-types';
import React, {useState} from 'react';
import {View, Text, FlatList} from 'react-native';
import EtherButton from '../components/EtherButton';
import {useTheme} from '../context/ThemeContext';
PageMenu.propTypes = {
onNext: func.isRequired,
onPrev: func.isRequired,
page: number.isRequired,
};
function PageMenu({onNext, onPrev, page}) {
const {style, values} = useTheme(getThemedStyles);
return (
<View style={style.pageMenuContainer}>
<EtherButton style={style.arrowButton} onPress={onPrev}>
<FontAwesomeIcon icon={faArrowLeft} color={values.BGSECOND} size={30} />
</EtherButton>
<View>
<Text style={style.counter}>{page}</Text>
</View>
<EtherButton style={style.arrowButton} onPress={onNext}>
<FontAwesomeIcon
icon={faArrowRight}
color={values.BGSECOND}
size={30}
/>
</EtherButton>
</View>
);
}
export default function TableList({
data,
pageSize,
style: overrides,
pageView = true,
}) {
const {style} = useTheme(getThemedStyles);
const [loadCount, setLoadCount] = useState(pageSize);
const [currentPage, setCurrentPage] = useState(0);
const [dataSlice, setDataSlice] = useState(data.slice(0, pageSize));
ListItem.proptypes = {
itemData: shape({
date: string.isRequired,
name: string.isRequired,
orderNumber: string.isRequired,
total: number.isRequired,
}),
};
function ListItem({orderData}) {
const [mouseHover, setMouseHover] = useState(false);
const textHighlight = [
style.valueText,
mouseHover ? style.textHighlighted : null,
];
return (
<View
onMouseOver={() => setMouseHover(true)}
onMouseLeave={() => setMouseHover(false)}
style={[style.listRow, mouseHover ? style.rowHighlighted : null]}
>
<View style={style.amountCell}>
<Text style={textHighlight}>{orderData.total} </Text>
</View>
<View style={style.nameCell}>
<Text style={textHighlight}>{orderData.name}</Text>
</View>
<View style={style.tokenCell}>
<Text style={textHighlight}>{orderData.orderNumber}</Text>
</View>
<View style={style.dateCell}>
<Text style={textHighlight}>
{new Date(orderData.date).toLocaleString()}
</Text>
</View>
</View>
);
}
function loadMore() {
const newCount = loadCount + pageSize;
setLoadCount(newCount);
setDataSlice(data.slice(0, newCount));
}
function changePage(direction) {
if (
(currentPage === 0 && direction === 'previous') ||
currentPage * pageSize > data
) {
return null;
}
if (direction === 'previous' && currentPage > 0) {
const newPage = currentPage - 1;
setDataSlice(data.slice(newPage * pageSize, currentPage * pageSize));
setCurrentPage(newPage);
}
if (direction === 'next') {
const newPage = currentPage + 1;
setDataSlice(data.slice(currentPage * pageSize, newPage * pageSize));
setCurrentPage(newPage);
}
}
return (
<View style={[style.mainContainer, overrides]}>
<View style={style.topRow}>
<View style={style.amountCell}>
<Text style={style.headerText}>Price</Text>
</View>
<View style={style.nameCell}>
<Text style={style.headerText}>Description</Text>
</View>
<View style={style.tokenCell}>
<Text style={style.headerText}>Order ID</Text>
</View>
<View style={style.dateCell}>
<Text style={style.headerText}>Date</Text>
</View>
</View>
<FlatList
data={dataSlice}
key={dataSlice}
renderItem={({item}) => <ListItem orderData={item} />}
keyExtractor={(item) => item.orderNumber}
style={style.flatList}
/>
<Text style={style.timeZone}>
Time shown in {new Date().toString().match(/([A-Z]+[-][0-9]+.*)/)[0]}
</Text>
<View style={style.bottomBar}>
{pageView ? (
<PageMenu
onPrev={() => changePage('previous')}
onNext={() => changePage('next')}
page={currentPage + 1}
/>
) : (
<EtherButton onPress={loadMore} style={style.loadMoreButton}>
<Text style={style.buttonText}>Load More</Text>
</EtherButton>
)}
</View>
</View>
);
}
const getThemedStyles = (theme, fontSize) => ({
mainContainer: {
backgroundColor: theme.BGFIRST,
borderColor: theme.FIRST,
borderWidth: 2,
borderRadius: 5,
overflowX: 'auto',
},
topRow: {
backgroundColor: theme.SECOND,
height: 40,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
borderTopLeftRadius: 2,
borderTopRightRadius: 2,
},
headerText: {
fontFamily: 'NotoSans_Regular',
fontSize: fontSize.body,
color: theme.LIGHT,
alignSelf: 'center',
},
flatList: {
paddingVertical: 20,
},
pageMenuContainer: {
flexDirection: 'row',
alignItems: 'center',
},
counter: {
fontFamily: 'NotoSans_Regular',
fontSize: fontSize.body,
backgroundColor: theme.BGSECOND,
color: theme.DARK,
padding: 5,
borderRadius: 5,
borderColor: theme.FIRST,
borderWidth: 2,
},
arrowButton: {
borderColor: theme.SECOND,
},
bottomBar: {
alignItems: 'center',
backgroundColor: theme.SECOND,
},
loadMoreButton: {
justifyContent: 'center',
alignItems: 'center',
height: 40,
marginVertical: 5,
width: '15%',
borderRadius: 5,
borderColor: theme.FIRST,
borderWidth: 2,
backgroundColor: theme.BGSECOND,
},
loadMoreText: {
fontFamily: 'NotoSans_Regular',
fontSize: fontSize.subheader,
},
buttonText: {
fontFamily: 'NotoSans_Bold',
fontSize: fontSize.legal,
color: theme.FIRST,
textAlign: 'center',
},
// Table
listRow: {
alignItems: 'center',
flexDirection: 'row',
height: 33,
justifyContent: 'space-around',
},
rowHighlighted: {
backgroundColor: theme.SECOND,
},
valueText: {
alignSelf: 'center',
fontFamily: 'NotoSans_Regular',
fontSize: fontSize.legal,
},
textHighlighted: {
color: theme.LIGHT,
},
amountCell: {
width: 80,
minWidth: 60,
},
nameCell: {
width: 220,
minWidth: 210,
},
tokenCell: {
width: 200,
minWidth: 150,
},
dateCell: {
width: 140,
minWidth: 115,
},
//
timeZone: {
alignSelf: 'center',
fontFamily: 'NotoSans_Bold',
fontSize: fontSize.legal,
color: theme.DARK,
marginBottom: 20,
},
});
您能否尝试将 changePage 放入 useCallback 并提供 dataSlice 作为依赖项?在我看来这像是数据泄露。
无关:如果您使用的是 keyExtractor,则不应使用 key。我会从 FlatList 中删除 key 道具。
我很尴尬花了这么长时间才弄明白,但我找到了答案。我基于数据设置了错误的变量。更改 if (direction === "next")
部分中的 setState 修复了它。这是我将其更改为:
setDataSlice(data.slice(newPage * pageSize, (newPage + 1) * pageSize));