iT邦幫忙

2025 iThome 鐵人賽

DAY 24
0
生成式 AI

30天用React打造AI工具箱系列 第 24

30天用React打造AI工具箱 Day24

  • 分享至 

  • xImage
  •  

30天用React打造AI工具箱 Day24

筆記管理工具升級版

今天繼續強化昨天完成的筆記管理工具,主要目標是讓使用者在開發模式下也能穩定保存資料,即使 F5 或修改程式碼重新載入,也不會讓筆記消失。

今日目標

  • 解決「頁面重整筆記消失」的問題。
  • 優化 localStorage 的使用方式,避免被空陣列覆蓋。
  • 確保搜尋功能只比對「標題」與「標籤」,而不搜尋內容。

方法

  1. 防止localStorage被覆蓋
    在 useEffect 中加入條件判斷
if (loaded && notes.length > 0) {
  localStorage.setItem("notes", JSON.stringify(notes))
}

這樣可以防止組件重新渲染時被清空。

  1. 加載旗標 loaded
    首次載入時先從 localStorage 讀取資料,
    並設定 loaded 為 true,之後才允許自動儲存。
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>
  )
}

上一篇
30天用React打造AI工具箱 Day23
系列文
30天用React打造AI工具箱24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言