기능 정의
- 검색창에 검색어를 입력하면 `title` 또는 `location`에 검색어가 포함된 데이터를 노출시킨다.
- 뒤로가기를 눌렀을 때, 이전 검색 결과로 이동한다.
컴포넌트 구현
검색창 코드
"뒤로가기를 눌렀을 때, 이전 검색 결과로 이동한다."라는 기능을 구현하기 위해서,
검색어를 입력했을 때 히스토리 스택이 추가되어야 합니다.
따라서 검색어를 검색하면 url 쿼리를 추가하여 히스토리 스택을 쌓아봅시다.
import { Input } from "@nextui-org/input";
import { ChangeEvent, useState } from "react";
interface SearchInputProps {
onSubmit: (value: string) => void;
}
const SearchInput = ({ onSubmit }: SearchInputProps) => {
const [value, setValue] = useState("");
return (
<form
onSubmit={(e) =>
{
e.preventDefault();
onSubmit(value);
}}
>
<Input
value={value}
onChange={(e) =>
{ setValue(e.target.value) }
}
placeholder="테마명/지점 검색"
autoComplete="off"
enterKeyHint="search"
/>
</form>
);
};
export default SearchInput;
`form`이 제출되었을 때 화면이 깜빡이는 현상을 막기 위해 `e.preventDefault()`를 실행시킨 후,
props로 받은 `onSubmit`함수에 검색어를 파라미터에 넣고 호출합니다.
<SearchInput onSubmit={(search) => router.push(`?search=${search}`)} />
`SearchInput`의 부모 컴포넌트에서 위와 같이 `prop`을 넘겨줍니다.
`onSubmit` 함수의 파라미터로 전달받은 값을 url 쿼리에 포함시켜 `router.push`합니다.
검색 페이지 코드
import { useRouter, useSearchParams } from "next/navigation";
const searchParmas = useSearchParams();
const search = searchParmas.get("search");
url이 `https://url.com?search=검색어` 일 경우,
`search`에 해당하는 "검색어" 라는 문자열을 가져오기 위해서 위와 같은 코드를 사용할 수 있습니다.
import { useRouter, useSearchParams } from "next/navigation";
// ...
const searchParmas = useSearchParams();
const search = searchParmas.get("search");
useEffect(() => {
const fetchThemes = async () => {
setIsLoading(true);
let url = `api/theme`;
if (search) url += `?search=${search}`;
try {
const response = await fetch(url);
const { data } = await response.json();
setThemes(data);
} finally {
setIsLoading(false);
}
};
}, [search])
url에서 가져온 `search` 값을 사용하여 next API 를 호출하는 코드입니다.
url의 `searchParam`에 `search` 값이 있다면 API url에 `? search=${search}`라는 쿼리를 추가합니다.
`fetch`를 사용하여 API를 호출하고 받아온 `data`를 `themes` state에 저장합니다.
url의 `search` 값이 변경되면 API를 다시 호출하기 위해 `dependency`에 `search`를 추가합니다.
{themes.map((theme) => (
<ThemeCard key={theme.id} theme={theme} />
))}
`themes` state에 저장한 값을 다음과 같이 노출시키면 완성입니다!
"use client";
import { useRouter, useSearchParams } from "next/navigation";
// ...
import SearchInput from "~/app/themes/components/SearchInput";
const SearchPage = () => {
const router = useRouter();
const searchParmas = useSearchParams();
const search = searchParmas.get("search");
const [themes, setThemes] = useState<Theme[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchThemes = async () => {
setIsLoading(true);
let url = `api/theme`;
if (search) url += `?search=${search}`;
try {
const response = await fetch(url);
const { data } = await response.json();
setThemes(data);
} finally {
setIsLoading(false);
}
};
fetchThemes();
}, [search]);
if (isLoading) return <></>
return (
<>
<SearchInput onSubmit={(search) => router.push(`?search=${search}`)} />
<Spacing px={30} />
<div className="flex pl-[12px]">
{search ? (
<Text>"{search}" 검색 결과</Text>
) : (
<Text>전체 검색 결과</Text>
)}
</div>
<Spacing px={16} />
<ul className="grid grid-cols-2 gap-[8px]">
{themes.map((theme) => (
<ThemeCard key={theme.id} theme={theme} />
))}
</ul>
</>
);
};
export default SearchPage;
전체 코드는 위와 같습니다.
API 구현
위에서 구현한 `fetchThemes` 함수를 보면 api 엔드포인트는 `api/theme` 입니다.
따라서 `src/app/api/theme/route.ts` 에 있는 `GET` 함수를 만들어봅시다.
// src/app/api/theme/route.ts
import { NextRequest, NextResponse } from "next/server"
export function GET(req: NextRequest) {
const search = req.nextUrl.searchParams.get('search')
return NextResponse.json({})
}
검색하고자 하는 검색어를 엔드포인트에 `?search=${검색어}`와 같이 쿼리를 붙여주었습니다.
`req.nextUrl.searchParams.get('search')` 를 실행하여 검색어의 값을 가져올 수 있습니다.
const data = await supabase
.from('theme')
.select('*')
.like('title', `%${search}%`)
supabase의 데이터 중 `title`에 검색어를 포함한 데이터를 찾기 위해서 `like` 함수를 사용할 수 있습니다.
const data = await supabase
.from('theme')
.select('*')
.or(`title.like.%${search}%,location.like.%${search}%`)
하지만 기능 정의를 살펴보면 `title` 뿐만 아니라, `location` 컬럼에도 검색어가 포함되어 있는지 함께 검색해야 합니다.
이럴 땐 여러 쿼리를 사용할 수 있는 `or` 함수를 사용합니다.
export async function GET(req: NextRequest) {
const supabase = createClient();
let query = supabase.from("theme").select("*");
const search = req.nextUrl.searchParams.get("search");
if (search) {
query = query.or(`title.like.%${search}%,location.like.%${search}%`);
}
const { data, error, status } = await query;
return NextResponse.json({ data, error }, { status });
}
전체 코드입니다.
`search`의 유무에 따라서 `like` 쿼리 실행 여부를 결정했습니다.
supabase 쿼리를 실행한 결과는 `NextResponse`에 `json` 형태로 담아 전달해 줍니다.
결과
검색창에 "포레"라고 검색했을 때, `url`에 `search="포레"` 쿼리가 추가되고,
결과값으로 "포레"를 포함하고 있는 항목이 성공적으로 노출됩니다. 🎉
'개발 > Next' 카테고리의 다른 글
[Next14] To Do List 만들기 with Supabase (2) Next API Route 사용하여 할 일 목록 구현하기 (2) | 2024.07.08 |
---|---|
[Next14] To Do List 만들기 with Supabase (1) 프로젝트 세팅 (0) | 2024.07.07 |
[Next14] 페이지와 레이아웃 : Pages and Layouts (0) | 2024.05.26 |
[Next14] 라우트 정의하기 : Defining Routes (0) | 2024.05.26 |
[Next14] 라우팅 기초 : Routing Fundamentals (0) | 2024.05.26 |