Remix初心者 : actionが呼ばれない
Q&A
Closed
解決したいこと
submitボタンを押したときに、actionが呼ばれるようにしたい
例)
Remixで、Event CalendarのようなWebアプリをつくっています。
現在、管理者側が操作できるカテゴリーページを作成しています。
ルーティング構成
routes/
└── admin.categories/
├── index.tsx # カテゴリ一覧ページ
└── delete.tsx # カテゴリ削除処理
└── category.new/ # 新規カテゴリ追加ページ
└── route.tsx
└── $categoryId.edit/ # カテゴリ編集ページ
└── route.tsx
- 削除
今回は$categoryId.editフォルダーでのactionが呼び起こされないという問題です。編集formがあるファイルに書かれた提出ボタンですが、それがクリックされても同じファイルにあるactionが呼び出されません
発生している問題・エラー
・提出ボタンクリック時、検証のネットワークをみたところ、リクエストが送られていない状態でした。
・提出ボタンクリック数分後、コンソールに
-
Unable to determine form for useIsSubmitting: とエラーがでました。**
useIsSubmitting
フックが、どのValidatedForm
**に属しているのかを特定できない状態です
該当するソースコード
admin.categories/index.tsx
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Box,
Button,
Grid,
GridItem,
} from "@chakra-ui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPenToSquare, faTrash } from "@fortawesome/free-solid-svg-icons";
import { Link, useLoaderData } from "@remix-run/react";
import CategoryDeleteModal from "./admin.categories.$categoryId.delete.";
import { useState } from "react";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { db } from "~/db.server";
export const loader = async ({ params }: LoaderFunctionArgs) => {
const categories = await db.category.findMany({
where: {
id: params.id,
},
});
return json({ categories: categories || [] });
};
const TableStyle = {
color: "white",
fontWeight: "bold",
fontSize: "20px",
};
function Category() {
const { categories = [] } = useLoaderData<typeof loader>();
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
return (
<>
<Box
display="flex"
alignItems="center"
justifyContent="center"
mt="0"
w="100%"
>
<Grid>
<GridItem marginBottom={10}>
<Link to="new">
<Button
bg="green"
color="white"
_hover={{ bg: "white", color: "green" }}
>
Add Category
</Button>
</Link>
</GridItem>
<GridItem>
<TableContainer width="1100px">
<Table variant="simple" bg="white" borderColor="brand.200">
<Thead bg="brand.200" textColor="white">
<Tr>
<Th style={TableStyle}>Id</Th>
<Th style={TableStyle}>Name</Th>
<Th style={TableStyle}>Color</Th>
<Th style={TableStyle}>Operation</Th>
</Tr>
</Thead>
<Tbody>
{categories.map((category) => (
<Tr key={category.id}>
<Td>{category.id}</Td>
<Td>{category.name}</Td>
<Td>{category.color}</Td>
<Td>
<Link to={`/${category.id}/edit`}>
<Button
marginRight={10}
bg="white"
color="blue"
_hover={{ color: "white", bg: "blue" }}
width="30px"
>
<FontAwesomeIcon
fontSize="30px"
icon={faPenToSquare}
/>
</Button>
</Link>
<Button
marginRight={10}
bg="white"
color="red"
_hover={{ color: "white", bg: "red" }}
onClick={() => setIsDeleteModalOpen(true)}
width="30px"
>
<FontAwesomeIcon fontSize="30px" icon={faTrash} />
</Button>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</GridItem>
</Grid>
<CategoryDeleteModal
isOpen={isDeleteModalOpen}
onClose={() => setIsDeleteModalOpen(false)}
/>
</Box>
</>
);
}
export default Category;
$categoryId.edit/route.tsx
import { Box, Grid, GridItem, Text } from "@chakra-ui/react";
import { getCategory, updateCategory } from "~/models/category.server";
import {
DataFunctionArgs,
LoaderFunctionArgs,
json,
redirect,
} from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { ValidatedForm, validationError } from "remix-validated-form";
import { db } from "~/db.server";
import { withZod } from "@remix-validated-form/with-zod";
import { z } from "zod";
import { FormInput } from "~/components/form/FormInput";
import { MySubmitButton } from "~/components/form/SubmitButton";
import { FormAlert } from "~/components/form/FormAlert";
export const validator = withZod(
z.object({
id: z.string().uuid(),
name: z.string().min(1, { message: "category name is required" }),
color: z.string().nullable(),
})
);
export type AlertProps = {
variant: "info" | "warning" | "success" | "error";
title: string;
details: string;
};
export type CategoryEditProps = {
category: {
id: string;
name: string;
color: string | null;
};
};
export async function loader({ params }: LoaderFunctionArgs) {
if (typeof params.categoryId !== "string") throw Error("invalid id");
const category = await getCategory(params.categoryId);
console.log("category", category);
if (!category) throw Error("category not found");
return json({ category });
}
export async function action({ request, params }: DataFunctionArgs) {
const formData = await request.formData();
console.log("action");
if (typeof params.categoryId !== "string") throw Error("invalid id");
const data = await validator.validate(formData);
if (data.error) return validationError(data.error);
const { name, color } = data.data;
try {
await updateCategory(params.categoryId, { name, color });
return redirect("/admin/categories");
} catch (error) {
console.error("Error updating category", error);
return json(
{
title: "Error",
details: "An error occurred while updating the category.",
},
{ status: 500 }
);
} finally {
db.$disconnect();
}
}
function EditCategory() {
const { category } = useLoaderData<typeof loader>();
const data = useActionData<AlertProps>();
return (
<>
<Box
display="flex"
alignItems="center"
justifyContent="center"
mt="0"
w="100%"
textColor="green"
marginBottom={20}
>
<Text fontWeight="bolder" fontSize="40px">
Edit Category
</Text>
</Box>
<ValidatedForm
validator={validator}
action="."
method="post"
defaultValues={{
name: category.name,
color: category.color,
id: category.id,
}}
>
<Box
display="flex"
alignItems="center"
justifyContent="center"
mt="0"
w="100%"
>
<Grid templateColumns="repeat(1, 1fr)">
<GridItem bg="none">
<FormInput name="name" label="category name" />
</GridItem>
<GridItem bg="none">
<FormInput name="color" label="color" />
</GridItem>
<input type="hidden" name="id" />
</Grid>
</Box>
<Box
display="flex"
alignItems="center"
justifyContent="center"
mt="0"
w="100%"
marginBottom={10}
>
<MySubmitButton value="update" />
{data && (
<FormAlert
variant="info"
title={data.title}
details={data.details}
/>
)}
</Box>
</ValidatedForm>
</>
);
}
export default EditCategory;
SubmitButton.tsx
import { Button } from "@chakra-ui/react";
import { useIsSubmitting } from "remix-validated-form";
export const MySubmitButton = ({ value }: { value: string }) => {
const isSubmitting = useIsSubmitting();
return (
<Button
type="submit"
disabled={isSubmitting}
bg="brand.200"
textColor="white"
_hover={{ bg: "white", textColor: "brand.200" }}
marginTop={10}
name="action"
value={value}
>
{isSubmitting ? "Submitting..." : "Submit"}
</Button>
);
};
自分で試したこと
1.formId
を使用する
**formId
を使用して、どのフォームの送信状態を参照。このformId
をValidatedForm
**にも渡す。
```jsx
jsxCopy code
// ValidatedForm コンポーネントに formId を設定
<ValidatedForm formId="myUniqueFormId">
{/* フォームの内容 */}
</ValidatedForm>
// useIsSubmitting フックに同じ formId を渡す
const isSubmitting = useIsSubmitting("myUniqueFormId");
⇒変化なし
- subaction
ValidatedForm (Remix Validated Form)
⇒同じURLページで複数のactionがあるわけではないので、これを使う必要はないと考えた
- 動的リーティングの確認
0