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

psst54·2023년 8월 7일
0
post-thumbnail

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

profile
[Front-end Engineer] 안 되는 것 빼고 다 합니다

0개의 댓글