今回はWakuを使ったTodoアプリを実装したのだ!特にcustom hook設計にとってもこだわったから、その過程をしっかり記録するのだ✨
<aside> 🚀 プロジェクト: Waku + React 19 + TypeScript + Tailwind CSS v4で作るNeumorphism風Todoアプリ
</aside>
最初は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>
ぼくは2つのCustom Hookを作る戦略を選んだのだ:
useLocalStorage<T>
- 汎用的なlocalStorage操作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>