I am new to web development and currently learning.
Here is what I would like to do
- display the data retrieved from supabase in a table.
- Place an edit button on each row, and when clicked, display a dialog with the data in that row as the initial values.
- edit the dialog and click the update button to update the data
I do not know how to implement 2 and 3.
In 2, I cannot get the currentCampaign in the defaultValue of useForm, and the currentCampaign seems to be changed correctly in useEffect.
In 3, my code was too dirty, so I deleted it. In fact, I tried to write a process to update when actionType === “update” in the Action function, but even the formData could not be obtained correctly.
The code is as shown here.
import { Form, useActionData, redirect } from "@remix-run/react";
import { ActionFunction } from "@remix-run/node";
import { ActionFunctionArgs} from "@remix-run/node";
import { useState, useEffect } from "react";
import { useForm, getFormProps, getInputProps} from "@conform-to/react"
import { parseWithZod, getZodConstraint } from "@conform-to/zod"
import { createClient } from '@supabase/supabase-js';
import { set, z } from 'zod';
const supabaseUrl = process.env.SUPA_BASE_URL!;
const supabaseAnonKey = process.env.SUPA_BASE_API_KEY!;
const supabase = createClient(supabaseUrl, supabaseAnonKey);
import { LoaderFunction, json } from "@remix-run/node"
import {useLoaderData} from "@remix-run/react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogClose,
DialogFooter,
} from "@/components/ui/dialog"
import { captureRejectionSymbol } from "events";
const schema = z.object({
career: z.preprocess(
(val) => (val === ''? undefined : val),
z.string({required_error: 'Career is required'}),
),
shop_name: z.preprocess(
(val) => (val === ''? undefined : val),
z.string({required_error: 'Shop name is required'}),
),
details: z.preprocess(
(val) => (val === ''? undefined : val),
z.string({required_error: 'Details is required'}),
),
cb: z.preprocess(
(val) => (val === ''? undefined : val),
z.number({required_error: 'CB is required'}),
),
})
//Action Function
export const action: ActionFunction = async ({request}) => {
const formData = await request.formData();
const actionType = formData.get('_action');
const id = formData.get('id');
const career = formData.get('career');
const shop_name = formData.get('shop_name');
const details = formData.get('details');
const cb = parseFloat(formData.get('cb') as string);
if(actionType === 'edit'){
console.log('edit mode');
if(typeof id === 'string'){
const {data, error} = await supabase
.from('campaigns')
.select('*')
.eq('id', id)
.single();
if(error) throw new Error(error.message);
data.mode = 'edit';
console.log(data);
return json(data);
}
}
if(actionType === 'update'){
console.log('update mode');
console.log(formData);
const submission = parseWithZod(formData, {schema});
console.log(career, shop_name, details, cb);
return
}
};
//Loader Function
export const loader: LoaderFunction = async () => {
const { data, error } = await supabase
.from('campaigns')
.select('*');
if (error) throw new Error(error.message);
return json(data);
};
//Component
export default function Campaigns() {
const data = useLoaderData();
const result = useActionData();
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [currentCampaign, setCurrentCampaign] = useState({});
useEffect(() => {
if(!result) return;
if(result.mode === 'edit'){
console.log('edit mode');
console.log('result', result);
setCurrentCampaign({...result});
}
}, [result]);
useEffect(() => {
console.log('currentCampaign', currentCampaign);
}, [currentCampaign]);
const [form, fields ] = useForm({
defaultValue: {
career: currentCampaign?.career || 'default career',
shop_name: currentCampaign?.shop_name || 'default shop',
details: currentCampaign?.details || 'default details',
cb: currentCampaign?.CB || 1000,
},
constraint: getZodConstraint(schema),
onValidate({formData}){
return parseWithZod(formData,{schema});
},
});
return (
<div>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Operation</TableHead>
<TableHead className="w-[100px]">Career</TableHead>
<TableHead>Shop_name</TableHead>
<TableHead>Details</TableHead>
<TableHead className="text-right">CB</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((campaign: any) => (
<TableRow key={campaign.id}>
<TableCell className="font-medium">
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
<DialogTrigger asChild>
<Form method="post">
<input type="hidden" name="id" value={campaign.id} />
<Button variant="outline"
type="submit"
value="edit"
name="_action"
>Edit</Button>
</Form>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit campaign</DialogTitle>
</DialogHeader>
<Form method="post" {...getFormProps(form)} >
<input type="hidden" name="id" value={currentCampaign?.id || ''} />
<Label htmlFor={fields.career.id}>Career</Label>
<Input {...getInputProps(fields.career, {type:'text'})} />
<Label htmlFor={fields.shop_name.id}>Shop_name</Label>
<Input {...getInputProps(fields.shop_name, {type:'text'})} />
<Label htmlFor={fields.details.id}>Details</Label>
<Input {...getInputProps(fields.details, {type:'text'})} />
<Label htmlFor={fields.cb.id}>CB</Label>
<Input {...getInputProps(fields.cb, {type:'number'})} />
<DialogFooter className="sm:justify-start">
<Button type="submit" variant="destructive" name="_action" value="update">Update</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
</TableCell>
<TableCell className="font-medium">{campaign.career}</TableCell>
<TableCell>{campaign.shop_name}</TableCell>
<TableCell>{campaign.details}</TableCell>
<TableCell className="text-right">{campaign.CB.toLocaleString()}yen</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}