🎉 ずんだもんの実装振り返り日記なのだ!

今回はWakuを使ったTodoアプリを実装したのだ!特にcustom hook設計にとってもこだわったから、その過程をしっかり記録するのだ✨

<aside> 🚀 プロジェクト: Waku + React 19 + TypeScript + Tailwind CSS v4で作るNeumorphism風Todoアプリ

</aside>

⏰ 実装の流れ(時系列)

🌱 Phase 1: 基本的なTodo機能実装

最初はtodo.mdのタスクに従って、段階的にTodoアプリを実装したのだ。この時点では、TodoAppコンポーネントに全てのlocalStorage処理を直接書いていたのだ。

// Phase 1の時のコード(良くない例)
const [todos, setTodos] = useState<Todo[]>([]);
const [isLoaded, setIsLoaded] = useState(false);

useEffect(() => {
  const savedData = localStorage.getItem(STORAGE_KEY);
  if (savedData) {
    try {
      const parsedData = JSON.parse(savedData);
      const todosWithDates = parsedData.map((todo: any) => ({
        ...todo,
        createdAt: new Date(todo.createdAt),
      }));
      setTodos(todosWithDates);
    } catch (error) {
      console.error('Failed to load todos:', error);
    }
  }
  setIsLoaded(true);
}, []);

useEffect(() => {
  if (isLoaded) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
  }
}, [todos, isLoaded]);

💡 転換点:ユーザーからの提案

「localstorageへのアクセスって今後も別の機能で使いそうだからcustom hookに切り出そうかと思うんだけどどう?」

この瞬間、ぼくの中で**「あ!これは素晴らしいアイデアなのだ!」**って思ったのだ。確かにlocalStorageの処理がTodoAppに散らばっているのは良くないし、再利用性も低いのだ。

<aside> 🧠 【ぼくの設計思想の変化】 単一責任の原則 → コンポーネントはUIロジックに集中すべき 再利用性 → 他のコンポーネントでもlocalStorageを使いたい場合がある 保守性 → localStorage関連のバグは1箇所で直せるように

</aside>

🛠️ Custom Hook設計への深いこだわり

🎯 二層構造の設計決定

ぼくは2つのCustom Hookを作る戦略を選んだのだ:

  1. useLocalStorage<T> - 汎用的なlocalStorage操作
  2. useLocalStorageTodos - Todo専用の特化Hook

<aside> 🤔 なぜ2つに分けたのだ?

汎用Hook = 将来的に他のデータ型でも使える Todo専用Hook = Date型の復元など、Todo特有の処理を担当

これにより、汎用性と特化性の両方を実現できるのだ!

</aside>

🔄 状態更新の流れ(これが重要なのだ!)

ぼくが一番こだわったのは**「isLoadedフラグを使った安全な状態管理」**なのだ。これがないと、初期化時に空のデータでlocalStorageを上書きしてしまう危険があったのだ!

1. コンポーネントマウント
   ↓
2. useEffect実行(初期化)
   ↓
3. localStorage.getItem(STORAGE_KEY)
   ↓
4. データがあれば → JSON.parse & Date復元
   ↓
5. setTodos(復元データ)
   ↓
6. setIsLoaded(true) ← 🔑 重要!これで初期化完了
   ↓
7. 以降のuseEffect(保存用)が動作開始
   ↓
8. todosが更新される度にlocalStorageに保存

<aside> ⚠️ 【重要なポイント】 isLoadedがfalseの間は保存処理をスキップ!

if (isLoaded) { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); }

これにより「初期化中に空配列で上書き」を防げるのだ!

</aside>

💡 isLoadedの重要な発見と活用