📝
React19で追加されたhooks触ってみた(useActionState)
useActionState
基本的には、useReducer
を非同期にしたものに近いが、少しだけ違う。
useActionState
は基本的にform
要素と一緒に使用する。
定義
const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);
引数
fn
返り値のformAction
を実行した際に呼び出される関数。
1つ目の引数は、前回のstate
(初回はinitialState
)を受け取る。
2つ目の引数は、基本的に任意の型を受け取る。FormData
を指定した場合は、form
のAction
属性に直接渡すことができる。
返り値は、state
またはPromise<state>
を返す。
そのため、非同期関数を引数として渡すこともできる。
initialState
初期値として使用する値。
permalink(オプション)
フォームが書き換えの対象とするユニークなページ URL を含んだ文字列。
返り値
state
現在のstate
を返し、初回レンダー時は、initialState
に渡した値が設定される。
formAction
useReducer
のdispatch
関数と同じような関数。
引数として、引数として渡したfn
の第2引数と同じ型を受け取る。
isPending
formAction
が実行中かどうかを返す。
form
要素を使用せずにbutton
要素のonClick
に値を設定して、formAction
を実行すると、formAction
が実行中でも、値がずっとfalse
になる。
例
1. FormData型を使用
const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms));
type User = {
id: number;
name: string;
};
const addUser = async (state: User[], formData: FormData) => {
await sleep(1000);
const id = state.length + 1;
const name = formData.get("name");
const newUser = {
id,
name,
};
return [...state, newUser] as User[];
};
const Component = () => {
const [state, formAction, isPending] = useActionState(addUser, [] as User[]);
return (
<div>
<div>{isPending ? "loading..." : ""}</div>
<ul>
{state.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
<form action={formAction}>
<input type="text" name="name" />
<button disabled={isPending} type="submit">
送信
</button>
</form>
</div>
);
};
2. 任意の型(FormData以外)を使用
const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms));
type User = {
id: number;
name: string;
};
const addUser = async (prev: User[], userName: string) => {
await sleep(1000);
const id = prev.length + 1;
const newUser = {
id,
name: userName,
};
return [...prev, newUser];
};
const Component = () => {
const [state, formAction, isPending] = useActionState(addUser, [] as User[]);
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
formAction(inputRef.current?.value ?? "");
};
return (
<div>
<div>{isPending ? "loading..." : ""}</div>
<ul>
{state.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
<form action={handleClick}>
<input type="text" name="name" ref={inputRef} />
<button disabled={isPending} type="submit">
送信
</button>
</form>
</div>
);
};
感想
以前までは、以下のように自分で呼び出し中などをuseState
で管理する必要があった。
useActionState
が追加されたことで、form
要素を使用する必要はあるが、呼び出し中などの管理を自分で行う必要がなく、かなり便利なhooksだと感じた。
const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms));
type User = {
id: number;
name: string;
};
const addUser = async (prev: User[], userName: string) => {
await sleep(1000);
const id = prev.length + 1;
const newUser = {
id,
name: userName,
};
return [...prev, newUser];
};
const Component = () => {
const [isPending, setIsPending] = useState(false);
const [state, setState] = useState([] as User[]);
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = async () => {
setIsPending(true);
const users = await addUser(state, inputRef.current?.value ?? "");
setState(users);
setIsPending(false);
};
return (
<div>
<div>{isPending ? "loading..." : ""}</div>
<ul>
{state.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
<input type="text" name="name" ref={inputRef} />
<button disabled={isPending} type="button" onClick={handleClick}>
送信
</button>
</div>
);
};
参考
Discussion