🦔
React19のuseActionStateを使ってみた
動作環境
- React:19.0.0
参考
ランニングページです。(サイトの詳細は気が向いたら作ります)
前書き
現在(2024/12/30)大学2年生でインターンにも行ってないガチガチの初心者なのでそこら辺、参考にする人は気にかけてください。
今回はReact + viteで作っています、初期設定(npm create vite@latest)とかは省きます。そこら辺は親切な人が色々挙げているのでみていただければ。
もし、プロのエンジニアの方が見ていらしたら「ここはこんな感じで書いた方がいいよー」とか優しく教えていただけると幸いです。
内容
下記は今回主に解説するコンポーネントの親コンポーネントです。
(useStateは別の記事でまとめるつもりなのでそちらに譲ります。)
interface pageProps {
userName: string;
age: number;
comment: string;
}
const useActionStatePage: React.FC<pageProps> = (props: pageProps) => {
const [formState, setFormState] = useState<pageProps>(props);
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
return setFormState({ ...formState, userName: e.target.value });
};
const handleAgeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
return setFormState({ ...formState, age: parseInt(e.target.value) });
};
const handleCommentChange = (e: React.ChangeEvent<HTMLInputElement>) => {
return setFormState({ ...formState, comment: e.target.value });
};
return (
<ActionForm
formState={formState}
handleAgeChange={handleAgeChange}
handleCommentChange={handleCommentChange}
handleNameChange={handleNameChange}
/>
);
};
export default useActionStatePage;
interfaceのpagePropsで値が書かれていますが、今回はこのコンポーネントの上から下記のデータを渡しています。
const formData = {
userName: "",
age: 0,
comment: "",
};
本題のコンポーネントです。(冗長なためCSSを省いています)
import React, { useActionState } from "react";
import { submitForm } from "../../lib/submit";
interface formState {
userName: string;
age: number;
comment: string;
}
interface formActionProps {
formState: formState;
handleNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleAgeChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleCommentChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
const ActionForm = ({
formState,
handleNameChange,
handleAgeChange,
handleCommentChange,
}: formActionProps) => {
const [message, formAction, isPending] = useActionState(submitForm, null); //重要
return (
<form
action={formAction} //重要!!
name="formData"
id="useActionState"
>
<div className="mb-4">
<label>Name:</label>
<input
type="text"
value={formState.userName}
name="userName"
onChange={(e) => handleNameChange(e)}
/>
</div>
<div className="mb-4">
<label>Age:</label>
<input
type="number"
value={formState.age}
name="age"
onChange={(e) => handleAgeChange(e)}
/>
</div>
<div className="mb-4">
<label>Comment:</label>
<input
type="text"
value={formState.comment}
name="comment"
onChange={(e) => handleCommentChange(e)}
/>
</div>
<div className="mb-4 text-center">
{isPending && <p>Submmitting...</p>}
{message?.state ? (
<p>{message.message}</p>
) : (
<p>{message?.message}</p>
)}
</div>
<div>
<button type="submit">
submit
</button>
</div>
</form>
);
};
export default ActionForm;
実物は先にもあげたようにランニングページをみていただければ...
さて、useActionStateが下記の部分で宣言されています
const [message, formAction, isPending] = useActionState(submitForm, null);
ここでは例としてuseActionState([引数1] , [引数2])として説明します。
- message : useActionStateで扱う変数です。formActionで指定された関数の返り値が格納されます。[引数2]の部分が初期値となるためmessageの初期値はnullです。
- formAction : useActionStateで扱う関数です。この場合では実行された時に[引数1]に設定した関数にmessageの値と<form></form>内のデータが渡されます。
- isPending : この値は処理中か否かを格納しています。簡単にいうとformActionが実行中はtureとなります。逆に実行していなければfalseとなります。
下記がsubmitForm関数です。
"use server";
interface QueryData {
get(key: string): string | null;
}
interface response {
state: boolean;
message: string;
}
interface response {
state: boolean;
message: string;
}
export async function submitForm(
// @ts-ignore
state: response | null,
queryData?: QueryData
): Promise<response> {
const isValidUserName = (userName: string | null | undefined) => {
if (!userName || typeof userName !== "string") {
return false;
}
return true;
};
const isValidAge = (age: number | null | undefined) => {
if (!age || typeof age !== "number") {
return false;
}
return true;
};
const isValidComment = (comment: string | null | undefined) => {
if (!comment || typeof comment !== "string") {
return false;
}
return true;
};
await new Promise((resolve) => setTimeout(resolve, 1000));
const userName = queryData?.get("userName");
const age = queryData?.get("age");
const comment = queryData?.get("comment");
if (!isValidUserName(userName)) {
return {
state: false,
message: "Please enter a valid name.",
};
}
if (!isValidAge(Number(age))) {
return {
state: false,
message: "Please enter a valid age.",
};
}
if (!isValidComment(comment)) {
return {
state: false,
message: "Please enter a valid comment.",
};
}
return {
state: true,
message: "Form submitted successfully.",
};
}
ここの部分に注目しましょう
export async function submitForm(
// @ts-ignore
state: response | null,
queryData?: QueryData
): Promise<response> {
// ~~~
引数についてです。
- state : これはuseActionStateを宣言した時のmessageの値が渡ってきます。この関数の戻り値がmessageの値になるので「一つ前の状態が渡ってくる」と言えばイメージがつきやすいかもしれません。
- queyData : これはformのinputの値が渡ってきます。注意なのですがinputのnameを必ず指定してください。そうしないとquaryData.get("value")で値を取得できません。
- // @ts-ignore : これでstateのTypescriptのコンパイル時のチェックを無視してるのですが、正直ここは良い方法がわかりません。TypeScriptは使用しない値に対するチェックが厳しいのですがuseActionStateはstateを必ず渡してくるのでどう対処するのが正解なんでしょう???プロの方教えてください。
今回の関数ではバリデーションチェックをしてstateとmessageをクライアント側に返しています。戻り値によってクライアント側に表示される値を変えています。
ちなみにbuttonタグ等にもformActionを設定できるみたいです。React19で大きく変わったところだとか、それでは〜
Discussion