メインコンテンツまでスキップ

第142章:練習:TODO追加をServer Actionsに置き換える➕✨

今日は「TODO追加」を fetch('/api/todos') でPOSTする方式から、**Server Actions(<form action={...}>)**に置き換えるよ〜!🧁💛 これができると「フォーム送信=サーバーで処理」になって、作り方がスッキリするよ☺️🫶 (Next.js)


🎯 今日のゴール

✅ ボタン押したら Server Action が動く ✅ 追加後に /todos の表示が更新される(revalidatePath) ✅ 送信中はボタンを 無効化&文言変更useFormStatus

revalidatePath は「次に表示するとき新しいデータにしてね!」ってキャッシュを無効化してくれるよ🧊🔁 (Next.js)


🧭 ざっくり構造(図解)✨


🛠️ 手順(この章で作る/直すファイル)

この章では例として /todos ページがある前提で進めるね☺️ (Windows+VSCodeなら、左のエクスプローラーでそのままファイル作ってOK!📁✨)

✅ 1) まず「TODOの保存場所」を1つにまとめる(学習用ミニ保存)

/lib/todos.ts を作るよ🗃️✨(※学習用なので、サーバー再起動で消えるよ〜)

// lib/todos.ts
export type Todo = {
id: string;
title: string;
createdAt: number;
};

const todos: Todo[] = [];

export function getTodos(): Todo[] {
// 新しい順に並べて返す(見やすい✨)
return [...todos].sort((a, b) => b.createdAt - a.createdAt);
}

export function addTodo(title: string): Todo {
const todo: Todo = {
id: crypto.randomUUID(),
title,
createdAt: Date.now(),
};
todos.push(todo);
return todo;
}

✅ 2) Server Action を作る(ここが今回の主役🧑‍🍳✨)

/app/todos/actions.ts を作るよ! ポイントは 'use server' と、最後の revalidatePath('/todos') 🔁

Server Function(Server Action)は サーバーで動く非同期関数だよ〜 ☁️✨ (Next.js)

// app/todos/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { addTodo } from '@/lib/todos';

export type AddTodoState = {
message: string;
};

export async function addTodoAction(
prevState: AddTodoState,
formData: FormData
): Promise<AddTodoState> {
const title = String(formData.get('title') ?? '').trim();

if (!title) {
return { message: '⚠️ タイトルを入れてね!' };
}

addTodo(title);

// 追加後に /todos の表示を更新させる✨
revalidatePath('/todos');

return { message: '✅ 追加できたよ!' };
}

revalidatePath の使い方は公式の関数リファレンスにもあるよ🔁 (Next.js)


✅ 3) フォームを Server Actions 対応に置き換える(useActionState 使用)

ここで「fetchでPOST」してた部分を消して、<form action={...}> にするよ📮✨ useActionState を使うと、Server Actionの返り値(今回だと message)をフォーム側で受け取れるよ🧠✨ (React)

/app/todos/TodoAddForm.tsx を作成(または置き換え)👇

// app/todos/TodoAddForm.tsx
'use client';

import React, { useEffect, useRef } from 'react';
import { useActionState } from 'react';
import { useFormStatus } from 'react-dom';
import { addTodoAction, type AddTodoState } from './actions';

function SubmitButton() {
const { pending } = useFormStatus(); // 送信中かどうか見れる✨ :contentReference[oaicite:5]{index=5}
return (
<button type="submit" disabled={pending}>
{pending ? '追加中…⏳' : '追加する➕'}
</button>
);
}

export default function TodoAddForm() {
const formRef = useRef<HTMLFormElement>(null);

const initialState: AddTodoState = { message: '' };
const [state, action] = useActionState(addTodoAction, initialState);

// 成功したらフォームを空にする✨
useEffect(() => {
if (state.message.startsWith('✅')) {
formRef.current?.reset();
}
}, [state.message]);

return (
<div style={{ display: 'grid', gap: 8, maxWidth: 420 }}>
<form ref={formRef} action={action} style={{ display: 'flex', gap: 8 }}>
<input
name="title"
placeholder="例:レポート提出📄"
required
style={{ flex: 1, padding: 8 }}
/>
<SubmitButton />
</form>

{state.message ? (
<p style={{ margin: 0 }}>{state.message}</p>
) : (
<p style={{ margin: 0, opacity: 0.6 }}>💡 1個追加してみてね!</p>
)}
</div>
);
}

useFormStatus は「フォーム送信の状態(pendingなど)」を取れるフックだよ〜!⏳✨ (React)


✅ 4) /todos ページでフォーム+一覧を表示する

/app/todos/page.tsx を作成(または更新)👇

// app/todos/page.tsx
import { getTodos } from '@/lib/todos';
import TodoAddForm from './TodoAddForm';

export default async function TodosPage() {
const todos = getTodos();

return (
<main style={{ padding: 16, display: 'grid', gap: 16 }}>
<h1 style={{ margin: 0 }}>TODO 📝✨</h1>

<TodoAddForm />

<ul style={{ margin: 0, paddingLeft: 18 }}>
{todos.map((t) => (
<li key={t.id}>{t.title}</li>
))}
</ul>
</main>
);
}

▶️ 動作確認(Windows)🪟✨

VSCode のターミナルで👇

npm run dev

ブラウザで👇にアクセス:

  • http://localhost:3000/todos

「追加する➕」を押して、一覧が増えたら成功!🎉✨


🧨 よくあるハマり(ここ超大事🥺)

  • 'use server' を actions.ts に書き忘れる → Server Actionにならない😭 (Next.js)
  • input の name="title" と、formData.get('title') がズレる → 空扱いになる😇
  • 追加しても一覧が更新されないrevalidatePath('/todos') を忘れてる可能性大🔁 (Next.js)
  • Server Actionは裏でPOSTで呼ばれるので、フォームは基本POST扱いだよ🧾 (Next.js)

✅ できたことまとめ(えらいっ🫶✨)

  • fetch('/api/...')POSTをやめて<form action={...}> に置き換えた📮
  • Server Actionで追加 → revalidatePath で表示更新までできた🔁✨ (Next.js)
  • useFormStatus で送信中UI(追加中…)が作れた⏳💛 (React)