You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 06:16:08