趣味の開発でremixを利用してwebアプリケーションを作成しています
その際、1ページに複数のformを設置したい場合、actionの中に複数の処理を書かないといけなくなり、ごちゃごちゃしてしまいます😥
単一責任の原則に則り、1action1処理にする方法をメモ
以下の方法でactionごとに処理の記載が可能
- actionのみのrouteを用意
-
fetcher
を使用したformで1のrouteにポスト -
fetcher.data
から戻り値を取得して画面に反映
困りがちな事例
ブログを例に考えてみた場合、記事にコメントを行う際に以下の処理を行うものとします
- 記事に"いいね"をつける
- 記事にコメントを付ける
上記をコードにしてみると、以下のようにaction内でformのtypeによって分岐することになると思います
これにより、actionの中が煩雑になって見づらくなってしまうのが個人的には結構ツラいです🫨
分岐ごとにserviceクラスへ処理を委譲するのもよいと思いますが、actionを複数にすることで対応することも可能でしたので例を後述します
// Article.tsx
export async function action({request}: ActionFunctionArgs) {
const formData = Object.fromEntries(await request.formData().entries());
if (formData.type === "good-form") {
// いいねを行う処理
else if (formData.type === "comment-form") {
// コメントをつける処理
}
...
}
export default function Article() {
const [good, setGood] = useState(false)
return (
// 記事内容の表示コード
...
// いいねをするフォーム
<Form id="good" method="post">
<input name="type" type="hidden" value="good-form"/>
<input name="articleId" type="hidden" value="1"/>
<button type="submit" name="good" value={good ? "true" : "false"}>
いいね
</button>
</Form>
// コメントするフォーム
<Form id="comment" method="post">
<input name="type" type="hidden" value="comment-form"/>
<input name="articleId" type="hidden" value="1"/>
<input name="content" type="text" value="~~~"/>
<button type="submit">送信</button>
</Form>
);
}
対策
1. actionのみのrouteを用意
困りがちな事例に倣ってroutes
の構成を考え、いいねとコメントのrouteを追加します
// 記事のroute
articles.tsx
// いいねのroute
+ good.tsx
// コメントのroute
+ comment.tsx
それぞれ以下のようなactionのみを定義します
// good.tsx
export async function action({request}: ActionFunctionArgs) {
const formData = Object.fromEntries(await request.formData().entries());
// いいねを行う処理
...
}
// comment.tsx
export async function action({request}: ActionFunctionArgs) {
const formData = Object.fromEntries(await request.formData().entries());
// コメントを行う処理
...
}
2. fetcher
を使用したformで1のrouteにポスト
困りがちな事例のコードに戻り、Form
をfetcher(Good|Comment).Form
に変更します
これでページ遷移やURLの書き換えが発生せずに対象のrouteにリクエストが行えます
(通常のFormでのリクエストではuseActionDataでの戻り値取得も不可だったはず、、なのでfetcher使うしかない)
// Article.tsx
- export async function action({request}: ActionFunctionArgs) {
- const formData = Object.fromEntries(await request.formData().entries());
-
- if (formData.type === "good-form") {
- // いいねを行う処理
- else if (formData.type === "comment-form") {
- // コメントをつける処理
- }
- ...
- }
export default function Article() {
+ const fetcherGood = useFetcher();
+ const fetcherComment = useFetcher();
const [good, setGood] = useState(false)
return (
// 記事内容の表示コード
...
// いいねをするフォーム
+ <fetcherGood.Form id="good" method="post" action="/good">
<input name="type" type="hidden" value="good-form"/>
<input name="articleId" type="hidden" value="1"/>
<button type="submit" name="good" value={good ? "true" : "false"}>
いいね
</button>
+ </fetcherGood.Form>
// コメントするフォーム
+ <fetcherComment.Form id="comment" method="post" action="/comment">
<input name="type" type="hidden" value="comment-form"/>
<input name="articleId" type="hidden" value="1"/>
<input name="content" type="text" value="~~~"/>
<button type="submit">送信</button>
+ </fetcherComment.Form>
);
}
3. fetcher.data
から戻り値を取得して画面に反映
さらに上記のコードにuseEffect()
でfetcher.data
を監視して変更時にいいねを反映するなり、コメント一覧を更新するなりで完了です
// Article.tsx
export default function Article() {
const fetcherGood = useFetcher();
const fetcherComment = useFetcher();
const [good, setGood] = useState(false)
+ useEffect(() => {
+ if (fetcherGood.state === "idle" && fetcherGood.data != null) {
+ // いいね戻り値に応じた処理
+ ...
+ }
+ }, [fetcherGood.data]);
+ useEffect(() => {
+ if (fetcherComment.state === "idle" && fetcherComment.data != null) {
+ // コメント戻り値に応じた処理
+ ...
+ }
+ }, [fetcherComment.data]);
return (
// 記事内容の表示コード
...
// いいねをするフォーム
<fetcherGood.Form id="good" method="post" action="/good">
<input name="type" type="hidden" value="good-form"/>
<input name="articleId" type="hidden" value="1"/>
<button type="submit" name="good" value={good ? "true" : "false"}>
いいね
</button>
</fetcherGood.Form>
// コメントするフォーム
<fetcherComment.Form id="comment" method="post" action="/comment">
<input name="type" type="hidden" value="comment-form"/>
<input name="articleId" type="hidden" value="1"/>
<input name="content" type="text" value="~~~"/>
<button type="submit">送信</button>
</fetcherComment.Form>
);
}
さいごに
上記のやり方の問題点としてはパスを使ってしまう点ですが、
パス設計をしっかりやれば問題にはならないかな?と思います
ほかに見通しよく作る方法があればまたメモしてみようと思います