CRUD SERVER ACTION 구현하기
"use server";
import { Database } from "types_db";
export type TodoRow = Database["public"]["Tables"]["todo"]["Row"];
export type TodoRowInsert = Database["public"]["Tables"]["todo"]["Insert"];
export type TodoRowUpdate = Database["public"]["Tables"]["todo"]["Update"];
supabase 설정하면서 생성된 types_db.ts를 보면
todo 테이블이 생성되어 있는 것을 확인할 수 있다.
actions에서 그 테이블로 작업할거기때문에 import해서 사용한다.
select 쿼리
getTodos
"use server";
import { Database } from "types_db";
import { createServerSupabaseClient } from "utils/supabase/server";
export type TodoRow = Database["public"]["Tables"]["todo"]["Row"];
export type TodoRowInsert = Database["public"]["Tables"]["todo"]["Insert"];
export type TodoRowUpdate = Database["public"]["Tables"]["todo"]["Update"];
//에러가 있을 경우 콘솔에 출력 throw 에러
function handleError(error) {
console.error(error);
throw new Error(error.message);
}
//get todos
export async function getTodos({ searchInput = "" }): Promise<TodoRow[]>
{
{
const supabase = await createServerSupabaseClient();
// select 쿼리문 : todo테이블에서 모든 데이터 -> 제목에 searchInput 단어 포함, 생성일자 순으로 오름차순 정렬하기
const { data, error } = await supabase
.from("todo")
.select("*")
.like("title", `%${searchInput}%`) //searchInput으로 들어오는 검색어
.order("created_at", { ascending: true }); //생성일자, 오름차순 정렬
//만약 에러가 있으면 handleError 함수 실행
if (error) {
handleError(error);
}
//데이터 리턴
return data;
}
// create, update todo
export async function createTodo(todo: TodoRowInsert) {
const supabase = await createServerSupabaseClient();
const { data, error } = await supabase.from("todo").insert({
...todo,
created_at: new Date().toISOString(), //실제 실행된 시점으로 넣어줌
});
if (error) {
handleError(error);
}
return data;
}
export async function updateTodo(todo: TodoRowUpdate) {
const supabase = await createServerSupabaseClient();
const { data, error } = await supabase
.from("todo")
.update({ ...todo, updated_at: new Date().toISOString() })
.eq("id", todo.id);
if (error) {
handleError(error);
}
return data;
}
//delete todo
export async function deleteTodo(id: number) {
const supabase = await createServerSupabaseClient();
const { data, error } = await supabase.from("todo").delete().eq("id", id);
if (error) {
handleError(error);
}
return data;
}
그리고 만들어놓은 ui.tsx에서
사용
useQuery 를 사용하면 읽기만가능하고
useMutation을 사용하면 추가, 수정 가능
const [searchInput, setSearchInput] = useState("");
//todo-actions ->getTodo
const todosQuery = useQuery({
queryKey: ["todos"],
queryFn: () => getTodos({ searchInput }),
});
const createTodoMutation = useMutation({
mutationFn: () =>
createTodo({
title: "New Todo", //기본값
completed: false,
}),
onSuccess: () => {
todosQuery.refetch(); //todo 추가하고 다시 가져오기
},
});
<Input
label="Search TODO"
placeholder="Search TODO"
icon={<i className="fas fa-search" />}
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
/>
{todosQuery.isPending && <p>Loading...</p>}
{todosQuery.data &&
todosQuery.data.map((todo) => <Todo key={todo.id} todo={todo} />)}
<Button
onClick={() => createTodoMutation.mutate()}
loading={createTodoMutation.isPending}
>
<i className="fas fa-plus mr-2"></i>
ADD TODO
</Button>
todo.tsx
"use client";
import { Checkbox, IconButton, Spinner } from "@material-tailwind/react";
import { useMutation } from "@tanstack/react-query";
import { deleteTodo, updateTodo } from "actions/todo-actions";
import { queryClient } from "config/ReactQueryClientProvider";
import { useState } from "react";
export default function Todo({ todo }) {
const [isEditing, setIsEditing] = useState(false);
const [completed, setCompleted] = useState(todo.completed);
const [title, setTitle] = useState(todo.title);
const updateTodoMutation = useMutation({
mutationFn: () =>
updateTodo({
id: todo.id,
title,
completed,
}),
onSuccess: () => {
setIsEditing(false);
queryClient.invalidateQueries({
queryKey: ["todos"], //업데이트 후 다시 가져오기
});
},
});
const deleteTodoMutation = useMutation({
mutationFn: () => deleteTodo(todo.id),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["todos"], //업데이트 후 다시 가져오기
});
},
});
return (
<div className=" w-full flex items-center gap-1">
<Checkbox
checked={completed}
onChange={async (e) => {
await setCompleted(e.target.checked);
await updateTodoMutation.mutate();
}}
/>
{isEditing ? (
<input
className="flex-1 border-b-black border-b pb-1"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
) : (
<p className={`${completed && "line-through"} flex-1`}>{title}</p>
)}
{isEditing ? (
<IconButton onClick={async () => await updateTodoMutation.mutate()}>
{updateTodoMutation.isPending ? (
<Spinner />
) : (
<i className="fas fa-check" />
)}
</IconButton>
) : (
<IconButton onClick={() => setIsEditing(true)}>
<i className="fas fa-pen" />
</IconButton>
)}
<IconButton onClick={() => deleteTodoMutation.mutate()}>
{deleteTodoMutation.isPending ? (
<Spinner />
) : (
<i className="fas fa-trash" />
)}
</IconButton>
</div>
);
}
https://supabase.com/dashboard/project/eceptjznfdhgchrlzyyt/editor/28998
실제로 데이터가 추가 및 수정 이 되는지 확인한다.
'FRONTEND > Next.js' 카테고리의 다른 글
[Next.js 14 + Supabase ] Recoil 사용하여 검색어 가져오기 (0) | 2024.08.06 |
---|---|
[Next.js 14 + Supabase ] Supabase Storage 파일 업로드 구현 및 react-dropzone 라이브러리 활용 (0) | 2024.08.01 |
[Next.js 14 + Supabase ] Supabase 프로젝트 생성 (0) | 2024.07.27 |
[Next.js 14 + Supabase ] Material Tailwind 기본 설정하기 (0) | 2024.07.27 |
[Next.js 14 + Supabase ] Next.js[ Typescript ] React Query (0) | 2024.07.26 |