こんにちは、コードいじり大好き人間です 👋
今回はずっと「あとでやろ〜」って放置してたタグ検索機能を、ついに!実装しました。
勢いで作ったので、その備忘録をここにドーンと置いておきます💪
📄 概要
Remix と microCMS を使ったブログに、タグをポチッと押すだけで記事一覧を絞り込める機能を入れました。
useSearchParams
で URL にタグ名くっつけて、loader 側でフィルタかける感じです。
記事詳細ページからもタグ付きで一覧に戻れるようになってます。ちょっと便利でしょ?
💡 今回の実装で考えたこと
Remix には action っていう、フォーム送信とか画面の操作に合わせて非同期処理を動かすイケてる機能があります。
結果は useActionData
で受け取れるんだけど、今回はあえて使いませんでした。
理由はカンタン、
「useActionData
と loader のどっち使うの?」「うわ、ややこしい…」って未来が見えたから🤣
なので今回は、タグクリック → URL にクエリ追加 → loader 再実行! っていうシンプル路線にしました。
🔍 microCMS の仕様チェック
docs をポチポチ見てたら、filters
パラメータにタグ突っ込むと検索できることが判明。
タグはカンマ区切りにしてあって、contains
を使えば部分一致検索もバッチリ。
client
.get({
endpoint: 'posts',
queries: { filters: 'tags[contains]タグ名' }
})
.then((res) => console.log(res));
これ動いたとき「おぉ〜動いた!」ってひとりでニヤけたのは秘密です🤫
📝 投稿一覧側の対応
タグを押したら URL にタグ名をセット、useSearchParams
でパラメータ更新。
すると自動で loader が再実行されて、新しい一覧が出てくる…うん、これ好き。
実装例(ContentCard)
import { useSearchParams } from "@remix-run/react";
export function ContentCard({ title, date, tags }: Props) {
const [, setSearchParams] = useSearchParams();
return (
<article className="card w-full">
<div className="card-body flex flex-col gap-4">
{tags && (
<div className="flex gap-2">
{tags.map((tag, index) => (
<button
type="button"
onClick={() => {
const params = new URLSearchParams();
params.set("tag", encodeURI(tag));
setSearchParams(params);
}}
key={index}
className="btn btn-accent btn-xs rounded-full text-xs"
>
{tag}
</button>
))}
</div>
)}
</div>
</article>
);
}
⚙️ loader 側の修正
export const clientLoader = async ({ request }: ClientLoaderFunctionArgs) => {
const parseRequest = zx.parseQuerySafe(request, {
tag: z.string().optional(),
});
if (!parseRequest.success) return;
try {
const queryTag = parseRequest.data.tag;
const result = await client.get<PostsResult>({
endpoint: "posts",
queries: {
limit: 10,
offset: 0,
filters:
queryTag === undefined
? undefined
: `tags[contains]${decodeURI(queryTag)}`,
},
});
return result;
} catch (error) {
console.error("posts clientLoader error", error);
return;
}
};
microCMS はエンコードされたままだと検索してくれないので、decodeURI
は忘れずに。
(忘れて30分くらい首をひねったのはワタシです…😇)
📄 記事詳細ページ側の対応
こっちはめちゃ簡単。useNavigate
でタグ付きの一覧ページに飛ばすだけ。
$path
は Remix Routes の型付きルーティングです。
export default function PostId() {
const contents = useLoaderData<typeof clientLoader>();
const navigate = useNavigate();
return (
<div className="card bg-base-100 w-[80%] h-full">
<div className="card-body flex flex-col gap-4">
<div className="flex gap-2">
{contents.tags?.split(",").map((tag, index) => (
<button
type="button"
onClick={() => {
navigate(
$path("/posts", {
tag: encodeURI(tag),
}),
);
}}
key={index}
className="btn btn-accent btn-xs rounded-full text-xs "
>
{tag}
</button>
))}
</div>
</div>
</div>
);
}
📌 まとめ
- タグクリックでサクッと絞り込み
- 詳細ページからもタグ付きで一覧に戻れる
- microCMS の
contains
で部分一致OK -
decodeURI
忘れると地獄を見る(体験談)
ここまで目を通してくださってありがとうございました!!!