Search Paramsによる検索機能
本記事の完成品です。
Your Title
Your Content
はじめに
この記事では、Next.js の Server Components を使って、検索機能を実装します。 サーバーサイドで検索を行うことで、セキュリティ面の向上に繋がり、クライアントの JavaScript の負荷を軽減することができます。
以下このブログサイトの検索機能を例に、実装方法を解説します。
page.tsx
export default async function BlogPage({ searchParams }: { searchParams: { search?: string } }) {
const searchQuery = searchParams.search ?? '';
let blogs: Blog[] = [];
// ブログ記事のデータを取得
const initialBlogsData = allBlogs
.filter((post) => post.published)
.sort((a, b) => {
return compareDesc(new Date(a.date), new Date(b.date));
});
// 検索クエリに一致する記事を抽出
const filteredBlogsData = initialBlogsData.filter((blog) => {
const title = blog.title.toLowerCase();
const tags = blog.tags.map((tag) => tag.toLowerCase());
const search = searchQuery.toLowerCase();
return title.includes(search) || tags.includes(search);
});
if (searchQuery.length > 0) {
if (filteredBlogsData) {
blogs = filteredBlogsData;
} else {
blogs = [];
}
} else {
blogs = initialBlogsData ?? [];
}
return (
<>
<h1 className="text-4xl font-bold">記事一覧</h1>
<p className="text-lg text-neutral-500 pt-2">執筆中の記事を一覧で表示します。</p>
<SearchServerParams />
<hr className="my-8" />
{blogs.length ? (
<div className="grid gap-10 grid-cols-1 sm:grid-cols-2">
<BlogsList blogs={blogs} />
</div>
) : (
<p>記事が見つかりませんでした。</p>
)}
</>
);
}
このコードはpage.tsx
で Server Components の引数にsearchParams
を追加したものです。
検索パラメータを URL に追加することで、サーバーサイドで検索を行うことを可能にします。
Input コンポーネントで文字が入力される度に、searchParams
の値を更新します。
そのため、searchParams
の値が変更される度に、変数の blogs も更新され、検索結果が表示されます。
SearchServerParams.tsx
'use client';
import { useCallback, useEffect, useState, useTransition } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import { Input } from '../ui/input';
import Spinner from '../ui/spinner';
const SearchServerParams = () => {
const [inputValue, setInputValue] = useState<string>('');
const [debouncedValue, setDebouncedValue] = useState<string>('');
const [mounted, setMounted] = useState<boolean>(false);
const router = useRouter();
const pathname = usePathname();
const [isPending, startTransition] = useTransition();
const handleSearchParams = useCallback(
(debouncedValue: string) => {
let params = new URLSearchParams(window.location.search);
if (debouncedValue.length > 0) {
params.set('search', debouncedValue);
} else {
params.delete('search');
}
startTransition(() => {
router.replace(`${pathname}?${params.toString()}`);
});
},
[pathname, router],
);
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const searchQuery = params.get('search') ?? '';
setInputValue(searchQuery);
}, []);
useEffect(() => {
if (debouncedValue.length > 0 && !mounted) {
setMounted(true);
}
}, [debouncedValue, mounted]);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(inputValue);
}, 500);
return () => {
clearTimeout(timer);
};
}, [inputValue]);
useEffect(() => {
if (mounted) handleSearchParams(debouncedValue);
}, [debouncedValue, handleSearchParams, mounted]);
return (
<div className="relative mt-8 mb-5">
<Input
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
placeholder="Search Blog"
className="text-base"
/>
{isPending && (
<div className="absolute top-2 right-2">
<Spinner />
</div>
)}
</div>
);
};
export default SearchServerParams;
ここでのコードはやや複雑ですが、順を追って見ていきましょう。
const handleSearchParams = useCallback(
(debouncedValue: string) => {
let params = new URLSearchParams(window.location.search);
if (debouncedValue.length > 0) {
params.set('search', debouncedValue);
} else {
params.delete('search');
}
startTransition(() => {
router.replace(`${pathname}?${params.toString()}`);
});
},
[pathname, router],
);
まず、handleSearchParams
では、debouncedValue
の値をsearch
クエリパラメータにセットします。
これは、debouncedValue
が変更される度に呼び出されます。
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const searchQuery = params.get('search') ?? '';
setInputValue(searchQuery);
}, []);
次に、useEffect
を用いて、search
クエリパラメータの値をinputValue
にセットします。
これは、ページが読み込まれた時に呼び出されます。
useEffect(() => {
if (debouncedValue.length > 0 && !mounted) {
setMounted(true);
}
}, [debouncedValue, mounted]);
次に、useEffect
を用いて、debouncedValue
の値が変更された時に、mounted
の値をtrue
にします。
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(inputValue);
}, 500);
return () => {
clearTimeout(timer);
};
}, [inputValue]);
次に、useEffect
を用いて、inputValue
の値が変更された時に、debouncedValue
の値をinputValue
にセットします。
これは、inputValue
が変更される度に呼び出されます。
useEffect(() => {
if (mounted) handleSearchParams(debouncedValue);
}, [debouncedValue, handleSearchParams, mounted]);
最後に、useEffect
を用いて、debouncedValue
の値が変更された時に、handleSearchParams
を呼び出します。
これは、debouncedValue
が変更される度に呼び出されます。
これらのuseEffect
の呼び出し順は、以下のようになります。
- ページが読み込まれた時に、
search
クエリパラメータの値をinputValue
にセットする。 inputValue
の値が変更された時に、debouncedValue
の値をinputValue
にセットする。debouncedValue
の値が変更された時に、handleSearchParams
を呼び出す。debouncedValue
の値が変更された時に、mounted
の値をtrue
にする。debouncedValue
の値が変更された時に、handleSearchParams
を呼び出す。
まとめ
今回は、useEffect
を用いて、search
クエリパラメータの値をinputValue
にセットする方法を学びました。