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

React时间块应用localStorage刷新后数据丢失问题求助

时间块应用localStorage数据刷新后丢失问题解决

问题根源

组件挂载时,依赖timeBlocks的第二个useEffect会优先执行,将初始空数组写入localStorage,直接覆盖了之前保存的有效数据。后续用于读取数据的第一个useEffect只能拿到被覆盖后的空值,最终导致刷新页面后数据全部丢失。

解决方案

将localStorage的读取逻辑移到useState的初始化函数中,移除用于初始化的第一个useEffect。这样组件首次挂载时会直接从localStorage加载数据作为初始状态,避免初始阶段写入空数组覆盖已有数据。

修改后的完整代码

import { useState, useEffect } from "react";

const timeSlots = [
  "6:00 AM", "7:00 AM", "8:00 AM", "9:00 AM", "10:00 AM", "11:00 AM", "12:00 PM", "1:00 PM", 
  "2:00 PM", "3:00 PM", "4:00 PM", "5:00 PM", "6:00 PM", "7:00 PM", "8:00 PM", "9:00 PM", "10:00 PM"
];

const TimeGrid = () => {
  // 直接在useState初始化时读取localStorage,仅执行一次
  const [timeBlocks, setTimeBlocks] = useState<{ time: string; duration: number; label: string }[]>(() => {
    const storedBlocks = localStorage.getItem("timeBlocks");
    return storedBlocks ? JSON.parse(storedBlocks) : [];
  });
  const [draggedBlock, setDraggedBlock] = useState<number | null>(null);

  // 仅在timeBlocks实际变化时写入localStorage
  useEffect(() => {
    localStorage.setItem("timeBlocks", JSON.stringify(timeBlocks));
  }, [timeBlocks]);

  const handleAddBlock = (time: string) => {
    const label = prompt("Enter a label for this time block:", "New Block");
    if (!label) return;

    const conflict = timeBlocks.some((block) => block.time === time);
    if (conflict) {
      alert("This time slot is already occupied. Please edit the existing block or choose another slot.");
      return;
    }

    const newBlock = { time, duration: 1, label };
    setTimeBlocks((prev) => [...prev, newBlock]);
  };

  const handleDragStart = (index: number) => {
    setDraggedBlock(index);
  };

  const handleDrop = (slot: string) => {
    if (draggedBlock === null) return;

    const conflict = timeBlocks.some((block, idx) => block.time === slot && idx !== draggedBlock);
    if (conflict) {
      alert("This time slot is already occupied. Please choose another slot.");
      return;
    }

    setTimeBlocks((prev) =>
      prev.map((block, idx) =>
        idx === draggedBlock ? { ...block, time: slot } : block
      )
    );
    setDraggedBlock(null);
  };

  const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
  };

  const clearStorage = () => {
    localStorage.removeItem("timeBlocks");
    setTimeBlocks([]);
  };

  return (
    <div className="grid grid-cols-1 gap-4">
      <button
        onClick={clearStorage}
        className="mt-4 bg-red-500 text-white p-2 rounded hover:bg-red-600"
      >
        Clear All Blocks
      </button>

      {timeSlots.map((slot, index) => (
        <div
          key={index}
          onClick={() => handleAddBlock(slot)}
          onDragOver={handleDragOver}
          onDrop={() => handleDrop(slot)}
          className="time-slot bg-gray-100 p-4 border border-gray-300 cursor-pointer"
        >
          <span>{slot}</span>
          {timeBlocks
            .map((block, idx) =>
              block.time === slot ? (
                <div
                  key={idx}
                  draggable
                  onDragStart={() => handleDragStart(idx)}
                  className="mt-2 bg-blue-500 text-white p-2 rounded cursor-grab"
                >
                  <span>{block.label} ({block.duration} hr)</span>
                  <div className="flex mt-1 gap-2">
                    <button
                      onClick={(e) => {
                        e.stopPropagation();
                        const newLabel = prompt("Edit the label:", block.label);
                        if (newLabel) {
                          setTimeBlocks((prev) =>
                            prev.map((b, i) => (i === idx ? { ...b, label: newLabel } : b))
                          );
                        }
                      }}
                      className="text-sm bg-yellow-400 hover:bg-yellow-500 text-black px-2 py-1 rounded"
                    >
                      Edit
                    </button>
                    <button
                      onClick={(e) => {
                        e.stopPropagation();
                        setTimeBlocks((prev) => prev.filter((_, i) => i !== idx));
                      }}
                      className="text-sm bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded"
                    >
                      Delete
                    </button>
                  </div>
                </div>
              ) : null
            )}
        </div>
      ))}
    </div>
  );
};

export default TimeGrid;

逻辑说明

  • useState的初始化函数仅在组件首次挂载时执行一次,直接从localStorage加载保存的数据作为初始状态,彻底避免了初始空数组覆盖已有数据的问题。
  • 第二个useEffect仅在timeBlocks发生实际修改时触发写入操作,确保用户添加、编辑、拖拽后的时间块数据能正确持久化到localStorage。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 18:15:17