#4 supabase에서 데이터 불러오기

Supabase를 연결하고 데이터를 불러오자


Table Of Contents


Supabase 패키지 설치


Supabase 패키지를 npm으로 설치해준다.

npm install @supabase/supabase-js

Supabase 연동


프로젝트 생성

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);

💥 process is not defined 에러

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 ...

데이터 로드 성공

콘솔로 찍어 보니 데이터가 잘 받아와졌다!

Parent-children 트리 구조로 만들기


재구조화

기존 [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를 클릭하는 경우도 넣어줬다. 이게 최선일까?