Created
November 28, 2024 09:11
-
-
Save kuc-arc-f/3d47ef11cb8d595a352396d0dd3f7e24 to your computer and use it in GitHub Desktop.
react-router-7, CRUD example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { Route } from "./+types/home"; | |
import { Form , useSubmit } from "react-router"; | |
import { useState , useEffect } from "react"; | |
import { z } from "zod"; | |
export function meta({}: Route.MetaArgs) { | |
return [ | |
{ title: "New React Router App" }, | |
{ name: "description", content: "Welcome to React Router!" }, | |
]; | |
} | |
// スキーマの定義 | |
const todoSchema = z.object({ | |
title: z.string().min(1, "タイトルは必須です"), | |
description: z.string().optional(), | |
completed: z.boolean().optional(), | |
}); | |
type Todo = z.infer<typeof todoSchema> & { | |
id: number; | |
created_at: string; | |
updated_at: string; | |
}; | |
export async function loader({ params }: Route.LoaderArgs) { | |
let apiUrlBase = import.meta.env.VITE_WORKERS_URL; | |
console.log(params); | |
//console.log("apiUrlBase=", apiUrlBase); | |
let apiUrl = apiUrlBase + "/api/todo4"; | |
console.log("apiUrl=", apiUrl); | |
const response = await fetch(apiUrl); | |
const data = await response.json(); | |
console.log(data); | |
return { todos: data}; | |
} | |
export async function action({ | |
request, | |
}: Route.ActionArgs) { | |
let formData = await request.formData(); | |
const action = formData.get("_action"); | |
let title = await formData.get("title"); | |
console.log("action=", action); | |
const input = { | |
title: title | |
} | |
console.log(input); | |
let apiUrlBase = import.meta.env.VITE_WORKERS_URL; | |
switch (action) { | |
case "create": | |
case "update": { | |
const title = formData.get("title") as string; | |
//const description = formData.get("description") as string; | |
const description = ""; | |
const completed = false; | |
const id = formData.get("id") as string; | |
try { | |
const validatedData = todoSchema.parse({ title, description, completed }); | |
console.log("apiUrlBase=", apiUrlBase); | |
console.log(validatedData); | |
const response = await fetch( | |
`${apiUrlBase}/api/todo4${action === "update" ? `/${id}` : ""}`, | |
{ | |
method: action === "create" ? "POST" : "PUT", | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify(validatedData), | |
} | |
); | |
return { success: true }; | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
//return json({ errors: error.errors }, { status: 400 }); | |
return { errors: error.errors }; | |
} | |
//return json({ error: "Failed to save todo" }, { status: 500 }); | |
return json({ error: "Failed to save todo" }, { status: 500 }); | |
} | |
} | |
case "delete": { | |
const id = formData.get("id"); | |
console.log("delete.id=", id); | |
const response = await fetch(`${apiUrlBase}/api/todo4/${id}`, { | |
method: "DELETE", | |
}); | |
return { success: true }; | |
} | |
default: | |
return json({ error: "Invalid action" }, { status: 400 }); | |
} | |
return {data : input }; | |
} | |
import DialogForm from './test2/DialogForm'; | |
const DIALOG_FORM_CREATE = "dialog_form_create"; | |
const DIALOG_FORM_EDIT = "dialog_form_edit"; | |
// | |
export default function Product({ | |
loaderData, | |
actionData, | |
}: Route.ComponentProps) { | |
let submit = useSubmit(); | |
const todos = loaderData.todos; | |
const [formData, setFormData] = useState<Todo>(null); | |
console.log(todos); | |
//console.log(actionData); | |
useEffect(() => { | |
if(actionData){ | |
console.log(actionData); | |
if(actionData.success){ | |
console.log("#success"); | |
location.reload(); | |
} | |
} | |
}, [actionData]); | |
const handleDelete = async function(todo){ | |
console.log(todo); | |
if (!window.confirm("Delete OK?")) { | |
return; | |
} | |
if(todo) { | |
const formData = new FormData(); | |
formData.append("_action", "delete"); | |
formData.append("id", todo.id); | |
submit(formData, { method: "post" }); | |
} | |
} | |
const handleEdit = async function(todo){ | |
console.log(todo); | |
setFormData(todo); | |
if(todo) { | |
openEdit(); | |
} | |
} | |
const openCreate = function(){ | |
const modalDialog = document.getElementById(DIALOG_FORM_CREATE); | |
if(modalDialog) { modalDialog.showModal(); } | |
} | |
const closeCreate = function(){ | |
const modalDialog = document.getElementById(DIALOG_FORM_CREATE); | |
if(modalDialog) { modalDialog.close(); } | |
} | |
const openEdit = function(){ | |
const modalDialog = document.getElementById(DIALOG_FORM_EDIT); | |
if(modalDialog) { modalDialog.showModal(); } | |
} | |
const closeEdit = function(){ | |
const modalDialog = document.getElementById(DIALOG_FORM_EDIT); | |
if(modalDialog) { modalDialog.close(); } | |
} | |
return ( | |
<div className="container mx-auto p-4"> | |
<h1 className="text-3xl font-bold">Test2</h1> | |
<hr /> | |
<button onClick={()=>openCreate()}>[ Add ]</button> | |
<dialog id={DIALOG_FORM_CREATE} className="dialog"> | |
<div className="bg-white px-8 pt-3 pb-3 dialog_body_wrap"> | |
<p className="text-3xl font-bold">Add </p> | |
<hr className="my-3" /> | |
<DialogForm idName={DIALOG_FORM_CREATE} mode="create" formData={null} /> | |
</div> | |
</dialog> | |
{/* edit_Form */} | |
<dialog id={DIALOG_FORM_EDIT} className="dialog"> | |
<div className="bg-white px-8 pt-3 pb-3 dialog_body_wrap"> | |
<p className="text-3xl font-bold">edit </p> | |
<hr className="my-3" /> | |
<DialogForm idName={DIALOG_FORM_EDIT} mode="edit" | |
formData={formData} /> | |
</div> | |
</dialog> | |
{/* List */} | |
<div className="space-y-4"> | |
{todos.map((todo) => ( | |
<div | |
key={todo.id} | |
className="flex items-center justify-between p-4 border rounded-lg" | |
> | |
<div> | |
<h3 className="font-semibold">{todo.title}</h3> | |
<p className="text-sm text-gray-600">{todo.description}</p> | |
</div> | |
<div className="flex gap-2"> | |
<button | |
variant="outline" | |
onClick={() => handleEdit(todo)} | |
> | |
編集 | |
</button> | |
<button | |
variant="destructive" | |
onClick={() => handleDelete(todo)} | |
> | |
削除 | |
</button> | |
</div> | |
</div> | |
))} | |
</div> | |
</div> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment