今天繼續強化昨天完成的筆記管理工具,主要目標是讓使用者在開發模式下也能穩定保存資料,即使 F5 或修改程式碼重新載入,也不會讓筆記消失。
if (loaded && notes.length > 0) {
localStorage.setItem("notes", JSON.stringify(notes))
}
這樣可以防止組件重新渲染時被清空。
const [loaded, setLoaded] = useState(false)
useEffect(() => {
const saved = JSON.parse(localStorage.getItem("notes") || "[]")
setNotes(saved)
setLoaded(true)
}, [])
import { useState, useEffect } from "react"
export default function NoteManager() {
const [notes, setNotes] = useState([])
const [title, setTitle] = useState("")
const [content, setContent] = useState("")
const [tag, setTag] = useState("")
const [search, setSearch] = useState("")
const [loaded, setLoaded] = useState(false)
useEffect(() => {
try {
const saved = JSON.parse(localStorage.getItem("notes") || "[]")
if (Array.isArray(saved)) setNotes(saved)
console.log("✅ 已載入筆記")
} catch (err) {
console.error("❌ 載入筆記錯誤:", err)
} finally {
setLoaded(true)
}
}, [])
useEffect(() => {
if (loaded && notes.length > 0) {
localStorage.setItem("notes", JSON.stringify(notes))
console.log("✅ 已儲存筆記")
}
}, [notes, loaded])
const addNote = () => {
if (!title.trim() || !content.trim()) return
const newNote = {
id: Date.now(),
title,
content,
tag: tag || "未分類",
createdAt: new Date().toLocaleString(),
}
setNotes([newNote, ...notes])
setTitle("")
setContent("")
setTag("")
}
const deleteNote = (id) => setNotes(notes.filter((n) => n.id !== id))
const filteredNotes = notes.filter(
(n) =>
n.title.toLowerCase().includes(search.toLowerCase()) ||
n.tag.toLowerCase().includes(search.toLowerCase())
)
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold">🗒️ 筆記管理工具(穩定版)</h2>
<div className="bg-white p-4 rounded shadow space-y-3">
<input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="輸入筆記標題" className="w-full border px-3 py-2 rounded" />
<textarea value={content} onChange={(e) => setContent(e.target.value)} placeholder="輸入筆記內容..." rows="4" className="w-full border px-3 py-2 rounded" />
<input value={tag} onChange={(e) => setTag(e.target.value)} placeholder="輸入標籤(例如:學習、工作)" className="w-full border px-3 py-2 rounded" />
<button onClick={addNote} className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">新增筆記</button>
</div>
<div className="bg-white p-4 rounded shadow">
<input value={search} onChange={(e) => setSearch(e.target.value)} placeholder="搜尋筆記(標題與標籤)" className="w-full border px-3 py-2 rounded" />
</div>
<div className="space-y-3">
{filteredNotes.length === 0 ? (
<p className="text-gray-500">目前沒有符合的筆記。</p>
) : (
filteredNotes.map((note) => (
<div key={note.id} className="bg-white p-4 rounded shadow border-l-4 border-blue-400">
<div className="flex justify-between items-start">
<div>
<h3 className="text-lg font-semibold">{note.title}</h3>
<p className="text-gray-700 whitespace-pre-wrap mt-1">{note.content}</p>
<p className="text-sm text-gray-500 mt-2">
🏷️ {note.tag} | 🕒 {note.createdAt}
</p>
</div>
<button onClick={() => deleteNote(note.id)} className="text-red-600 hover:text-red-800 text-sm">刪除</button>
</div>
</div>
))
)}
</div>
</div>
)
}