如何在React Native中实现不丢失数据的列表筛选功能
解决React Native Todo列表筛选丢失数据问题
问题核心是你在筛选时直接修改了存储全量任务的Todos状态,导致原数据被覆盖,切换筛选条件时无法找回之前的数据。以下是具体修改方案:
关键修改点
- 新增
allTodos状态保存所有任务,作为数据源的唯一真实来源,不随筛选操作改变 - 用
filteredTodos状态存储当前筛选后的结果,用于FlatList渲染 - 所有数据操作(添加、完成、编辑、删除)都基于
allTodos更新,之后自动重新筛选 - 筛选函数仅根据
allTodos生成新的filteredTodos,不再修改原始数据
修改后的完整代码
import React, { useState, useEffect } from 'react'; import { StyleSheet, Text, View, TextInput, TouchableOpacity, FlatList, Alert, Modal, Dimensions } from 'react-native'; import Icon from 'react-native-vector-icons/MaterialIcons'; import AsyncStorage from '@react-native-async-storage/async-storage'; function App() { const [todoInput, setTodoInput] = useState(''); // 保存全量任务数据,不随筛选改变 const [allTodos, setAllTodos] = useState([]); // 保存当前筛选后的任务,用于渲染 const [filteredTodos, setFilteredTodos] = useState([]); const StatusTab = [ { status: 'All', completed: 'All' }, { status: 'Complete', completed: true }, { status: 'Incomplete', completed: false } ]; const [currentStatus, setCurrentStatus] = useState('All'); // 根据当前筛选状态更新filteredTodos const updateFilteredTodos = () => { if (currentStatus !== 'All') { setFilteredTodos(allTodos.filter(item => item.completed === currentStatus)); } else { setFilteredTodos([...allTodos]); } }; const setStatusFilter = (status) => { setCurrentStatus(status); }; const [isRender, setIsRender] = useState(false); const [modalVisible, setModalVisible] = useState(false); const [editText, setEditText] = useState(''); const [editItemId, setEditItemId] = useState(null); useEffect(() => { GetTasks(); }, []); // 筛选状态变化时,重新计算筛选结果 useEffect(() => { updateFilteredTodos(); }, [currentStatus, allTodos]); // 仅保存全量数据到本地 useEffect(() => { SaveTasks(allTodos); }, [allTodos]); const ListItem = ({ Todo }) => { return ( <View style={styles.ListItem}> <View style={{ flex: 1 }}> <Text style={[styles.TaskText, { textDecorationLine: Todo?.completed ? "line-through" : "none" }]}> {Todo?.task} </Text> </View> {!Todo?.completed && ( <TouchableOpacity style={[styles.EditIcon]} onPress={() => completeTodo(Todo?.id)}> <Icon name='done' size={20} color='#FFFFFF' /> </TouchableOpacity> )} <TouchableOpacity style={[styles.EditIcon, { backgroundColor: '#5D76E8' }]} onPress={() => editTodo(Todo)}> <Icon name='edit' size={20} color='#FFFFFF' /> </TouchableOpacity> <TouchableOpacity style={[styles.EditIcon, { backgroundColor: '#D30404' }]} onPress={() => deleteTodo(Todo?.id)}> <Icon name='delete' size={20} color='#FFFFFF' /> </TouchableOpacity> </View> ); }; const SaveTasks = async (todos) => { try { const stringifyTodos = JSON.stringify(todos); await AsyncStorage.setItem('Todos', stringifyTodos); } catch (e) { console.log(e); } }; const GetTasks = async () => { try { const savedTodos = await AsyncStorage.getItem('Todos'); if (savedTodos != null) { const parsedTodos = JSON.parse(savedTodos); setAllTodos(parsedTodos); // 初始化时默认显示全部 setFilteredTodos(parsedTodos); } } catch (error) { console.log(error); } }; const addTodo = () => { if (todoInput.trim() === "") { Alert.alert("Error", "Please Input Task"); return; } const newTodo = { id: Math.random(), task: todoInput.trim(), completed: false, }; // 更新全量数据 setAllTodos([...allTodos, newTodo]); setTodoInput(''); }; const completeTodo = (todoID) => { const updatedTodos = allTodos.map((item) => { if (item.id === todoID) { return { ...item, completed: true }; } return item; }); setAllTodos(updatedTodos); }; const editTodo = (item) => { setModalVisible(true); setEditText(item.task); setEditItemId(item.id); }; const handleEditItem = () => { if (!editText.trim()) return; const updatedTodos = allTodos.map(item => { if (item.id === editItemId) { return { ...item, task: editText.trim() }; } return item; }); setAllTodos(updatedTodos); setIsRender(!isRender); }; const onPressSaveEdit = () => { handleEditItem(); setModalVisible(false); }; const deleteTodo = (todoID) => { Alert.alert("Confirm", "Delete Task?", [ { text: "Yes", onPress: () => { const updatedTodos = allTodos.filter(item => item.id !== todoID); setAllTodos(updatedTodos); } }, { text: "No" } ]); }; return ( <View style={styles.Container}> <View style={styles.Header}> <TextInput style={styles.SearchBar} placeholder='Add Task' value={todoInput} onChangeText={(text) => setTodoInput(text)} /> <TouchableOpacity onPress={addTodo}> <View style={styles.IconContainer}> <Icon name="add" color='#FFFFFF' size={30} /> </View> </TouchableOpacity> </View> <View style={styles.TaskList}> <FlatList showsVerticalScrollIndicator={false} contentContainerStyle={{ padding: 20, paddingBottom: 100 }} data={filteredTodos} keyExtractor={(item) => item.id.toString()} renderItem={({ item }) => <ListItem Todo={item} />} extraData={isRender} /> <Modal animationType='fade' visible={modalVisible} onRequestClose={() => setModalVisible(false)} > <View style={styles.ModalView}> <Text style={styles.ModalText}>Change Task:</Text> <TextInput style={styles.ModalInput} onChangeText={(text) => setEditText(text)} value={editText} editable={true} multiline={false} maxLength={200} /> <Text style={styles.ModalText}>Task Status:</Text> <TouchableOpacity style={styles.FilterTab} onPress={() => { const updatedTodos = allTodos.map(item => { if (item.id === editItemId) return { ...item, completed: true }; return item; }); setAllTodos(updatedTodos); }} > <Text style={styles.TabText}>Complete</Text> </TouchableOpacity> <TouchableOpacity style={styles.FilterTab} onPress={() => { const updatedTodos = allTodos.map(item => { if (item.id === editItemId) return { ...item, completed: false }; return item; }); setAllTodos(updatedTodos); }} > <Text style={styles.TabText}>Incomplete</Text> </TouchableOpacity> <TouchableOpacity onPress={() => onPressSaveEdit()} style={styles.SaveButton}> <Text style={styles.ModalText}>Save</Text> </TouchableOpacity> </View> </Modal> </View> <View style={styles.Footer}> {StatusTab.map(e => ( <TouchableOpacity key={e.status} style={[styles.FilterTab, currentStatus === e.completed && styles.FilterTabActive]} onPress={() => setStatusFilter(e.completed)} > <Text style={[styles.TabText, currentStatus === e.completed && styles.TabTextActive]}>{e.status}</Text> </TouchableOpacity> ))} </View> </View> ); } const styles = StyleSheet.create({ Container: { flex: 1, alignItems: 'center', justifyContent: 'center' }, Header: { flexDirection: 'row', backgroundColor: '#000000', alignItems: "center", justifyContent: 'center', width: '100%' }, SearchBar: { borderColor: "#FFFFFF", backgroundColor: "#DDDDDD", marginHorizontal: 5, width: '40%', height: '80%', borderRadius: 30, paddingHorizontal: 15 }, IconContainer: { height: 50, width: 50, backgroundColor: '#061380', borderRadius: 25, justifyContent: 'center', alignItems: 'center' }, TaskList: { flex: 10, width: '100%', flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-around', alignItems: 'flex-start' }, ListItem: { padding: 20, backgroundColor: '#DEDEDE', flexDirection: 'row', elevation: 12, borderRadius: 7, marginVertical: 10, width: '100%' }, TaskText: { fontWeight: 'bold', fontSize: 15, color: '#000000', }, EditIcon: { height: 25, width: 25, backgroundColor: '#1BCC48', justifyContent: 'center', alignItems: 'center', marginLeft: 5, borderRadius: 3 }, ModalView: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 20 }, ModalText: { fontSize: 25, fontWeight: 'bold', marginVertical: 15, marginLeft: 10 }, ModalInput: { width: '90%', height: 70, borderColor: '#000000', borderWidth: 1, fontSize: 25, paddingHorizontal: 10 }, SaveButton: { backgroundColor: '#3AE3A0', paddingHorizontal: 100, paddingVertical: 10, alignItems: 'center', marginTop: 20, borderRadius: 5 }, Footer: { flexDirection: 'row', alignSelf: 'center', marginBottom: 10, width: '100%', justifyContent: 'space-around' }, FilterTab: { width: Dimensions.get('window').width / 3.5, flexDirection: 'row', borderWidth: 1, borderColor: '#4A4A4A', padding: 10, justifyContent: 'center', borderRadius: 5 }, FilterTabActive: { backgroundColor: '#F87861' }, TabText: { fontSize: 16, }, TabTextActive: { color: '#FFFFFF' }, }); export default App;
额外修复的小问题
- 修正了编辑任务时获取文本的错误(原代码用
item.text,实际是item.task) - 完善了编辑弹窗中修改任务状态的功能,修改后会同步更新全量数据
- 优化了输入框的padding,提升输入体验
- 统一了状态变量命名风格,提升代码可读性
内容的提问来源于stack exchange,提问作者Ignis
相关产品推荐
相关产品推荐

