🌟
React19のuseActionsStateをTypeScriptで書く
注意!!
2024/06/18現在、まだRC版なので使い方が変わる可能性があることを留意してください。
公式サンプル(JavaScript版)
元記事
// Using <form> Actions and useActionState
function ChangeName({ name, setName }) {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
return null;
},
null,
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
);
}
解説
TypeScriptだと、previousState
の型を、前回の値とエラー時に返す値で同じ型で表現する必要があり、まとめる必要が出てくる。
前回submitボタン押した時のデータが必要ない場合はまとめなくてよい。
公式のサンプルは、previousStateと書かなければ混乱しなかったのでは?とも思う。
(formには別途普通のstateを使うと思うし。。。)
前回の値を使う場合(前回の値とエラー型を保持できる型を指定する場合)
import { useState, useActionState } from 'react'
// コピペで動かせるように適当に関数を設定
const updateName = async (name: string) => `${name}を更新できません`
// エラーだけの型定義だと前回の値が取得できないので、データも一緒に定義する
interface ActionStateType {
data: { [key: string]: string }
error: string | null
}
function ChangeName() {
// submit時に値がクリアされてしまうためstateを使うが
// その場合は、formDataの値を使う必要もなくなる気がして正しいのか不明
const [name, setName] = useState('')
const [actionState, submitAction, isPending] = useActionState(
async (
previousActionData: ActionStateType,
formData: FormData,
): Promise<ActionStateType> => {
// 前回submit時にreturnしたデータが入ってくる(初回は初期値)
console.log(previousActionData)
const fname = formData.get('name') as string
const error = await updateName(fname)
if (error) {
return { data: { name: fname }, error }
}
return { data: { name: fname }, error: null }
},
// ここで初期データを指定しておかないと初回のsubmit時に前回の状態が正しく取れない
{ data: { name: "" }, error: null },
)
return (
<form action={submitAction}>
<input
type="text"
name="name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button type="submit" disabled={isPending}>
Update
</button>
{actionState.error && <p>{actionState.error}</p>}
</form>
)
}
前回の値を使わない場合(エラー型のみ指定する場合)
import { useState, useActionState } from 'react'
// コピペで動かせるように適当に関数を設定(ボタンのdisableも見れるようにdelayする)
const updateName = async (name:string) => new Promise((resolve) => {
setTimeout(() => resolve(name), 3000)
})
function ChangeName() {
const [error, submitAction, isPending] = useActionState(
async (
_previousError: string | null,
formData: FormData
): Promise<string | null> => {
const error = await updateName(formData.get("name") as string);
if (error) {
return error;
}
return null;
},
null,
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
);
}
submit前の値を使わないほうが、エラー型のみとなるのでスッキリする。
(スッキリしているのは、stateの記述を割愛したのもあるとは思われる)
ひとこと
React18よりもシンプルに書けるようになったので良いですね^^
Discussion