Supabase 패키지를 npm으로 설치해준다.
npm install @supabase/supabase-js
supabase에서 프로젝트를 생성하고, Setting - API에서 URL과 API key를 가져와서 .env에 저장해준다.
SUPABASE_URL=SUPABASE_URL
SUPABASE_KEY=SUPABASE_KEY
이 변수들을 불러와서 supabase 서버에 연결해준다.
import { createClient } from "@supabase/supabase-js";
const SUPABASE_URL = process.env.SUPABASE_URL ? process.env.SUPABASE_URL : "";
const SUPABASE_KEY = process.env.SUPABASE_KEY ? process.env.SUPABASE_KEY : "";
export const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
Supabase는 TypeScript를 지원하기 때문에 아래처럼 타입을 생성할 수 있다.
export interface Database {
public: {
Tables: {
movies: {
Row: {
// the data expected from .select()
id: number;
title: string;
created_at: Date;
last_edited_at: Date;
content: Date;
parent_id: number;
};
Insert: {
// the data to be passed to .insert()
};
Update: {
// the data to be passed to .update()
};
};
};
};
}
아래처럼 불러와서 적용시켜주자.
import { createClient } from "@supabase/supabase-js";
import { Database } from "./types";
const SUPABASE_URL = process.env.SUPABASE_URL ? process.env.SUPABASE_URL : "";
const SUPABASE_KEY = process.env.SUPABASE_KEY ? process.env.SUPABASE_KEY : "";
export const supabase = createClient<Database>(SUPABASE_URL, SUPABASE_KEY);
service core:user:worker: Uncaught ReferenceError: process is not defined at fe8kda2uxw.js:11436:10
프로젝트 처음으로 시간이 걸리는 에러를 만났다.
React나 Next에서는 process.env로 .env 파일을 불러올 수 있었는데, Remix에서는 process.env를 서버 사이드 코드에서만 불러올 수 있다고 한다.
loader() 함수에서 아래처럼 context를 이용해서 불러와주면 된다.
export const loader = async ({ context }: LoaderArgs) => {
return {
SUPABASE_URL: context.env.SUPABASE_URL,
SUPABASE_KEY: context.env.SUPABASE_KEY,
};
};
최종적으로는 아래처럼 supabase에 연결해서 데이터를 호출하는 부분까지 작성했다.
import react from "react";
import type { LoaderArgs } from "@remix-run/cloudflare";
import { useLoaderData } from "@remix-run/react";
import { createClient } from "@supabase/supabase-js";
import { Database } from "@supabase/types";
export const loader = async ({ context }: LoaderArgs) => {
const supabase = createClient<Database>(
context.env.SUPABASE_URL,
context.env.SUPABASE_KEY
);
const loadData = async () => {
try {
const { data, error } = await supabase.from("posts").select();
if (error) throw new Error();
return data;
} catch (err) {
alert("데이터를 불러오지 못했습니다");
return null;
}
};
return loadData();
};
export default ...
콘솔로 찍어 보니 데이터가 잘 받아와졌다!
기존 [item1, item2, …itemN]의 array를
[
{...item1,
children:[
{...item3,
children: []
}
]
},
{...item2,
children:[]
},
]
같은 트리 구조로 만들어야 한다.
function buildTree(items: any[]) {
const itemMap = {};
for (const item of items) {
itemMap[item.id] = { ...item, children: [] };
}
const rootNodes = [];
for (const item of items) {
const parentID = item.parent_id;
if (parentID === null || !itemMap[parentID]) {
rootNodes.push(itemMap[item.id]);
} else {
itemMap[parentID].children.push(itemMap[item.id]);
}
}
return rootNodes;
}
buildTree(rawData)
처럼 호출하면 데이터를 원하는 구조로 변환할 수 있다.
이제 데이터를 아래와 같은 형식으로 표현해줘야 한다.
우선 parent가 null인 노드들에 대해서 아래처럼 map 함수를 호출해준 뒤, renderTreeItem 함수를 재귀적으로 호출한다.
{data.map((datum, datumIdx: number) => {
return renderTreeItem(datum, datumIdx, 0);
})}
renderTreeItem의 구현은 아래와 같다. 우선 자기 자신을 그리고, 만약 자식 노드들이 있다면 자식 노들을 그려줘야 한다.
map 함수를 사용해야 하기 때문에 key를 할당해 주는 것을 잊지 말고, depth+1을 인자로 전달해서 자식 노드들에는 인덴트를 조금씩 더 넣어줘야 한다.
const renderTreeItem = (item, idx: number, depth: number) => {
return (
<div key={idx}>
<CategoryItem
href={`/subBlog/${item.id}`}
title={item.title}
postCount={item.postCount}
indent={depth}
isSelected={false}
/>
{item?.children.map((child, childIdx: number) =>
renderTreeItem(child, childIdx, depth + 1)
)}
</div>
);
};
화살표들을 열고 닫는 기능도 만들었다!
초록색 부분이 모두 a 태그라서 안의 button 태그를 누를 때마다 반응했는데, 이 부분은 이벤트 핸들러를 달아서 해결했다.
const handleAnchorClick = (event) => {
if (
event.target.tagName.toLowerCase() === "button" ||
event.target.tagName.toLowerCase() === "svg" ||
event.target.tagName.toLowerCase() === "path"
) {
event.preventDefault();
}
};
const handleButtonClick = () => {
setDataOpen(id);
};
a 태그에 onClick={handleAnchorClick}
을 넣어주고 위처럼 button/svg/path를 클릭한 경우에는 a태그의 기본 동작이 작동하지 않도록 했다. button을 클릭하는 경우만 넣으면 될 줄 알았는데 화살표를 클릭하는 경우에 svg나 path를 클릭하는 걸로 잡혀서 svg나 path를 클릭하는 경우도 넣어줬다. 이게 최선일까?