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
相关产品推荐
相关产品推荐

