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

React待办应用Local Storage存储复选框失效及报错问题

解决React待办应用Local Storage持久化的对象渲染错误

这个错误的核心原因很明确:你把React组件元素直接存在了state的toDo数组里,然后把整个数组序列化存到Local Storage。当你从Local Storage取出来的时候,原来的React元素已经被JSON转换成了普通的JavaScript对象(就是错误提示里的{key, ref, props, _owner, _store}),React根本没法把这个对象当作子元素渲染,所以就抛出了这个错误。

正确的思路:存数据,而非组件

React state里应该存储的是纯数据(比如字符串、数字、普通对象),而不是React组件本身。组件是用来渲染数据的,数据才是需要持久化的内容。我们需要重构你的代码,把任务的状态数据存在state里,渲染时再根据数据生成组件。

修改后的完整代码

import React, { useState, useEffect } from "react";
import { Delete, Refresh, Add } from "../components/Actions";
import { Header } from "../components/Header";
import { PriorityLists } from "../components/PriorityLists";
import { v4 as uuidv4 } from 'uuid';

function Task() {
  // 初始化时从Local Storage取纯数据,而不是带组件的对象
  const [toDo, setToDo] = useState(() => {
    const storedTasks = localStorage.getItem('tasks');
    return storedTasks ? JSON.parse(storedTasks) : [];
  });
  const [idsToRefresh, setIdsToRefresh] = useState([]);
  const [filter_items, setFilterItems] = useState(false);

  // 当toDo数据变化时,持久化到Local Storage
  useEffect(() => {
    localStorage.setItem('tasks', JSON.stringify(toDo));
  }, [toDo]);

  // 删除已勾选的任务逻辑
  useEffect(() => {
    if (filter_items) {
      setToDo(prev => prev.filter(item => !item.checked));
      setFilterItems(false); // 重置过滤状态
    }
  }, [filter_items]);

  // 更新任务的勾选状态
  function toggleTaskChecked(id, checked) {
    setToDo(prev => prev.map(task => 
      task._Id === id ? { ...task, checked } : task
    ));
  }

  // 添加新任务:存纯数据,而非组件
  function addToDos() {
    const id = uuidv4();
    // 这里只存任务的基础数据:ID、勾选状态、优先级(如果需要的话)
    setToDo(prev => [...prev, { _Id: id, checked: false }]);
    setIdsToRefresh(prev => [...prev, id]);
  }

  // 刷新(删除刚添加的任务)
  function refresh() {
    setToDo(prev => prev.filter(item => !idsToRefresh.includes(item._Id)));
    setIdsToRefresh([]); // 清空刷新ID列表
  }

  return (
    <div className="main-content">
      <div className="container-fluid">
        <div className="row underline">
          <div className="col">
            <div className="row">
              <div className="col-3 pt-2">
                <Refresh _refresh={refresh} />
              </div>
              <div className="col-6 text-center">
                <Header header={"Tasks"} />
              </div>
              <div className="col-3 pt-2">
                <button className="float-right">
                  <Delete setFilterItems={setFilterItems} filter={filter_items} />
                </button>
              </div>
            </div>
          </div>
        </div>
        <div className="row">
          <div className="col">
            {toDo.map(item => (
              <div key={item._Id}>
                <ul>
                  <li>
                    {/* 渲染时根据数据生成PriorityLists组件 */}
                    <PriorityLists 
                      id={item._Id} 
                      checked={item.checked} 
                      addIds={toggleTaskChecked} // 注意这里改了函数名,更语义化
                    />
                  </li>
                </ul>
              </div>
            ))}
          </div>
        </div>
        <div className="row">
          <div className="col pr-4">
            <Add addToDos={addToDos} />
          </div>
        </div>
      </div>
    </div>
  )
}

export default Task;

关键修改点说明

  1. state存储纯数据:toDo数组里的每个元素现在是普通对象,只包含_Id和checked状态(如果你的PriorityLists需要其他数据,比如优先级,可以在这里添加对应的字段)。
  2. 渲染时生成组件:在map循环里,我们根据每个任务的数据渲染<PriorityLists>组件,而不是直接渲染存储的组件元素。
  3. 修正状态更新逻辑:把原来的addIds改成toggleTaskChecked,直接更新toDo数组中对应任务的checked状态,这样状态变化会触发Local Storage的持久化。
  4. 优化Local Storage初始化:用函数式初始化useState,避免每次组件渲染都执行JSON.parse。
  5. 简化删除逻辑:删除已勾选任务时,直接过滤toDo数组中checked为true的项,不需要额外维护ids数组。

这样修改后,你存到Local Storage的就是纯JSON数据,不会再出现React元素被序列化的问题,页面跳转后数据也能正确从Local Storage读取并渲染。

内容的提问来源于stack exchange,提问作者Ron

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:39:06