이번 주 진행한 프로젝트
1. 리액트 & api 이용해 이미지검색하는 사이트 만들기
2. 넥스트js 이용해 supabase 데이터베이스 연동하여 todo리스트 만들기
1번 프로젝트는 많이 놓쳐서 다음 주 내에 정리해야할거같다.. 🥹
2번 프로젝트에서 css랑 ui는 다음에 해도 될 것 같고 supabase연동 부분 위주로 정리해보았다.
feat: supabase 연동 - add new page 기능 만들고 aside에 페이지 목록 불러오기
Database | Supabase Docs
Database | Supabase Docs
Use Supabase to manage your data.
supabase.com
"use client";
import { Button } from "@/components/ui";
import { supabase } from "@/lib/supabase";
function InitPage() {
const handleCreateTask = async () => {
console.log("버튼동작");
const { data, error } = await supabase
.from("boards")
.insert([{ some_column: "someValue", other_column: "otherValue" }])
.select();
};
return (
<div className="w-full h-full flex flex-col items-center justify-center">
<div className="flex flex-col items-center justify-center gap-5 mb-6">
<h3 className="scroll-m-20 text-2xl font-semibold tracking-tight">
How to start:
</h3>
<div className="flex flex-col items-center gap-3">
<small className="text-sm font-normal leading-none">
1. Create a page
</small>
<small className="text-sm font-normal leading-none">
2. Add boards to page
</small>
</div>
</div>
<Button
className="text-[#E79057] bg-transparent border border-[#E79057] hover:bg-[#FFF9F5] w-[180px]"
onClick={handleCreateTask}
>
Add New Page
</Button>
</div>
);
}
export default InitPage;
const { data, error } = await supabase
.from("boards")
.insert([{ title: null, start_date: null, end_date: null , boards: null}])
.select();
};
"use client";
import { Button } from "@/components/ui";
import { toast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
function InitPage() {
const handleCreateTask = async () => {
console.log("버튼동작");
try {
const { data, status, error } = await supabase
.from("todos")
.insert([
{ title: "", start_date: null, end_date: null, boards: null },
])
.select();
console.log(data);
if (status === 201 && data) {
/** TOAST UI 띄우기 */
// 설치코드: npx shadcn@latest add toast
toast({
title: "새로운 투두리스트가 생성",
description: "수파베이스확인",
});
}
} catch (error) {
console.error(error);
toast({
variant: "destructive",
title: "새로운 투두리스트가 생성",
description: "개발자 도구창을 확인하세요",
});
}
};
return (
---------------------------생략---------------------------
);
}
export default InitPage;
타입정의
export interface Task {
id: number;
title: string;
startDate: Date;
endtDate: Date;
boards: Board;
}
export interface Board {
id: string; //테이블분리할경우 타입변경될수이승ㅁ
title: string;
startDate: Date;
endtDate: Date;
contenr: string;
isCompleted: boolean;
}
"use client";
/** 컴포넌트 */
import { Button, SearchBar } from "@/components/ui";
import { toast, useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { useRouter } from "next/navigation";
function AsideSection() {
const { toast } = useToast();
const router = useRouter();
/** 페이지목록- todos 전체 */
const getTasks = async()=>{
const { data: boards, error } = await supabase
.from('boards')
.select('*')
};
/** add new page */
const handleCreateTask = async () => {
console.log("버튼동작");
try {
const { data, status, error } = await supabase
.from("todos")
.insert([
{ title: "", start_date: null, end_date: null, boards: null },
])
.select();
console.log(data);
if (status === 201 && data) {
/** TOAST UI 띄우기 */
// 설치코드: npx shadcn@latest add toast
toast({
title: "새로운 투두리스트가 생성",
description: "수파베이스확인",
});
router.push(`/board/${data[0].id}`);
}
} catch (error) {
console.error(error);
toast({
variant: "destructive",
title: "새로운 투두리스트가 생성",
description: "개발자 도구창을 확인하세요",
});
}
};
return (
<aside className="page__aside">
{/* 검색창 UI */}
<SearchBar placeholder="검색어를 입력하세요." />
{/* Add New Page 버튼 UI */}
<Button className="text-[#E79057] bg-white border border-[#E79057] hover:bg-[#FFF9F5]" onClick={handleCreateTask}>Add New Page</Button>
{/* TODO 목록 UI 하나 */}
<div className="flex flex-col mt-4 gap-2">
<small className="text-sm font-medium leading-none text-[#A6A6A6]">9Diin의 TODO-BOARD</small>
<ul className="flex flex-col">
<li className="bg-[#F5F5F5] min-h-9 flex items-center gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer">
<div className="h-[6px] w-[6px] rounded-full bg-[#00F38D]"></div>
등록된 제목이 없습니다.
</li>
</ul>
</div>
</aside>
);
}
export { AsideSection };
function AsideSection() {
const { toast } = useToast();
const router = useRouter();
const [tasks, setTasks] = useState<Task[]>([]);
/** 페이지목록- todos 전체 */
const getTasks = async()=>{
try {
const { data, status } = await supabase.from('todos').select('*');
if (status === 200 && data !== null) setTasks(data);
} catch (error) {
console.error(error);
toast({
variant: "destructive",
title: "에러",
description: "에러발생",
});
}
};
/** add new page */
const handleCreateTask = async () => {
console.log("버튼동작");
try {
const { data, status, error } = await supabase
.from("todos")
.insert([
{ title: "", start_date: null, end_date: null, boards: null },
])
.select();
console.log(data);
if (status === 201 && data) {
/** TOAST UI 띄우기 */
// 설치코드: npx shadcn@latest add toast
toast({
title: "새로운 투두리스트가 생성",
description: "수파베이스확인",
});
router.push(`/board/${data[0].id}`);
}
} catch (error) {
console.error(error);
toast({
variant: "destructive",
title: "새로운 투두리스트가 생성",
description: "개발자 도구창을 확인하세요",
});
}
};
useEffect(()=>{
getTasks();
});
return (
<aside className="page__aside">
{/* 검색창 UI */}
<SearchBar placeholder="검색어를 입력하세요." />
{/* Add New Page 버튼 UI */}
<Button className="text-[#E79057] bg-white border border-[#E79057] hover:bg-[#FFF9F5]" onClick={handleCreateTask}>Add New Page</Button>
{/* TODO 목록 UI 하나 */}
<div className="flex flex-col mt-4 gap-2">
<small className="text-sm font-medium leading-none text-[#A6A6A6]">9Diin의 TODO-BOARD</small>
<ul className="flex flex-col">
{ tasks.length===0?(<li className="bg-[#F5F5F5] min-h-9 flex items-center gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer">
<div className="h-[6px] w-[6px] rounded-full bg-[#00F38D]"></div>
등록된 제목이 없습니다.
</li>) :(tasks.map((task: Task)=>{
return (
<li className="bg-[#F5F5F5] min-h-9 flex items-center gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer">
<div className="h-[6px] w-[6px] rounded-full bg-[#00F38D]"></div>
{task.title ? task.title: "제목 없음"}
</li>
);
})) }
</ul>
</div>
</aside>
);
}
const { toast } 하면 머가다르지
→에러뜸!
https://velog.io/@chaerin00/React-%EC%9D%98-%EC%9D%98%EB%AF%B8
React { }의 사용
React에서 굳이 궁금해하지 않았던 { }가 쓰이는 규칙👨🏫
velog.io
"use client";
/** 컴포넌트 */
import { Button, SearchBar } from "@/components/ui";
import { toast, useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { Task } from "@/types";
import { useRouter, useParams } from "next/navigation";
import { useEffect, useState } from "react";
function AsideSection() {
const { toast } = useToast();
const router = useRouter();
const { id }= useParams();
const [tasks, setTasks] = useState<Task[]>([]);
/** 페이지목록- todos 전체 */
const getTasks = async () => {
try {
const { data, status } = await supabase.from("todos").select("*");
if (status === 200 && data !== null) setTasks(data);
} catch (error) {
console.error(error);
toast({
variant: "destructive",
title: "에러",
description: "에러발생",
});
}
};
/** add new page */
const handleCreateTask = async () => {
console.log("버튼동작");
try {
const { data, status, error } = await supabase
.from("todos")
.insert([
{
title: "",
start_date: null,
end_date: null,
boards: null,
},
])
.select();
console.log(data);
if (status === 201 && data) {
/** TOAST UI 띄우기 */
// 설치코드: npx shadcn@latest add toast
toast({
title: "새로운 투두리스트가 생성",
description: "수파베이스확인",
});
router.push(`/board/${data[0].id}`);
}
} catch (error) {
console.error(error);
toast({
variant: "destructive",
title: "생성실패",
description: "개발자 도구창을 확인하세요",
});
}
};
useEffect(() => {
getTasks();
}, []);
return (
<aside className="page__aside">
{/* 검색창 UI */}
<SearchBar placeholder="검색어를 입력하세요." />
{/* Add New Page 버튼 UI */}
<Button
className="text-[#E79057] bg-white border border-[#E79057] hover:bg-[#FFF9F5]"
onClick={handleCreateTask}>
Add New Page
</Button>
{/* TODO 목록 UI 하나 */}
<div className="flex flex-col mt-4 gap-2">
<small className="text-sm font-medium leading-none text-[#A6A6A6]">
고마11의 TODO-board
</small>
<ul className="flex flex-col">
{tasks.length ? (
tasks.map((task: Task) => {
return (
<li
key={task.id}
className={`${ task.id === Number(id) ? `bg-[#F5F5F5]` : `bg-transparent` }
min-h-9 flex items-center gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer`}
onClick={() =>
router.push(`/board/${task.id}`)
}
>
<div
className={`${ task.id === Number(id) ? `bg-[#00F38D]` : `bg-neutral-400` }
h-[6px] w-[6px] rounded-full`}>
</div>
<span
className={`${ task.id === Number(id) ? `` : `text-neutral-400` }`} >
{task.title ? task.title : "등록된 TASK 제목이 없습니다."}
</span>
</li>
);
})
) : (
<li className="bg-[#F5F5F5] text-neutral-400 w-full flex items-center justify-center min-h-9 gap-2 py-2 px-[10px] rounded-sm text-sm cursor-pointer">
생성된 TASK가 없습니다.
</li>
)}
</ul>
</div>
</aside>
);
}
export { AsideSection };
useParams로 링크의 id관리하기
→ 그 id가 parameter임
[React] useParams() 사용하여 파라미터 가져오기
[React] useParams() 사용하여 파라미터 가져오기
리액트에서 라우터 사용 시 파라미터 정보를 가져와 활용하고 싶다면 useParams()라는 훅을 사용하면 된다. 라우터를 사용하고 있다는 가정 하에 useParams 사용 방법에 대해 알아보도록 하겠다.1) useP
velog.io
→해결은
feat: 게시물(Task) 페이지 기능 개발- 특정 id 값에 따른 TASK 데이터 불러오기 select "*”
feat: 게시물(Task) 페이지 기능 개발- 특정 id 값에 따른 TASK 데이터 불러오기 select "*" · nrkcode/next-next-app-supabase@b4ac53e · GitHub
feat: 게시물(Task) 페이지 기능 개발- 특정 id 값에 따른 TASK 데이터 불러오기 select "*" · nrkcode/next-ne
nrkcode committed Nov 23, 2024
github.com
import { toast, useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { Task } from "@/types";
import { useRouter, useParams } from "next/navigation";
import { useEffect, useState } from "react";
function BoardPage() {
const { toast } = useToast();
const router = useRouter();
const { id }= useParams();
const [task, setTask] = useState<Task[]>([]);
/** 특정 id 값에 따른 TASK 데이터 */
const getTask = async () => {
try {
const { data, status } = await supabase.from("todos").select("*");
if (status === 200 && data !== null) setTask(data);
} catch (error) {
console.error(error);
toast({
variant: "destructive",
title: "에러",
description: "에러발생",
});
}
};
/** Board Card 생성 및 데이터베이스에 저장 */
const handleCreateBoard = async () => {};
useEffect(() => {
getTask();
}, []);
...
<h3 className="scroll-m-20 text-2xl font-semibold tracking-tight">There is no board yet.</h3>
<small className="text-sm font-medium leading-none text-[#6D6D6D] mt-3 mb-7">Click the button and start flashing!</small>
<button onClick={handleCreateBoard}>
[feat] 게시물(Task) 페이지 기능 개발 Add New Board 버튼 클릭 시, BoardCard 생성
const [boards, setBoards] = useState<Board[]>(task?.boards || []);
/** Board Card 생성 및 데이터베이스에 저장 */
const handleCreateBoard = async () => {
const newBoard: Board = {
id: nanoid(), // 추후에 Supabase boards 컬럼을 다른 테이블로 분리할 경우, 타입이 변경될 수 있음
title: "",
startDate: null,
endtDate: null,
content: "",
isCompleted: false,
};
setBoards((prevBoards) => [...prevBoards, newBoard]);
};
task?.boards를 가져와서 setBoards함수를 통해 boards에 넣음
boards를 수파베이스로 보냄
const updateTaskOneColumnByID = async () => {
try {
const {data, status} = await supabase.from("todos").update({boards:boards}).eq("id",id);
} catch (error) {
console.error(error);
}
}
이번주 후기
이번 주는 돌아가며 자기소개도 해서 다른 분들의 이름을 알게 되었다.
온라인 강의실에 대답을 다들 안하셔서 머쓱하지만 나라도.. 라는 마음으로 열심히 대답하고 있다..
리액트랑 넥스트 강의를 빨리 완강해야 할텐데..
수업시간에 빨리빨리 타자 쳐서 따라가야겠다.
아예 모르는 걸 하려니까 어렵다ㅜ
열심히 합시다.
——————————————————————————
본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다.
#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정