エンジニアを目指す初学者に向けて、わかりやすく解説したブログです。
サイトをリニューアルしました

【Remix】actionとは

actionとは

Icon in a page linkaction | Remix

loaderと同様にサーバーサイドで処理される関数で、
GET以外のメソッド(POST、PUT、PATCH、DELETE)で呼び出しが行われたときに実行される関数。

フォームを送信したいときや、バックエンドにデータ送信を行いたい場合に利用する。

フォームから単にPOSTリクエストを送る

Icon in a page linkJSONPlaceholder - Free Fake REST API にPOSTリクエストを送信する処理を実装する。

jsonplaceholderは以下のようにダミーのPOSTメソッドが用意されていて、
叩くとリクエストボディと同じ結果が返ってくるので、POSTの確認はこれを用いて行う。

$ curl -XPOST "https://jsonplaceholder.typicode.com/posts" -H 'Content-Type: application/json' -d '{"title":"hoge"}'
{
  "title": "hoge",
  "id": 101
}

app/routes/posts.tsx

フォームに文字を入力し、jsonplaceholderにPOSTする処理を実装する。

Remixの Formは少し特別で、input要素で入力された要素がaction関数の引数に渡り、action関数が実行される流れとなる。

参考:Icon in a page linkForm | Remix

import { ActionFunctionArgs } from "@remix-run/node";
import { Form, redirect } from "@remix-run/react";

export async function action({ request }: ActionFunctionArgs) {
  // フォームに入力されたデータを取得
  const body = await request.formData();
  const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: body.get("title"),
    }),
  });
  const data = await response.json();
  console.log({ data }); // ログ出力で実行を確認
  return redirect("/");  // 任意のページにリダイレクト(今回はTOPページ)
}

export default function Posts() {
  return (
    <>
      <h1>フォームサンプル</h1>
      <Form method="post">
        <input type="text" name="title" />
        <button type="submit">Create post</button>
      </Form>
    </>
  );
}

■結果

Image in a image block
16:56:05 [vite] hmr update /app/routes/posts.tsx (x16)
{ data: { title: 'ほげほげ', id: 101 } }

フォームに入力された内容がjsonplaceholderにPOSTされ、レスポンスがログ出力されていることが確認できる。

POSTしたレスポンスデータを使って画面に表示する

POSTした結果のレスポンスボディを、画面に表示したいときもある。

その場合は useActionDataを使うと良い。

app/routes/posts.tsx

import { ActionFunctionArgs } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";

export async function action({ request }: ActionFunctionArgs) {
  const body = await request.formData();
  const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: body.get("title"),
    }),
  });
  const data = await response.json();
  // レスポンスボディを返却する
  return {
    id: data.id,
    title: data.title,
  };
}

export default function Posts() {
  // これでactionの返り値を取得できる
  // `typeof action`でレスポンスの型を推論してくれる
  const data = useActionData<typeof action>();
  return (
    <>
      <h1>フォームサンプル</h1>
      <Form method="post">
        <input type="text" name="title" />
        <button type="submit">Create post</button>
      </Form>
      <div>
        <ul>
          <li>{data?.id}</li>
          <li>{data?.title}</li>
        </ul>
      </div>
    </>
  );
}

■送信前

Image in a image block

■送信後

Image in a image block

番外編:ボタンを押して任意のPOSTリクエストを送る

これはクライアントサイドで実行することを想定する。

これはRemix関係なく、普通にReactのコードを書くだけで良い。

もし認証情報が必要な場合は、
📄Arrow icon of a page link【Remix】ブラウザから呼ぶプロキシAPIを作る を参考にプロキシAPIを作成し、そこを経由することで認証させるのが望ましい。

app/routes/posts.tsx

import { useState } from "react";

interface Post {
  id: number;
  title: string;
  body: string;
}

export default function Posts() {
  // ReactのuseStateにより状態管理
  const [post, setPost] = useState<Post | undefined>(undefined);

  // jsonplaceholderにPOSTリクエストを行い、レスポンスデータを`post`に設定する
  const fetchPost = async () => {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: "title",
        body: "body",
      }),
    });
    const data = (await response.json()) as Post;
    setPost(data);
  };

  return (
    <>
      <h1>フォームサンプル</h1>
      <button type="submit" onClick={fetchPost}>
        POSTデータを送信してレスポンスを取得するボタン
      </button>
      <p>id: {post?.id}</p>
      <p>title: {post?.title}</p>
      <p>body: {post?.body}</p>
    </>
  );
}

■ボタン押下前

Image in a image block

■ボタン押下後

Image in a image block

関連記事