Astro Actions 試してみた
この記事について
Astro 4.8 で Experimental な機能として登場した Astro Actions をサクッと試してみた記事です。
先月 Astro DB 試してみた成果物を作ったので
今回はこれを Astro Actions を使うように改造したいと思います。
環境
"@astrojs/check": "^0.6.0"
"@astrojs/db": "^0.11.1"
"@astrojs/preact": "^3.3.0"
"@astrojs/vercel": "^7.6.0"
"astro": "^4.8.0"
"preact": "^10.21.0"
"typescript": "^5.4.5"
完成品
https://github.com/k1350/astro-db-test/tree/experimental-astro-actions
experimental-astro-actions というブランチにコミットしました。
本文
1. Astro Actions を有効化
まず astro.config.mjs で Astro Actions を有効にします。
import db from "@astrojs/db";
import preact from "@astrojs/preact";
import vercel from "@astrojs/vercel/serverless";
import { defineConfig } from "astro/config";
// https://astro.build/config
export default defineConfig({
integrations: [db(), preact()],
output: "server",
adapter: vercel(),
+ experimental: {
+ actions: true,
+ },
});
2. Astro Actions を書く
次に src/actions/index.ts にアクションを書きます。
今回はもともと src/pages/api/addLikes.ts に記述していた「いいね」追加処理を移植します。
(ついでに前回の成果物では更新後の「いいね」を取得するために画面ごとリロードしていて劣悪なユーザー体験だったので、今回はここで最新の「いいね」数を返すようにします。)
import { defineAction, z } from "astro:actions";
import { Like, count, db, eq } from "astro:db";
export const server = {
addLike: defineAction({
accept: "form",
input: z.object({ url: z.string().regex(/^\/.*/) }),
handler: async ({ url }) => {
await db.insert(Like).values({ url, createdAt: new Date() });
const likeCounts = await db
.select({ count: count() })
.from(Like)
.where(eq(Like.url, url));
const likeCount = likeCounts.length > 0 ? likeCounts[0].count : 0;
return { likeCount };
},
}),
};
Astro Actions の構文について、https://docs.astro.build/en/reference/configuration-reference/#experimentalactions には handler
が context
という引数も取るように描いてあるのですが、これだと動きませんでした。
追記: ドキュメント修正されました。
現在は Blog のほうに書いてあるように context
無しの構文が正となります。
Actions RFC によると、もとは第二引数で context
を取れる形だったのが、最終的には getApiContext()
で取得するように変更され、ドキュメントの更新が今現在は追いついていないようです。
getApiContext()
からは cookie などが取得できるみたいですね。
3. クライアントサイドから Astro Actions を呼ぶ
次に「いいね」追加処理を実行していた部分を Astro Actions を使用する形式に直します。
「"astro:actions" が見つからない」的なエラーメッセージがエディタに表示される場合や、actions.addLike
の戻り値の型が取得できない場合は一度 astro dev
コマンドを実行すると解消すると思います。
+ import { actions } from "astro:actions";
import { useState } from "preact/hooks";
type Props = {
url: string;
+ updateLikeCount: (newLikeCount: number) => void;
};
- export function AddLikeForm({ url }: Props) {
+ export function AddLikeForm({ url, updateLikeCount }: Props) {
const [isSubmitting, setIsSubmitting] = useState(false);
async function submit(e: SubmitEvent) {
e.preventDefault();
setIsSubmitting(true);
const formData = new FormData(e.target as HTMLFormElement);
- const response = await fetch("/api/addLike", {
- method: "POST",
- body: formData,
- });
- if (response.status === 200) {
- window.location.reload();
- }
+ const result = await actions.addLike(formData);
+ updateLikeCount(result.likeCount);
setIsSubmitting(false);
}
return (
<form onSubmit={submit}>
<input type="hidden" name="url" value={`${url}`} />
<button type="submit" disabled={isSubmitting}>
Add Like
</button>
</form>
);
}
あとは不要になった src/pages/api/addLikes.ts を消し、諸々整えて Vercel にデプロイします!
デプロイは特に問題なく、Astro Actions を使って「いいね」を更新することができるようになりました🎉
感想
Astro Actions 側で input
を Zod のスキーマで定義させる点は優れていると思いました。
accept: "form"
の宣言で FormData を受け取るか、JSON を受け取るかを切り替えられるのもわかりやすくて良いですね。
Discussion