このコースはReact 19で書いてきましたが、19で入った新しい書き方にはまだ触れていませんでした。ここでまとめます。
急いで覚える必要はありません。(既存の書き方はそのまま動きます。)ただ、新しい記事やライブラリのコードには出てくるので、読めるようにしておくのがこの回の目的です。
<form action={…}>——フォームに関数を渡す
formsの回ではonSubmitとpreventDefault()を書きました。React 19では、actionに関数を渡せます。
<form action={formAction}>
<input name="name" />
<button type="submit">ずかんに追加</button>
</form>3つ、自動でやってくれます。
preventDefault()が要らない(Reactが止める)- 入力値は
FormDataとして関数に渡る - 成功したらフォームが自動でリセットされる
valueとonChangeのペアも不要になり、<input name="name" />だけで済みます。
useActionState——送信中と結果を持つ
actionに渡す関数を包むと、結果と送信中の状態が手に入ります。
import { useActionState } from 'react'
type ActionState = { error: string | null }
const [state, formAction, isPending] = useActionState<ActionState, FormData>(
async (_prevState, formData) => {
const name = String(formData.get('name') ?? '').trim()
if (name === '') {
return { error: '名前を入れてください' }
}
await saveBird({ name }) // サーバーに送るつもりの処理
return { error: null }
},
{ error: null }, // 初期状態
)返ってくる3つを使います。
| 受け取るもの | 中身 |
|---|---|
state | 関数が返した値(エラーメッセージなど) |
formAction | <form action={…}>に渡すもの |
isPending | 送信中かどうか(自前のstateが不要) |
<form action={formAction}>
<input name="name" />
<button type="submit" disabled={isPending}>
{isPending ? '追加中…' : 'ずかんに追加'}
</button>
{state.error && <p className="text-red-600">{state.error}</p>}
</form>useStateが1つも出てきません。 入力値・エラー・送信中を全部自前で持っていたformsの回と比べると、減った量が分かります。
useOptimistic——待たずに先に見せる
サーバーの返事は速くても200ミリ秒かかります。その間、画面が固まって見えるのを避けるのがuseOptimisticです。
import { useOptimistic, startTransition } from 'react'
const [optimisticBirds, addOptimisticBird] = useOptimistic(
birds,
(current, newBird) => [...current, { ...newBird, sending: true }],
)
function handleAdd(newBird) {
startTransition(async () => {
addOptimisticBird(newBird) // 先に画面へ足す
await saveBird(newBird) // そのあと実際に送る
})
}先に画面へ反映して、あとから本当に送ります。 失敗したらReactが自動で元に戻します(birdsの値に戻る)ので、自分で巻き戻しを書く必要はありません。
useTransition——重い更新で画面を止めない
useTransitionは、急がなくていい更新に印を付ける道具です。
const [isPending, startTransition] = useTransition()
function handleSearch(value) {
setKeyword(value) // 入力欄はすぐ反映(急ぐ)
startTransition(() => {
setDeferredKeyword(value) // 重い一覧の更新は後回しでよい
})
}大量の一覧を絞りこむような処理で、入力の反応を優先しつつ、重い更新は追いかけさせることができます。isPendingで「更新中」の印も出せます。
renderingの回で扱った最適化の、もう一つの引き出しです(useMemoが計算を減らすのに対し、こちらは優先順位を変える)。
use——PromiseやContextを読む
React 19で入ったuseは、フックのルールから外れた特別なものです。
import { use } from 'react'
function BirdName({ birdPromise }) {
const bird = use(birdPromise) // Promiseが解決するまで待つ(Suspenseと組む)
return <h1>{bird.name}</h1>
}- 条件分岐やループの中でも呼べる(他のフックはできない)
- Promiseを渡すと、解決するまで
Suspenseのfallbackが出る - Contextも読める(
use(BirdsContext))
いま何を使うか
| やりたいこと | いまの推奨 |
|---|---|
| 送信するだけのフォーム | <form action>+useActionState |
| 入力のたびに反応するUI | value+onChange(従来どおり) |
| 複雑な検証つきフォーム | React Hook Form+zod(前の回) |
| いいね・お気に入りの即時反映 | useOptimistic |
| 一覧の重い更新で入力が引っかかる | useTransition |
| データ取得 | TanStack Query(またはフレームワーク) |
公式ドキュメントも見てみる
ja.react.dev外部サイトuseActionState – React公式のuseActionState。「フォームアクションと組み合わせる」例が、この回の書き方そのものです
ja.react.dev外部サイトuseOptimistic – ReactuseOptimisticのリファレンス。失敗したときに自動で戻ることも、ここで確認できますこのレッスンのまとめ
<form action={関数}>——preventDefault()不要、FormDataで受け取り、成功で自動リセットuseActionState——[結果, action, 送信中]。自前のisSubmittingが要らなくなるuseOptimistic——待たずに先に反映し、失敗したら自動で戻る- 楽観的な更新はほぼ成功する操作だけ。決済のような場面では使わない
useTransitionは優先順位を変える道具(useMemoは計算を減らす道具)useはフックのルールの外。ただし自前fetchの置きかえではない- 既存の書き方もそのまま動く。読めるようにしておけば十分
次は、Next.jsです。ここで出てきたActionsは、そこでサーバー側とつながります。