LoginSignup
Akinko0915
@Akinko0915 (暁 中原)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Remix初心者 : actionが呼ばれない

解決したいこと

submitボタンを押したときに、actionが呼ばれるようにしたい

例)
Remixで、Event CalendarのようなWebアプリをつくっています。
現在、管理者側が操作できるカテゴリーページを作成しています。

ルーティング構成

routes/
└── admin.categories/
├── index.tsx # カテゴリ一覧ページ
└── delete.tsx # カテゴリ削除処理
└── category.new/ # 新規カテゴリ追加ページ
└── route.tsx
└── $categoryId.edit/ # カテゴリ編集ページ
└── route.tsx

  1. 削除
    今回は$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を使用して、どのフォームの送信状態を参照。このformIdValidatedForm**にも渡す。

```jsx
jsxCopy code
// ValidatedForm コンポーネントに formId を設定
<ValidatedForm formId="myUniqueFormId">
  {/* フォームの内容 */}
</ValidatedForm>

// useIsSubmitting フックに同じ formId を渡す
const isSubmitting = useIsSubmitting("myUniqueFormId");

⇒変化なし

  1. subaction
    ValidatedForm (Remix Validated Form)

⇒同じURLページで複数のactionがあるわけではないので、これを使う必要はないと考えた

  1. 動的リーティングの確認
0

No Answers yet.

Your answer might help someone💌