Supabase와 NextJS로 블로그 만들기 - Search
안녕하세요?
이번 시간에는 Supabase 블로그 마지막 시간이 Search 기능 추가 시간입니다.
블로그 포스트의 생성, 조회, 업데이트, 삭제 기능을 완성했는데요.
남은 건 바로 검색 기능입니다.
/pages/post/search.tsx 파일로 진행할 예정입니다.
일단 User-Authenticated 템플릿을 아래와 같이 준비합시다.
import React, { useState } from "react";
import Link from "next/link";
import { GetServerSideProps } from "next";
import { useMessage } from "../../lib/message";
import { useFormFields } from "../../lib/utils";
import { supabase } from "../../lib/supabase";
import { User } from "@supabase/supabase-js";
import classNames from "classnames";
const SearchPost = ({ user }: { user: User }) => {
const { messages, handleMessage } = useMessage();
return (
<div className="flex flex-col items-center justify-start py-4 min-h-screen">
<h2 className="text-lg my-4">
Hello, {user && user.email ? user.email : "Supabase User!"}
</h2>
{messages &&
messages.map((message, index) => (
<div
key={index}
className={classNames(
"shadow-md rounded px-3 py-2 text-shadow transition-all mt-2 text-center",
message.type === "error"
? "bg-red-500 text-white"
: message.type === "success"
? "bg-green-300 text-gray-800"
: "bg-gray-100 text-gray-800"
)}
>
{message.message}
</div>
))}
<h1 className="py-3 font-semibold text-2xl text-blue-600">
Search Posts
</h1>
};
export default SearchPost;
export type NextAppPageUserProps = {
props: {
user: User;
loggedIn: boolean;
};
};
export type NextAppPageRedirProps = {
redirect: {
destination: string;
permanent: boolean;
};
};
export type NextAppPageServerSideProps =
| NextAppPageUserProps
| NextAppPageRedirProps;
export const getServerSideProps: GetServerSideProps = async ({
req,
}): Promise<NextAppPageServerSideProps> => {
const { user } = await supabase.auth.api.getUserByCookie(req);
if (!user) {
return {
redirect: {
destination: "/auth",
permanent: false,
},
};
}
return {
props: {
user,
loggedIn: !!user,
},
};
};
이제 Search를 위한 Form 필드 관련 State를 준비해야겠죠.
type FormFieldProps = {
title: string;
id?: number;
};
const FORM_VALUES: FormFieldProps = {
title: "",
};
const SearchPost = ({ user }: { user: User }) => {
const { messages, handleMessage } = useMessage();
const [values, handleChange, resetFormFields] =
useFormFields<FormFieldProps>(FORM_VALUES);
const [posts, setPosts] = useState<FormFieldProps[]>(null);
FormFieldProps에는 title을 필수, id를 옵셔널로 지정했습니다.
왜냐하면 useFormFields에서 form 관리를 할 때는 title로만 검색할 예정이거든요.
그리고 supabase.select로 검색된 post를 불러왔을 때는 title, id 두 개를 불러올 예정입니다.
그래서 id 부분을 옵셔널로 지정했습니다.
그리고 posts, setPosts 처럼 검색된 post 결과를 배열로 지정했습니다.
이제 검색방법에 대해 알아볼까요?
const handleSumbit = async (event: React.FormEvent) => {
event.preventDefault();
const { data: posts, error } = await supabase
.from("posts")
.select("title, id")
.like("title", `%${values.title}%`)
.order("id");
if (error) {
console.log(error);
handleMessage({
message: "Error : Search Posts",
type: "error",
});
} else {
// console.log(posts);
setPosts(posts);
}
resetFormFields();
};
handleSubmit 함수를 살펴봅시다.
form의 values.title에 있는 값을 supabase 검색을 이용하는데요.
title의 정확한 값을 검색하려면 textSearch를 써도 되지만, 저 같은 경우는 검색어가 들어가 있는 모든 Post를 검색하기를 원해서 like 메써드를 사용했습니다.
supabase.select.like.order 방식인데요.
like 메써드 안에는 "title"처럼 어느 칼럼에서 검색할지 먼저 지정하고,
두 번째처럼
여기서 주의할 점은 '%${values.title}%' 이렇게 쓰시면 안 됩니다.
SQL 명령어로 볼 때 다음과 같이 작동해야 하기 때문입니다.
select title, id from posts where title like %query%;
query 앞과 끝에 %가 와야 하기 때문에 따옴표를 넣으면 안 됩니다.
마지막으로 UI의 Form 부분입니다.
return (
<div className="flex flex-col items-center justify-start py-4 min-h-screen">
<h2 className="text-lg my-4">
Hello, {user && user.email ? user.email : "Supabase User!"}
</h2>
{messages &&
messages.map((message, index) => (
<div
key={index}
className={classNames(
"shadow-md rounded px-3 py-2 text-shadow transition-all mt-2 text-center",
message.type === "error"
? "bg-red-500 text-white"
: message.type === "success"
? "bg-green-300 text-gray-800"
: "bg-gray-100 text-gray-800"
)}
>
{message.message}
</div>
))}
<h1 className="py-3 font-semibold text-2xl text-blue-600">
Search Posts
</h1>
<form
onSubmit={handleSumbit}
className="bg-white w-3/4 md:w-1/2 shadow-md rounded px-8 pt-6 pb-8 mb-4"
>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="title"
>
Title
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id="title"
name="title"
required
value={values.title}
onChange={handleChange}
/>
</div>
<div className="flex gap-2">
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit"
>
Search
</button>
</div>
</form>
{!!posts &&
posts.map((post, index) => (
<section className="text-center lg:text-left">
<div className="max-w-screen-xl px-4 py-4 mx-auto sm:px-6 lg:px-8 lg:items-end lg:justify-between lg:flex">
<div className="max-w-xl mx-auto lg:ml-0">
<Link href={`/post/${post.id}`}>
<a className="mt-2 text-3xl font-bold sm:text-4xl text-gray-500">
{post?.title}
</a>
</Link>
</div>
</div>
</section>
))}
</div>
);
실행 결과를 볼까요?
검색 결과를 클릭하면 Post의 상세 페이지로 이동될 겁니다.
이상으로 Supabase 블로그 시스템을 완성했고요.
여러분께서 직접 검색 시 content에서도 검색할 수 있게 한번 코드를 수정해 보시는 것도 좋을 듯 싶습니다.